Summary
The objectpath.Encoder.For() function allocates heavily, causing significant GC pressure in tools that call it frequently (e.g., static analyzers like nilaway that compute object paths for cross-package inference).
Problem
Profiling nogo with ~40 analyzers on a large monorepo (~9000 packages) revealed that objectpath functions consumed 504MB (60%) of allocations on hot packages:
271MB (32%) objectpath.(*finder).find
101MB (12%) strconv.AppendInt
68MB (8%) objectpath.(*Encoder).For
65MB (8%) objectpath.appendOpArg
The main allocation sources are:
make([]byte, 0, 48) in Encoder.For() - allocated per call
strconv.AppendInt in appendOpArg - allocates for each method/field index
&finder{} in find() - new struct per search with lazily-allocated maps
Proposed Solution
- Pool path buffers using
sync.Pool instead of allocating per call
- Pre-compute small integers (0-99) to avoid
strconv.AppendInt for common indices
- Pool finder structs with pre-allocated maps, using
clear() to reset between uses
Results
On a package with heavy objectpath usage:
| Metric |
Before |
After |
Improvement |
| Allocations |
929MB |
445MB |
52% reduction |
| GC cycles |
21 |
14 |
33% reduction |
| GC pause |
1.77ms |
0.81ms |
54% reduction |
After the change, objectpath functions no longer appear in the top allocators.
Patch
I have a working patch ready to submit as a PR. The changes are backward-compatible and do not change any public APIs.
/cc @adonovan @findleyr
Summary
The
objectpath.Encoder.For()function allocates heavily, causing significant GC pressure in tools that call it frequently (e.g., static analyzers like nilaway that compute object paths for cross-package inference).Problem
Profiling nogo with ~40 analyzers on a large monorepo (~9000 packages) revealed that
objectpathfunctions consumed 504MB (60%) of allocations on hot packages:271MB (32%) objectpath.(*finder).find
101MB (12%) strconv.AppendInt
68MB (8%) objectpath.(*Encoder).For
65MB (8%) objectpath.appendOpArg
The main allocation sources are:
make([]byte, 0, 48)inEncoder.For()- allocated per callstrconv.AppendIntinappendOpArg- allocates for each method/field index&finder{}infind()- new struct per search with lazily-allocated mapsProposed Solution
sync.Poolinstead of allocating per callstrconv.AppendIntfor common indicesclear()to reset between usesResults
On a package with heavy objectpath usage:
After the change, objectpath functions no longer appear in the top allocators.
Patch
I have a working patch ready to submit as a PR. The changes are backward-compatible and do not change any public APIs.
/cc @adonovan @findleyr