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
274 lines
10 KiB
Go
274 lines
10 KiB
Go
package indexer
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/intern"
|
|
)
|
|
|
|
// backingPtr returns the address of a string's backing array so two
|
|
// strings can be checked for *sharing storage* rather than merely being
|
|
// byte-equal. The empty string has no backing array.
|
|
func backingPtr(s string) uintptr {
|
|
if len(s) == 0 {
|
|
return 0
|
|
}
|
|
return uintptr(unsafe.Pointer(unsafe.StringData(s)))
|
|
}
|
|
|
|
// freshString returns a heap copy of s with its own backing array, so
|
|
// equal values never share storage by accident. It models un-interned
|
|
// parse output, where every node holds a separate allocation of the
|
|
// same repetitive field value.
|
|
func freshString(s string) string {
|
|
b := make([]byte, len(s))
|
|
copy(b, s)
|
|
return string(b)
|
|
}
|
|
|
|
// buildFreshNodes constructs files*perFile nodes (and one edge per node)
|
|
// where every repetitive field — file_path, language, kind — is a
|
|
// distinct backing array, as if freshly minted by the parser. The file
|
|
// path is identical within a file but a separate allocation on each node.
|
|
func buildFreshNodes(files, perFile int) ([]*graph.Node, []*graph.Edge) {
|
|
nodes := make([]*graph.Node, 0, files*perFile)
|
|
edges := make([]*graph.Edge, 0, files*perFile)
|
|
for f := 0; f < files; f++ {
|
|
path := fmt.Sprintf("internal/some/deeply/nested/pkg%d/source_file_%d.go", f, f)
|
|
for k := 0; k < perFile; k++ {
|
|
id := fmt.Sprintf("pkg%d/source_file_%d.go::Sym%d", f, f, k)
|
|
next := fmt.Sprintf("pkg%d/source_file_%d.go::Sym%d", f, f, (k+1)%perFile)
|
|
nodes = append(nodes, &graph.Node{
|
|
ID: id,
|
|
Kind: graph.NodeKind(freshString("function")),
|
|
Name: fmt.Sprintf("Sym%d", k),
|
|
FilePath: freshString(path),
|
|
Language: freshString("go"),
|
|
})
|
|
edges = append(edges, &graph.Edge{
|
|
From: id,
|
|
To: next,
|
|
Kind: graph.EdgeCalls,
|
|
FilePath: freshString(path),
|
|
})
|
|
}
|
|
}
|
|
return nodes, edges
|
|
}
|
|
|
|
// internRepetitiveFields routes only the four repetitive node fields
|
|
// through the interner, mirroring what applyRepoPrefix does to them in
|
|
// isolation from per-node ID prefixing — used to measure the live-heap
|
|
// effect of the field interning without the ID table as a confound.
|
|
func internRepetitiveFields(nodes []*graph.Node, edges []*graph.Edge) {
|
|
for _, n := range nodes {
|
|
n.FilePath = intern.String(n.FilePath)
|
|
n.Language = intern.String(n.Language)
|
|
n.Kind = graph.NodeKind(intern.String(string(n.Kind)))
|
|
}
|
|
for _, e := range edges {
|
|
e.FilePath = intern.String(e.FilePath)
|
|
}
|
|
}
|
|
|
|
// applyRepoPrefixPerNodeBaseline reproduces the pre-cache behaviour:
|
|
// it concatenates prefix+FilePath for every node and edge, minting a
|
|
// throwaway string per reference. It exists only as the benchmark
|
|
// baseline the per-file cache improves on.
|
|
func applyRepoPrefixPerNodeBaseline(repoPrefix string, nodes []*graph.Node, edges []*graph.Edge) {
|
|
if repoPrefix == "" {
|
|
return
|
|
}
|
|
prefix := repoPrefix + "/"
|
|
const unresolvedMarker = "unresolved::"
|
|
for _, n := range nodes {
|
|
n.ID = intern.String(prefix + n.ID)
|
|
n.FilePath = intern.String(prefix + n.FilePath)
|
|
n.RepoPrefix = repoPrefix
|
|
n.Name = intern.String(n.Name)
|
|
n.Language = intern.String(n.Language)
|
|
n.Kind = graph.NodeKind(intern.String(string(n.Kind)))
|
|
}
|
|
for _, e := range edges {
|
|
e.From = intern.String(prefix + e.From)
|
|
if strings.HasPrefix(e.To, unresolvedMarker) {
|
|
e.To = intern.String(e.To)
|
|
} else {
|
|
e.To = intern.String(prefix + e.To)
|
|
}
|
|
e.FilePath = intern.String(prefix + e.FilePath)
|
|
}
|
|
}
|
|
|
|
// TestApplyRepoPrefix_InternsRepetitiveFields verifies that after the
|
|
// node-emit stamping pass the four repetitive fields keep their exact
|
|
// values AND that two nodes sharing a value share one backing array —
|
|
// proving the interning actually collapses the duplicates.
|
|
func TestApplyRepoPrefix_InternsRepetitiveFields(t *testing.T) {
|
|
idx := &Indexer{}
|
|
idx.SetRepoPrefix("myrepo")
|
|
|
|
nodes := []*graph.Node{
|
|
{ID: "a.go::A", Kind: graph.NodeKind(freshString("function")), Name: "A", FilePath: freshString("pkg/a.go"), Language: freshString("go")},
|
|
{ID: "a.go::B", Kind: graph.NodeKind(freshString("function")), Name: "B", FilePath: freshString("pkg/a.go"), Language: freshString("go")},
|
|
}
|
|
edges := []*graph.Edge{
|
|
{From: "a.go::A", To: "a.go::B", Kind: graph.EdgeCalls, FilePath: freshString("pkg/a.go")},
|
|
}
|
|
|
|
// Sanity: the two nodes hold genuinely distinct backing arrays for
|
|
// the repetitive fields before interning, so a post-pass match is
|
|
// meaningful rather than an accident of allocation.
|
|
if backingPtr(nodes[0].FilePath) == backingPtr(nodes[1].FilePath) {
|
|
t.Fatal("setup: file paths already share a backing array before interning")
|
|
}
|
|
if backingPtr(string(nodes[0].Kind)) == backingPtr(string(nodes[1].Kind)) {
|
|
t.Fatal("setup: kinds already share a backing array before interning")
|
|
}
|
|
|
|
idx.applyRepoPrefix(nodes, edges)
|
|
|
|
// Values are byte-identical to the unchanged-behaviour expectation.
|
|
const wantPath = "myrepo/pkg/a.go"
|
|
wantID := []string{"myrepo/a.go::A", "myrepo/a.go::B"}
|
|
for i, n := range nodes {
|
|
if n.ID != wantID[i] {
|
|
t.Fatalf("node %d ID = %q, want %q", i, n.ID, wantID[i])
|
|
}
|
|
if n.FilePath != wantPath {
|
|
t.Fatalf("node %d FilePath = %q, want %q", i, n.FilePath, wantPath)
|
|
}
|
|
if string(n.Kind) != "function" {
|
|
t.Fatalf("node %d Kind = %q, want %q", i, n.Kind, "function")
|
|
}
|
|
if n.Language != "go" {
|
|
t.Fatalf("node %d Language = %q, want %q", i, n.Language, "go")
|
|
}
|
|
if n.RepoPrefix != "myrepo" {
|
|
t.Fatalf("node %d RepoPrefix = %q, want %q", i, n.RepoPrefix, "myrepo")
|
|
}
|
|
}
|
|
if edges[0].From != "myrepo/a.go::A" || edges[0].To != "myrepo/a.go::B" {
|
|
t.Fatalf("edge endpoints = %q -> %q, want myrepo/a.go::A -> myrepo/a.go::B", edges[0].From, edges[0].To)
|
|
}
|
|
if edges[0].FilePath != wantPath {
|
|
t.Fatalf("edge FilePath = %q, want %q", edges[0].FilePath, wantPath)
|
|
}
|
|
|
|
// Identity: equal values now share exactly one backing array.
|
|
if backingPtr(nodes[0].FilePath) != backingPtr(nodes[1].FilePath) {
|
|
t.Fatal("FilePath not interned: nodes hold distinct backing arrays after the pass")
|
|
}
|
|
if backingPtr(string(nodes[0].Kind)) != backingPtr(string(nodes[1].Kind)) {
|
|
t.Fatal("Kind not interned: nodes hold distinct backing arrays after the pass")
|
|
}
|
|
if backingPtr(nodes[0].Language) != backingPtr(nodes[1].Language) {
|
|
t.Fatal("Language not interned: nodes hold distinct backing arrays after the pass")
|
|
}
|
|
if backingPtr(nodes[0].RepoPrefix) != backingPtr(nodes[1].RepoPrefix) {
|
|
t.Fatal("RepoPrefix not shared: nodes hold distinct backing arrays after the pass")
|
|
}
|
|
// The edge's file path shares storage with the node's — the per-file
|
|
// cache hands both the same interned instance.
|
|
if backingPtr(edges[0].FilePath) != backingPtr(nodes[0].FilePath) {
|
|
t.Fatal("edge FilePath not interned to the same backing array as the node FilePath")
|
|
}
|
|
}
|
|
|
|
// TestApplyRepoPrefix_PerFileCacheMatchesPerNode pins the invariant that
|
|
// the per-file cache produces byte-identical results to the per-node
|
|
// concatenation it replaces, for both nodes and edges.
|
|
func TestApplyRepoPrefix_PerFileCacheMatchesPerNode(t *testing.T) {
|
|
cachedNodes, cachedEdges := buildFreshNodes(3, 4)
|
|
baseNodes, baseEdges := buildFreshNodes(3, 4)
|
|
|
|
idx := &Indexer{}
|
|
idx.SetRepoPrefix("repo")
|
|
idx.applyRepoPrefix(cachedNodes, cachedEdges)
|
|
applyRepoPrefixPerNodeBaseline("repo", baseNodes, baseEdges)
|
|
|
|
if len(cachedNodes) != len(baseNodes) {
|
|
t.Fatalf("node count drift: cached=%d base=%d", len(cachedNodes), len(baseNodes))
|
|
}
|
|
for i := range cachedNodes {
|
|
if cachedNodes[i].FilePath != baseNodes[i].FilePath {
|
|
t.Fatalf("node %d FilePath: cached=%q base=%q", i, cachedNodes[i].FilePath, baseNodes[i].FilePath)
|
|
}
|
|
if cachedNodes[i].ID != baseNodes[i].ID {
|
|
t.Fatalf("node %d ID: cached=%q base=%q", i, cachedNodes[i].ID, baseNodes[i].ID)
|
|
}
|
|
if cachedNodes[i].Kind != baseNodes[i].Kind {
|
|
t.Fatalf("node %d Kind: cached=%q base=%q", i, cachedNodes[i].Kind, baseNodes[i].Kind)
|
|
}
|
|
}
|
|
for i := range cachedEdges {
|
|
if cachedEdges[i].FilePath != baseEdges[i].FilePath {
|
|
t.Fatalf("edge %d FilePath: cached=%q base=%q", i, cachedEdges[i].FilePath, baseEdges[i].FilePath)
|
|
}
|
|
if cachedEdges[i].From != baseEdges[i].From || cachedEdges[i].To != baseEdges[i].To {
|
|
t.Fatalf("edge %d endpoints differ", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkApplyRepoPrefix_PerFileCache measures the production stamping
|
|
// pass; the file path is interned once per file via the cache.
|
|
func BenchmarkApplyRepoPrefix_PerFileCache(b *testing.B) {
|
|
idx := &Indexer{}
|
|
idx.SetRepoPrefix("benchrepo")
|
|
benchPrefixPass(b, func(nodes []*graph.Node, edges []*graph.Edge) {
|
|
idx.applyRepoPrefix(nodes, edges)
|
|
})
|
|
}
|
|
|
|
// BenchmarkApplyRepoPrefix_PerNodeConcat measures the pre-cache baseline
|
|
// that concatenates prefix+path for every node and edge.
|
|
func BenchmarkApplyRepoPrefix_PerNodeConcat(b *testing.B) {
|
|
benchPrefixPass(b, func(nodes []*graph.Node, edges []*graph.Edge) {
|
|
applyRepoPrefixPerNodeBaseline("benchrepo", nodes, edges)
|
|
})
|
|
}
|
|
|
|
func benchPrefixPass(b *testing.B, apply func([]*graph.Node, []*graph.Edge)) {
|
|
const files, perFile = 40, 500
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
b.StopTimer()
|
|
nodes, edges := buildFreshNodes(files, perFile)
|
|
b.StartTimer()
|
|
apply(nodes, edges)
|
|
}
|
|
}
|
|
|
|
// BenchmarkNodeFields_LiveHeap_Interned and _NotInterned report the
|
|
// resident heap of a 100k-node corpus with vs without interning the
|
|
// repetitive fields, so the live-set reduction is directly observable
|
|
// as the live-heap-bytes custom metric.
|
|
func BenchmarkNodeFields_LiveHeap_Interned(b *testing.B) { benchLiveHeap(b, true) }
|
|
func BenchmarkNodeFields_LiveHeap_NotInterned(b *testing.B) { benchLiveHeap(b, false) }
|
|
|
|
func benchLiveHeap(b *testing.B, interned bool) {
|
|
const files, perFile = 50, 2000 // 100k nodes / 100k edges
|
|
var lastHeap float64
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
nodes, edges := buildFreshNodes(files, perFile)
|
|
if interned {
|
|
internRepetitiveFields(nodes, edges)
|
|
}
|
|
runtime.GC()
|
|
var ms runtime.MemStats
|
|
runtime.ReadMemStats(&ms)
|
|
lastHeap = float64(ms.HeapAlloc)
|
|
runtime.KeepAlive(nodes)
|
|
runtime.KeepAlive(edges)
|
|
}
|
|
b.ReportMetric(lastHeap, "live-heap-bytes")
|
|
}
|