diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 13:47:35 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-09-17 13:47:35 +0200 |
| commit | b596085d2391ce3701fa6820a6728ba634ac453b (patch) | |
| tree | 4943546cb21326de6195a64bd94eab430a8e1cb1 /internal/dup/dup_test.go | |
| parent | 6971543d4749574d4ca575c4e8acf04f9e86d6bb (diff) | |
| download | krino-b596085d2391ce3701fa6820a6728ba634ac453b.tar.gz krino-b596085d2391ce3701fa6820a6728ba634ac453b.zip | |
a content class is worked out once, not once per copy
Lookup walked the whole size group for every file in it, so N copies of
one file cost N walks of an N-member group, each taking the index's
lock at every step - which is why more workers made it slower rather
than faster. Every member of a class elects the same original (the
invariant identicalTo already documents and a test already pins), so
the class is memoised on the first walk and every later member is a
map lookup.
Measured over identical files, invented data, same machine:
500 files 0.17s -> 0.12s
2000 files 1.89s -> 0.58s
and the plans are byte-identical once the sandbox path is normalised.
The test counts walks: one per content class, not one per file.
Diffstat (limited to 'internal/dup/dup_test.go')
| -rw-r--r-- | internal/dup/dup_test.go | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/internal/dup/dup_test.go b/internal/dup/dup_test.go index 8984336..a616118 100644 --- a/internal/dup/dup_test.go +++ b/internal/dup/dup_test.go @@ -577,3 +577,52 @@ func TestThreeCopiesWithOverlappingExtraDirKeepOne(t *testing.T) { } } } + +// TestLookupWalksAClassOnce: every member of a content class elects the +// same original, so the class is worth computing once. Walking the size +// group again for every member makes a directory of N copies cost N^2 +// comparisons - 8000 identical files took 35 s of it - and the walk takes +// the index's lock at every step, so more workers made it slower rather +// than faster. This counts the walks: one per class, not one per file. +func TestLookupWalksAClassOnce(t *testing.T) { + d := t.TempDir() + const n = 12 + var files []scan.File + for i := 0; i < n; i++ { + // Two classes of one size, so the size group cannot be the thing + // being memoised. + body := "the same bytes for all of these" + if i%2 == 1 { + body = "different bytes, identical size" + } + files = append(files, put(t, d, fmt.Sprintf("f%02d.bin", i), []byte(body), i)) + } + x, errs := NewIndex(files, nil) + if len(errs) > 0 { + t.Fatal(errs) + } + for _, f := range files { + lookup(t, x, f) + } + if x.walks > 2 { + t.Errorf("identicalTo ran %d times for 2 content classes over %d files; want one walk per class", x.walks, n) + } + + // The answers must be what they were before memoising: one original + // per class, and that original is not itself a duplicate. + dups := 0 + origs := map[string]int{} + for _, f := range files { + orig, isDup := lookup(t, x, f) + if isDup { + dups++ + } + origs[orig]++ + } + if dups != n-2 { + t.Errorf("%d duplicates over two classes of %d files, want %d", dups, n, n-2) + } + if len(origs) != 2 { + t.Errorf("the two classes elected %d originals, want 2", len(origs)) + } +} |
