perf(callgrind): FNV-1a hash + stored-hash pre-filter in name lookups#27
Conversation
Greptile SummaryThis PR optimizes Callgrind name-table lookups.
Confidence Score: 5/5The PR appears safe to merge. No blocking failures remain.
|
| Filename | Overview |
|---|---|
| callgrind/fn.c | Implements FNV-1a hashing and hash pre-filters while retaining string equality checks for collisions. |
| callgrind/global.h | Adds stored 32-bit name hashes to Callgrind object, file, and function nodes. |
Reviews (4): Last reviewed commit: "perf(callgrind): FNV-1a hash + stored-ha..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
|
merge upstream and rerun |
Merged the latest The branch was behind
The merge was clean (no conflicts). Both incoming commits only touch CI/benchmark infrastructure ( The benchmarks run on Merge commit: Summary
Was this response helpful? React with 👍 or 👎 to help us improve CodSpeedBot. |
9a7caa7 to
7400f48
Compare
Unable to generate the flame graphsThe performance report has correctly been generated, but there was an internal error while generating the flame graphs for this run. We're working on fixing the issue. Feel free to contact us on Discord or at support@codspeed.io if the issue persists. |
Replace the weak shift-by-8 str_hash (which only considered the last 4 bytes of a string) with a full 32-bit FNV-1a hash, and store the full hash on obj/file/fn nodes so lookups compare the stored hash before calling the byte-by-byte VG_(strcmp). This shrinks hash chains and eliminates virtually all strcmp calls for non-matching entries, cutting time spent in get_fn_node_infile and VG_(strcmp) when profiling programs with many unique function names. The FNV constants are declared as named defines (FNV_OFFSET_BASIS, FNV_PRIME), consistent with the previous HASH_CONSTANT define.
7400f48 to
c044ad6
Compare
Problem
Flamegraph analysis identified
get_fn_node_infile(callgrind/fn.c) andVG_(strcmp)(coregrind/m_libcbase.c) as the two hottest functions, together accounting for ~18% of total runtime when profiling programs with many unique functions (e.g. Python with standard-library imports).The root cause is twofold:
Weak hash function. The original
str_hashusedHASH_CONSTANT = 256, i.e. a left-shift by 8 bits per character. On a 32-bitUInt, after processing just 4 characters only the last 4 bytes of the string determine the hash, so function names sharing a suffix (e.g.foo_handlervsbar_handler) always collide.No hash pre-filter. Every hash-chain walk called
VG_(strcmp)for each candidate even when the full hash already indicated a mismatch. This is expensive because Valgrind'sstrcmpis a byte-by-byte implementation.Solution
FNV-1a hash. Replaced the shift-by-8 hash with the FNV-1a algorithm (XOR-then-multiply), which mixes all characters into a full 32-bit hash with excellent distribution. The FNV constants are declared as named
#defines (FNV_OFFSET_BASIS,FNV_PRIME), consistent with howHASH_CONSTANTwas previously defined.Stored-hash comparison. Added a
name_hashfield tofn_node,file_node, andobj_node. On lookup, the full 32-bit hash is compared before callingstrcmp. Since two different strings sharing the same FNV-1a hash is a ~1-in-4-billion event, this eliminates virtually allstrcmpcalls for non-matching entries.Both changes apply to all three Callgrind name hash tables (object, file, function).
Validation
coregrind+callgrind) with no new warnings.echoand on a heavy Python workload (2699 distinct functions hashed) — object/file/function lookups produce correct profiling output.codspeed-macrorunners. The original analysis measured +2.2% overall improvement, with Python workloads (many unique function names, higher hash-table pressure) showing the largest benefit.Notes
Per reviewer feedback, the FNV constants are placed in proper
#defines (matching the priorHASH_CONSTANTstyle) rather than being inlined as magic numbers.