diff options
| author | Jonas Knobloch <jonas.knobloch@t-online.de> | 2026-04-27 23:07:17 +0200 |
|---|---|---|
| committer | Jonas Knobloch <jonas.knobloch@t-online.de> | 2026-04-27 23:07:17 +0200 |
| commit | 7a704818d55baca346a691a4f7e4560294b542ce (patch) | |
| tree | f7705e4b8c92da7a4e1f1ebb3c6b1d7aa15d1d3c | |
| parent | 98309180f26fed7299a9890f3873e801f319627c (diff) | |
Add utility to count lines
| -rw-r--r-- | dataset/count.go | 92 | ||||
| -rw-r--r-- | dataset/count_test.go | 135 | ||||
| -rw-r--r-- | dataset/go.mod | 12 | ||||
| -rw-r--r-- | dataset/go.sum | 30 |
4 files changed, 267 insertions, 2 deletions
diff --git a/dataset/count.go b/dataset/count.go new file mode 100644 index 0000000..b2e6d14 --- /dev/null +++ b/dataset/count.go @@ -0,0 +1,92 @@ +package dataset + +import ( + "bytes" + "context" + "io" + "os" + "runtime" + "sync/atomic" + + "golang.org/x/sync/errgroup" +) + +func countLinesAll(names []string, delimiter []byte) (int, error) { + g, ctx := errgroup.WithContext(context.Background()) + + g.SetLimit(runtime.NumCPU() - 1) + + var total atomic.Int64 + + for _, name := range names { + g.Go(func() error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + var file *os.File + + if f, err := os.Open(name); err != nil { + return err + } else { + file = f + } + + if n, err := countLines(file, delimiter); err != nil { + return err + } else { + total.Add(n) + } + + return nil + }) + } + + if err := g.Wait(); err != nil { + return 0, err + } + + return int(total.Load()), nil +} + +func countLines(r io.Reader, delimiter []byte) (int64, error) { + if len(delimiter) == 0 { + panic("empty delimiter") + } + + l := len(delimiter) + + buf := make([]byte, 128*1024) + + var total int64 + + carry := 0 + + for { + n, err := r.Read(buf[carry:]) + + active := carry + n + + if active >= l { + total += int64(bytes.Count(buf[:active], delimiter)) + + carry = l - 1 + + copy(buf[:carry], buf[active-carry:]) + } else { + carry = active + } + + if err == io.EOF { + break + } + + if err != nil { + return 0, err + } + } + + return total, nil +} diff --git a/dataset/count_test.go b/dataset/count_test.go new file mode 100644 index 0000000..0df1ffe --- /dev/null +++ b/dataset/count_test.go @@ -0,0 +1,135 @@ +package dataset + +import ( + "bufio" + "os" + "sync" + "testing" + + "go.jknobloc.com/x/shelf" +) + +func TestCountLinesAll(t *testing.T) { + names := []string{ + shelf.Abs("data/babylm/train_100M/bnc_spoken.train"), + shelf.Abs("data/babylm/train_100M/childes.train"), + shelf.Abs("data/babylm/train_100M/gutenberg.train"), + shelf.Abs("data/babylm/train_100M/open_subtitles.train"), + shelf.Abs("data/babylm/train_100M/simple_wiki.train"), + shelf.Abs("data/babylm/train_100M/switchboard.train"), + } + + n, err := countLinesAll(names, []byte("\n")) + + if err != nil { + t.Fatal(err) + } + + m, err := countLinesNaive(names...) + + if err != nil { + t.Fatal(err) + } + + if n != m { + t.Errorf("expected %d but got %d\n", m, n) + } +} + +func BenchmarkCountAll(b *testing.B) { + names := []string{ + shelf.Abs("data/babylm/train_100M/bnc_spoken.train"), + shelf.Abs("data/babylm/train_100M/childes.train"), + shelf.Abs("data/babylm/train_100M/gutenberg.train"), + shelf.Abs("data/babylm/train_100M/open_subtitles.train"), + shelf.Abs("data/babylm/train_100M/simple_wiki.train"), + shelf.Abs("data/babylm/train_100M/switchboard.train"), + } + + for i := 0; i < b.N; i++ { + _, err := countLinesAll(names, []byte("\n")) + + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkCountLinesNaive(b *testing.B) { + names := []string{ + shelf.Abs("data/babylm/train_100M/bnc_spoken.train"), + shelf.Abs("data/babylm/train_100M/childes.train"), + shelf.Abs("data/babylm/train_100M/gutenberg.train"), + shelf.Abs("data/babylm/train_100M/open_subtitles.train"), + shelf.Abs("data/babylm/train_100M/simple_wiki.train"), + shelf.Abs("data/babylm/train_100M/switchboard.train"), + } + + for i := 0; i < b.N; i++ { + _, err := countLinesNaive(names...) + + if err != nil { + b.Fatal(err) + } + } +} + +func countLinesNaive(names ...string) (int, error) { + var wg sync.WaitGroup + + results := make(chan int, len(names)) + errors := make(chan error, len(names)) + + for _, name := range names { + wg.Add(1) + + go func() { + defer wg.Done() + + var scanner *bufio.Scanner + + if file, err := os.Open(name); err != nil { + results <- 0 + errors <- err + + return + } else { + scanner = bufio.NewScanner(file) + + buf := make([]byte, 0, 1024*1024) + + scanner.Buffer(buf, 1024*1024) + + defer file.Close() + } + + count := 0 + + for scanner.Scan() { + count++ + } + + if err := scanner.Err(); err != nil { + results <- 0 + errors <- err + + return + } + + results <- count + }() + } + + wg.Wait() + + close(results) + close(errors) + + total := 0 + + for count := range results { + total += count + } + + return total, <-errors +} diff --git a/dataset/go.mod b/dataset/go.mod index 2c4cc0a..c728e2b 100644 --- a/dataset/go.mod +++ b/dataset/go.mod @@ -2,16 +2,24 @@ module go.jknobloc.com/x/dataset go 1.25.0 -require github.com/apache/arrow-go/v18 v18.5.1 +require ( + github.com/apache/arrow-go/v18 v18.5.1 + go.jknobloc.com/x/shelf v0.0.0-20260424095002-12f15fdd8c04 + golang.org/x/sync v0.20.0 +) require ( github.com/andybalholm/brotli v1.2.0 // indirect github.com/apache/thrift v0.22.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.8.0 // indirect + github.com/go-git/go-git/v5 v5.18.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/flatbuffers v25.12.19+incompatible // indirect github.com/google/uuid v1.6.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect github.com/klauspost/compress v1.18.3 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -22,7 +30,6 @@ require ( golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.50.0 // indirect - golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect golang.org/x/text v0.35.0 // indirect @@ -32,4 +39,5 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/dataset/go.sum b/dataset/go.sum index 387cfe9..9a30077 100644 --- a/dataset/go.sum +++ b/dataset/go.sum @@ -8,6 +8,12 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= +github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -24,20 +30,30 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -48,6 +64,8 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= +go.jknobloc.com/x/shelf v0.0.0-20260424095002-12f15fdd8c04 h1:p4CSIEO/qUdDKQ8pPcumce80Tf8m2UFcc2MwElUGQIE= +go.jknobloc.com/x/shelf v0.0.0-20260424095002-12f15fdd8c04/go.mod h1:j+bN57GbQXhfjqGJ0KEeGjGsJ3QpOy9FriNBigqT5xQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= @@ -63,20 +81,32 @@ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42s golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
