Skip to content

Commit 2ed8e5b

Browse files
committed
perf(callgrind): cut redundant debug-info address lookups on the dump path
Writing the profile at exit queries file/line information for every instruction address of every dumped basic block (~141k addresses for `python3 testdata/test.py`). Almost all of that work is redundant: consecutive addresses belong to the same source line and the same source file. - Add a one-entry range cache to search_all_loctabs(). It answers a query that lands in the last hit loctab entry (or the next one) with a couple of compares instead of walking every DebugInfo and binary searching its loctab. It is only reused within the epoch it was found in and is dropped by advance_current_DiEpoch(), so a stale DebugInfo can never be read. - Return early from VG_(get_inline_fnname) when --read-inline-info=no. No inltab is built in that case, so the answer is always "no inlined function", but the loctab search was still performed for every dumped address. This mirrors the guard already present in VG_(new_IIPC). - Memoize the last file_node in get_debug_pos(). The existing debug cache is keyed by instruction address so it never hits while walking the distinct addresses of a basic block; every miss called CLG_(get_file_node), which rebuilds and rehashes the absolute source path. The memo compares the interned (dir, file) pointers and is reset by init_debug_cache() at the start of each dump. Callgrind output is byte-for-byte identical to the previous build.
1 parent ae6bf15 commit 2ed8e5b

2 files changed

Lines changed: 97 additions & 1 deletion

File tree

callgrind/dump.c

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,25 @@ static int debug_cache_line[DEBUG_CACHE_SIZE];
361361
static Bool debug_cache_info[DEBUG_CACHE_SIZE];
362362
static const HChar* debug_cache_inlfn[DEBUG_CACHE_SIZE];
363363

364+
/* One-entry memo for the last file_node looked up by get_debug_pos().
365+
*
366+
* The cache above is keyed by instruction address, so it never hits while a
367+
* dump walks the (all distinct) addresses of a basic block. Every one of
368+
* those misses ends in CLG_(get_file_node), which rebuilds the absolute
369+
* source path on the stack and hashes it character by character - even
370+
* though consecutive addresses nearly always belong to the same source file.
371+
*
372+
* The (dir, file) strings come from the debug info and are interned per
373+
* DebugInfo, so the same source file always yields the same pointers and
374+
* comparing pointers is enough. The memo is only used within one dump: no
375+
* guest code runs while dumping, so no debug info can be loaded or discarded
376+
* in between, and init_debug_cache() resets it at the start of every dump.
377+
*/
378+
static obj_node* debug_last_obj;
379+
static const HChar* debug_last_dir;
380+
static const HChar* debug_last_file;
381+
static file_node* debug_last_node;
382+
364383
static __inline__
365384
void init_debug_cache(void)
366385
{
@@ -372,6 +391,10 @@ void init_debug_cache(void)
372391
debug_cache_info[i] = 0;
373392
debug_cache_inlfn[i] = 0;
374393
}
394+
debug_last_obj = 0;
395+
debug_last_dir = 0;
396+
debug_last_file = 0;
397+
debug_last_node = 0;
375398
}
376399

377400
static /* __inline__ */
@@ -397,7 +420,21 @@ Bool get_debug_pos(BBCC* bbcc, Addr addr, AddrPos* p)
397420
file = "???";
398421
p->line = 0;
399422
}
400-
p->file = CLG_(get_file_node)(bbcc->bb->obj, dir, file);
423+
/* Same source file as the previous lookup? Then reuse its node
424+
* instead of rebuilding and rehashing the absolute path. */
425+
if ((bbcc->bb->obj == debug_last_obj) &&
426+
(dir == debug_last_dir) &&
427+
(file == debug_last_file)) {
428+
p->file = debug_last_node;
429+
}
430+
else {
431+
p->file = CLG_(get_file_node)(bbcc->bb->obj, dir, file);
432+
433+
debug_last_obj = bbcc->bb->obj;
434+
debug_last_dir = dir;
435+
debug_last_file = file;
436+
debug_last_node = p->file;
437+
}
401438

402439
debug_cache_info[cachepos] = found_file_line;
403440
debug_cache_addr[cachepos] = addr;

coregrind/m_debuginfo/debuginfo.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,13 @@ inline DiEpoch VG_(current_DiEpoch) ( void ) {
137137
DiEpoch dep; dep.n = current_epoch; return dep;
138138
}
139139

140+
/* Forward */
141+
static void invalidate_loctab_cache ( void );
142+
140143
static void advance_current_DiEpoch ( const HChar* msg ) {
141144
current_epoch++;
145+
/* Anything cached about the previous epoch's DebugInfos is now stale. */
146+
invalidate_loctab_cache();
142147
if (DEBUG_EPOCHS)
143148
VG_(printf)("Advancing current epoch to %u due to %s\n",
144149
current_epoch, msg);
@@ -2138,6 +2143,35 @@ static void search_all_symtabs ( DiEpoch ep, Addr ptr,
21382143
}
21392144

21402145

2146+
/* One-entry cache for search_all_loctabs().
2147+
*
2148+
* Callers query addresses with strong locality: dumping a profile asks for
2149+
* every instruction of a basic block in turn, and one source line usually
2150+
* covers several instructions, so consecutive queries land in the same
2151+
* loctab entry or in the one right after it, of the same DebugInfo.
2152+
* Remembering the last hit turns those queries into a couple of compares
2153+
* instead of a walk over all DebugInfos plus a binary search of the loctab.
2154+
*
2155+
* The entry is only reused for the epoch it was found in, and it is dropped
2156+
* whenever the epoch advances (see advance_current_DiEpoch), i.e. whenever
2157+
* debug info is loaded or discarded, so a stale DebugInfo is never read. */
2158+
static DiEpoch loctab_cache_ep; /* zeroed => invalid epoch */
2159+
static DebugInfo* loctab_cache_di = NULL;
2160+
static Word loctab_cache_locno = 0;
2161+
2162+
static void invalidate_loctab_cache ( void )
2163+
{
2164+
loctab_cache_di = NULL;
2165+
}
2166+
2167+
/* Does loctab entry `lno` of `di` cover `ptr`? */
2168+
static inline
2169+
Bool loctab_entry_covers ( const DebugInfo* di, Word lno, Addr ptr )
2170+
{
2171+
Addr lo = di->loctab[lno].addr;
2172+
return ptr >= lo && ptr - lo < (Addr)di->loctab[lno].size;
2173+
}
2174+
21412175
/* Search all loctabs that we know about to locate ptr at epoch ep. If
21422176
*found, set pdi to the relevant DebugInfo, and *locno to the loctab entry
21432177
*number within that. If not found, *pdi is set to NULL. */
@@ -2146,6 +2180,25 @@ static void search_all_loctabs ( DiEpoch ep, Addr ptr,
21462180
{
21472181
Word lno;
21482182
DebugInfo* di;
2183+
2184+
/* Same entry as last time, or the one following it? */
2185+
if (LIKELY(loctab_cache_di != NULL && eq_DiEpoch(ep, loctab_cache_ep))) {
2186+
di = loctab_cache_di;
2187+
lno = loctab_cache_locno;
2188+
if (LIKELY(loctab_entry_covers(di, lno, ptr))) {
2189+
*locno = lno;
2190+
*pdi = di;
2191+
return;
2192+
}
2193+
lno++;
2194+
if (lno < di->loctab_used && loctab_entry_covers(di, lno, ptr)) {
2195+
loctab_cache_locno = lno;
2196+
*locno = lno;
2197+
*pdi = di;
2198+
return;
2199+
}
2200+
}
2201+
21492202
for (di = debugInfo_list; di != NULL; di = di->next) {
21502203
if (!is_DI_valid_for_epoch(di, ep))
21512204
continue;
@@ -2155,6 +2208,9 @@ static void search_all_loctabs ( DiEpoch ep, Addr ptr,
21552208
&& ptr < di->text_avma + di->text_size) {
21562209
lno = ML_(search_one_loctab) ( di, ptr );
21572210
if (lno == -1) goto not_found;
2211+
loctab_cache_ep = ep;
2212+
loctab_cache_di = di;
2213+
loctab_cache_locno = lno;
21582214
*locno = lno;
21592215
*pdi = di;
21602216
return;
@@ -2384,6 +2440,9 @@ Bool VG_(get_inline_fnname) ( DiEpoch ep, Addr a, const HChar** inl_fnname )
23842440
DebugInfo* si;
23852441
Word locno;
23862442

2443+
if (!VG_(clo_read_inline_info))
2444+
return False; // No inltab was built, so no way to find inlined calls.
2445+
23872446
/* Find the DebugInfo for this address */
23882447
search_all_loctabs(ep, a, &si, &locno);
23892448
if (si == NULL || si->inltab == NULL)

0 commit comments

Comments
 (0)