Skip to content

Commit 343bf99

Browse files
committed
add benchmark for objectpath
1 parent 0114d45 commit 343bf99

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package objectpath_test
6+
7+
import (
8+
"go/ast"
9+
"go/build"
10+
"go/importer"
11+
"go/parser"
12+
"go/token"
13+
"go/types"
14+
"path/filepath"
15+
"testing"
16+
17+
"golang.org/x/tools/go/types/objectpath"
18+
)
19+
20+
// testData holds pre-loaded type information for benchmarks.
21+
var testData struct {
22+
pkg *types.Package
23+
objects []types.Object
24+
methods []*types.Func
25+
fields []*types.Var
26+
paths []objectpath.Path
27+
}
28+
29+
func init() {
30+
// Load net/http for realistic benchmarks - it has interfaces,
31+
// structs with many fields, and methods.
32+
pkg, err := build.Default.Import("net/http", "", 0)
33+
if err != nil {
34+
panic("failed to import net/http: " + err.Error())
35+
}
36+
37+
fset := token.NewFileSet()
38+
var files []*ast.File
39+
for _, filename := range pkg.GoFiles {
40+
f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, filename), nil, 0)
41+
if err != nil {
42+
panic("failed to parse: " + err.Error())
43+
}
44+
files = append(files, f)
45+
}
46+
47+
conf := types.Config{Importer: importer.Default()}
48+
tpkg, err := conf.Check("net/http", fset, files, nil)
49+
if err != nil {
50+
panic("failed to type-check: " + err.Error())
51+
}
52+
53+
testData.pkg = tpkg
54+
scope := tpkg.Scope()
55+
56+
// Collect diverse objects for comprehensive benchmarking
57+
for _, name := range scope.Names() {
58+
obj := scope.Lookup(name)
59+
testData.objects = append(testData.objects, obj)
60+
61+
// Collect methods from named types
62+
if named, ok := obj.Type().(*types.Named); ok {
63+
for i := 0; i < named.NumMethods(); i++ {
64+
m := named.Method(i)
65+
testData.methods = append(testData.methods, m)
66+
testData.objects = append(testData.objects, m)
67+
}
68+
69+
// Collect fields from struct types
70+
if st, ok := named.Underlying().(*types.Struct); ok {
71+
for i := 0; i < st.NumFields(); i++ {
72+
f := st.Field(i)
73+
testData.fields = append(testData.fields, f)
74+
testData.objects = append(testData.objects, f)
75+
}
76+
}
77+
}
78+
}
79+
80+
// Pre-encode paths for decode benchmarks
81+
enc := new(objectpath.Encoder)
82+
for _, obj := range testData.objects {
83+
if path, err := enc.For(obj); err == nil {
84+
testData.paths = append(testData.paths, path)
85+
}
86+
}
87+
}
88+
89+
// BenchmarkEncoderFor measures the cost of encoding object paths.
90+
func BenchmarkEncoderFor(b *testing.B) {
91+
if len(testData.objects) == 0 {
92+
b.Skip("no test objects available")
93+
}
94+
for b.Loop() {
95+
enc := new(objectpath.Encoder)
96+
for _, obj := range testData.objects {
97+
_, _ = enc.For(obj)
98+
}
99+
}
100+
}
101+
102+
// BenchmarkEncoderFor_SingleEncoder measures encoding with encoder reuse.
103+
func BenchmarkEncoderFor_SingleEncoder(b *testing.B) {
104+
if len(testData.objects) == 0 {
105+
b.Skip("no test objects available")
106+
}
107+
enc := new(objectpath.Encoder)
108+
for b.Loop() {
109+
for _, obj := range testData.objects {
110+
_, _ = enc.For(obj)
111+
}
112+
}
113+
}
114+
115+
// BenchmarkEncoderFor_Methods focuses on method path encoding.
116+
func BenchmarkEncoderFor_Methods(b *testing.B) {
117+
if len(testData.methods) == 0 {
118+
b.Skip("no methods available")
119+
}
120+
for b.Loop() {
121+
enc := new(objectpath.Encoder)
122+
for _, m := range testData.methods {
123+
_, _ = enc.For(m)
124+
}
125+
}
126+
}
127+
128+
// BenchmarkEncoderFor_Fields focuses on struct field path encoding.
129+
func BenchmarkEncoderFor_Fields(b *testing.B) {
130+
if len(testData.fields) == 0 {
131+
b.Skip("no fields available")
132+
}
133+
for b.Loop() {
134+
enc := new(objectpath.Encoder)
135+
for _, f := range testData.fields {
136+
_, _ = enc.For(f)
137+
}
138+
}
139+
}
140+
141+
// BenchmarkObject measures decoding paths back to objects.
142+
func BenchmarkObject(b *testing.B) {
143+
if len(testData.paths) == 0 {
144+
b.Skip("no paths available")
145+
}
146+
for b.Loop() {
147+
for _, path := range testData.paths {
148+
_, _ = objectpath.Object(testData.pkg, path)
149+
}
150+
}
151+
}
152+
153+
// BenchmarkRoundTrip measures encode + decode cycles.
154+
func BenchmarkRoundTrip(b *testing.B) {
155+
if len(testData.objects) == 0 {
156+
b.Skip("no test objects available")
157+
}
158+
for b.Loop() {
159+
enc := new(objectpath.Encoder)
160+
for _, obj := range testData.objects {
161+
path, err := enc.For(obj)
162+
if err != nil {
163+
continue
164+
}
165+
_, _ = objectpath.Object(testData.pkg, path)
166+
}
167+
}
168+
}
169+
170+
// BenchmarkEncoderFor_Repeated measures many sequential encoding calls.
171+
func BenchmarkEncoderFor_Repeated(b *testing.B) {
172+
if len(testData.objects) == 0 {
173+
b.Skip("no test objects available")
174+
}
175+
// Use a subset to get more iterations
176+
objects := testData.objects
177+
if len(objects) > 100 {
178+
objects = objects[:100]
179+
}
180+
for b.Loop() {
181+
enc := new(objectpath.Encoder)
182+
for j := 0; j < 10; j++ {
183+
for _, obj := range objects {
184+
_, _ = enc.For(obj)
185+
}
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)