From effbe09becb2060f310466018e72f1944479b7bc Mon Sep 17 00:00:00 2001 From: CodSpeed Bot Date: Wed, 2 Sep 2026 06:21:23 +0000 Subject: [PATCH] perf(callgrind): memoize object/file name lookups CLG_(get_obj_node)() and CLG_(get_file_node)() are called for every newly translated basic block and for every address resolved while dumping. Both are dominated by string work rather than by the lookup itself: get_file_node rebuilds the full path on each call and hashes it with str_hash(), which does a modulo per character, before walking a hash chain with VG_(strcmp); get_obj_node hashes the object's full path every time. Consecutive basic blocks -- and consecutive cost lines of a dump -- almost always belong to the same source file of the same object, so this work recomputes the same answer over and over. Add a one-entry memo cache to each lookup, keyed on pointer identity of the arguments: (DiEpoch, DebugInfo*) for objects and (DiEpoch, obj_node*, dir, file) for files. The names are owned by the debuginfo reader and are stable for a given DebugInfo, so identical pointers imply identical strings. On a hit, the path construction, the hashing and the chain walk are skipped entirely. The debuginfo epoch is part of the key: it changes whenever debuginfo is discarded, which is the only way the memoized pointers could later be reused for different strings, so the cache stays sound across dlclose/unload. --- callgrind/fn.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/callgrind/fn.c b/callgrind/fn.c index f1d96aca1..8a59c3462 100644 --- a/callgrind/fn.c +++ b/callgrind/fn.c @@ -356,12 +356,49 @@ obj_node* new_obj_node(DebugInfo* di, obj_node* next) return obj; } +/* One-entry memo caches for the object and file name lookups below. + * + * Both lookups are done for every newly translated basic block (via + * get_fn_node_inseg) and for every address resolved while dumping, and both + * are dominated by string work: hashing (and, for files, first building) a + * full path, then walking a hash chain comparing strings. The arguments, + * however, hardly ever change between consecutive calls: successive basic + * blocks -- and successive cost lines of a dump -- almost always belong to + * the same source file of the same object. + * + * The names are owned by the debuginfo reader and are stable for a given + * DebugInfo, so identical pointers imply identical strings. Comparing the + * pointers therefore lets us return the previous result directly. The + * debuginfo epoch is part of the key: it changes whenever debuginfo is + * discarded, which is the only way the memoized pointers could later be + * reused for different strings. + */ +static struct { + DiEpoch ep; + DebugInfo* di; + obj_node* node; +} last_obj_lookup; + +static struct { + DiEpoch ep; + obj_node* obj; + const HChar* dir; + const HChar* file; + file_node* node; +} last_file_lookup; + obj_node* CLG_(get_obj_node)(DebugInfo* di) { obj_node* curr_obj_node; UInt objname_hash; const HChar* obj_name; - + DiEpoch ep = VG_(current_DiEpoch)(); + + if (last_obj_lookup.node && + last_obj_lookup.di == di && + last_obj_lookup.ep.n == ep.n) + return last_obj_lookup.node; + obj_name = di ? VG_(DebugInfo_get_filename)(di) : anonymous_obj; /* lookup in obj hash */ @@ -376,6 +413,10 @@ obj_node* CLG_(get_obj_node)(DebugInfo* di) new_obj_node(di, obj_table[objname_hash]); } + last_obj_lookup.ep = ep; + last_obj_lookup.di = di; + last_obj_lookup.node = curr_obj_node; + return curr_obj_node; } @@ -404,6 +445,14 @@ file_node* CLG_(get_file_node)(obj_node* curr_obj_node, { file_node* curr_file_node; UInt filename_hash; + DiEpoch ep = VG_(current_DiEpoch)(); + + if (last_file_lookup.node && + last_file_lookup.obj == curr_obj_node && + last_file_lookup.dir == dir && + last_file_lookup.file == file && + last_file_lookup.ep.n == ep.n) + return last_file_lookup.node; /* Build up an absolute pathname, if there is a directory available */ HChar filename[VG_(strlen)(dir) + 1 + VG_(strlen)(file) + 1]; @@ -426,6 +475,12 @@ file_node* CLG_(get_file_node)(obj_node* curr_obj_node, curr_obj_node->files[filename_hash]); } + last_file_lookup.ep = ep; + last_file_lookup.obj = curr_obj_node; + last_file_lookup.dir = dir; + last_file_lookup.file = file; + last_file_lookup.node = curr_file_node; + return curr_file_node; }