summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Knobloch <jonas.knobloch@t-online.de>2026-04-27 23:07:17 +0200
committerJonas Knobloch <jonas.knobloch@t-online.de>2026-04-27 23:07:17 +0200
commit7a704818d55baca346a691a4f7e4560294b542ce (patch)
treef7705e4b8c92da7a4e1f1ebb3c6b1d7aa15d1d3c
parent98309180f26fed7299a9890f3873e801f319627c (diff)
Add utility to count lines
-rw-r--r--dataset/count.go92
-rw-r--r--dataset/count_test.go135
-rw-r--r--dataset/go.mod12
-rw-r--r--dataset/go.sum30
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=