Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

128 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package clones
import "sort"
// Length-stratified LSH bounds.
//
// The 0.82 Jaccard threshold implies that two clone bodies' token
// counts differ by at most ~22% — Jaccard(A,B) ≤ min(|A|,|B|) /
// max(|A|,|B|), so a 0.82 clone needs max ≤ min/0.82 ≈ 1.22·min. We
// length-bucket items into 5 overlapping geometric classes (each
// adjacent pair overlapping by ~50%) so any real clone pair lands in
// at least one shared class, while items more than ~1.6× apart in
// size never enter the same LSH pass. On a 150k-item graph this
// alone drops the candidate count by 510× because k8s-style
// boilerplate (options-builders, controllers, list-watchers) shares
// signatures within its size class but doesn't fan out across
// classes.
//
// Bounds are [lo, hi). The catch-all final class covers everything
// beyond 400 tokens. An item at token count tc belongs to every
// class i where lo[i] ≤ tc < hi[i] — usually two adjacent classes
// when tc is in the overlap region, one otherwise.
var lengthBucketBounds = []struct{ lo, hi int }{
{0, 80},
{50, 160},
{100, 320},
{200, 640},
{400, 1 << 31},
}
// lengthClassesOf returns the class indexes a tokenCount belongs to.
// A tokenCount of 0 ("unknown" — legacy items without TokenCount
// stamped) returns every class so the legacy single-bucket detection
// behaviour is preserved.
func lengthClassesOf(tokenCount int) []int {
if tokenCount <= 0 {
all := make([]int, len(lengthBucketBounds))
for i := range all {
all[i] = i
}
return all
}
var out []int
for i, b := range lengthBucketBounds {
if tokenCount >= b.lo && tokenCount < b.hi {
out = append(out, i)
}
}
return out
}
// DetectPairsStratifiedWithStats is the length-stratified variant of
// DetectPairsWithStats. It partitions items into overlapping length
// classes (lengthBucketBounds), runs the per-class LSH pass
// independently, and merges the results.
//
// Two LSH-level wins:
//
// 1. Cross-class candidate pairs never enter any band bucket — pairs
// more than ~1.6× apart in token count are dropped before the
// bucket-fan-out blow-up that hurts non-stratified LSH on huge
// graphs (the per-bucket-cap telemetry's main source).
// 2. Per-class LSH indexes are smaller, so the `seen` dedup map and
// the band buckets themselves use bounded memory regardless of
// the global item count.
//
// Pair output is merged across classes; an item that lands in two
// adjacent classes can have its pairs surface twice (once per class),
// so a canonical-pair dedup runs on the merge.
//
// Items whose TokenCount is 0 fall into every class, so callers that
// haven't migrated to stamping token counts get the unstratified
// behaviour back.
func DetectPairsStratifiedWithStats(items []Item, threshold float64) (pairs []Pair, skippedBuckets, skippedBucketItems int) {
if threshold <= 0 {
threshold = DefaultThreshold
}
if len(items) < 2 {
return nil, 0, 0
}
// Partition into length classes. An item in the overlap region
// of two classes appears in both.
classes := make([][]Item, len(lengthBucketBounds))
for _, it := range items {
for _, c := range lengthClassesOf(it.TokenCount) {
classes[c] = append(classes[c], it)
}
}
// Per-class LSH. Each class is independent — different classes
// share no band buckets and therefore produce no cross-class
// candidate pairs.
seen := make(map[[2]string]struct{})
for _, classItems := range classes {
if len(classItems) < 2 {
continue
}
classPairs, sb, sbi := DetectPairsWithStats(classItems, threshold)
skippedBuckets += sb
skippedBucketItems += sbi
for _, p := range classPairs {
// DetectPairsWithStats already canonicalises A < B inside
// each Pair, so the key matches across classes.
key := [2]string{p.A, p.B}
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
pairs = append(pairs, p)
}
}
// Stable output order so callers logging "found N clones" get
// reproducible numbers across runs. DetectPairsWithStats sorts by
// descending similarity within a class; the merge re-sorts globally.
sort.Slice(pairs, func(i, j int) bool {
if pairs[i].Similarity != pairs[j].Similarity {
return pairs[i].Similarity > pairs[j].Similarity
}
if pairs[i].A != pairs[j].A {
return pairs[i].A < pairs[j].A
}
return pairs[i].B < pairs[j].B
})
return pairs, skippedBuckets, skippedBucketItems
}