perf(perf-map-agent): index the git root once instead of walking it per source lookup - #17
not-matthias wants to merge 1 commit into
Conversation
…er source lookup Every distinct class-relative source path that is not on disk (JDK and dependency classes) triggered a full recursive walk of the git root on the JVM service thread, concurrently with the running benchmark. With ~130 distinct misses on a ~6500-file tree this was ~130k getdents64 and ~450k stat calls, and ~1s of service-thread CPU during a ~3.5s run. The git root is now walked exactly once into a basename-keyed hash index; each lookup is a single bucket scan for the suffix match. The walk uses d_type to avoid a stat per entry, falling back to stat only for symlinks and DT_UNKNOWN. Same measurement after the change: 968 getdents64, 7 extra stats, 0.03s service-thread CPU.
|
| e->next = source_index[bucket]; | ||
| source_index[bucket] = e; |
There was a problem hiding this comment.
Duplicate paths resolve differently
If a multi-module checkout has the same package-relative file in two source roots, such as com/example/Foo.java, prepending each entry reverses their traversal order. index_find therefore returns the last discovered match, while the previous recursive search returned the first. The perf map can consequently associate a compiled method with a different source file.
| e->next = source_index[bucket]; | |
| source_index[bucket] = e; | |
| e->next = NULL; | |
| index_entry_t **tail = &source_index[bucket]; | |
| while (*tail) { | |
| tail = &(*tail)->next; | |
| } | |
| *tail = e; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: jmh-fork/jmh-core/native-perf-map-agent/src/main/c/perf_map_agent.c
Line: 175-176
Comment:
**Duplicate paths resolve differently**
If a multi-module checkout has the same package-relative file in two source roots, such as `com/example/Foo.java`, prepending each entry reverses their traversal order. `index_find` therefore returns the last discovered match, while the previous recursive search returned the first. The perf map can consequently associate a compiled method with a different source file.
```suggestion
e->next = NULL;
index_entry_t **tail = &source_index[bucket];
while (*tail) {
tail = &(*tail)->next;
}
*tail = e;
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Hooray! CodSpeed harness just leveled up!The base and head of this comparison were measured with different runner settings, so their benchmark values are not directly comparable. What changed between base and head:
Re-run the base with the same settings to get a valid performance comparison. Comparing |
Problem
Walltime flamegraphs for JVM benchmarks spend a large share of samples in:
These are not leftover warmup frames.
resolve_source_filein the perf-map JVMTI agent walked the whole git root recursively for every distinct class-relative source path. The result was cached per path, but a miss — every JDK and dependency class, which can never be on disk under the repo — paid a full tree walk first.CompiledMethodLoadkeeps firing during the measured phase (tier transitions, late compiles, deopt/recompile), and it is delivered on the JVM service thread concurrently with the benchmark. Walltime sampling captures all threads, so those walks land in the flamegraph.Measured with a JIT-heavy workload (~3.5 s) in a ~6500-file checkout,
strace -f -c:getdents64newfstatatopenat132 distinct source paths were requested, all of them misses in that run — i.e. 132 full tree walks.
Change
Walk the git root exactly once, on the first lookup, into a basename-keyed hash index (4096 chained buckets, FNV-1a). Each resolution is then one bucket scan for a path ending in
/<relative_path>..-prefixed,build,target,node_modules), same perf-map output format.d_typeinstead of astat()per entry, falling back tostat()only for symlinks andDT_UNKNOWN(so symlinks are still followed as before).Agent_OnUnload, alongside the existing per-path cache.Verification
./gradlew :jmh-fork:jmh-core:native-perf-map-agent:linkRelease— clean, no warnings../gradlew :jmh-fork:jmh-core:test --tests "io.codspeed.*"— 22 tests, 0 skipped, 0 failures.PerfMapAgentTestruns the real agent in a child JVM and asserts both the resolved<abs path>.java::<class>.<method>form and the no-source fallback form.libperf_map_agent.so; perf map line count and the set of distinct source paths are unchanged.Closes COD-3562.