Skip to content

Commit 7400f48

Browse files
codspeedbotart049
authored andcommitted
perf(callgrind): FNV-1a hash + stored-hash pre-filter in name lookups
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.
1 parent 20aa609 commit 7400f48

2 files changed

Lines changed: 48 additions & 23 deletions

File tree

callgrind/fn.c

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -307,21 +307,31 @@ void CLG_(init_obj_table)(void)
307307
obj_table[i] = 0;
308308
}
309309

310-
#define HASH_CONSTANT 256
311-
312-
static UInt str_hash(const HChar *s, UInt table_size)
310+
#define FNV_OFFSET_BASIS 2166136261u
311+
#define FNV_PRIME 16777619u
312+
313+
/* Compute a full 32-bit FNV-1a hash of a string.
314+
* FNV-1a provides excellent distribution, much better than the
315+
* original shift-by-8 hash which only considered the last 4 bytes
316+
* of each string. The improved distribution reduces chain lengths
317+
* in the hash tables and eliminates most strcmp calls when combined
318+
* with the stored-hash comparison in lookup functions.
319+
*/
320+
static UInt str_hash_full(const HChar *s)
313321
{
314-
int hash_value = 0;
315-
for ( ; *s; s++)
316-
hash_value = (HASH_CONSTANT * hash_value + *s) % table_size;
317-
return hash_value;
322+
UInt hash = FNV_OFFSET_BASIS;
323+
for ( ; *s; s++) {
324+
hash ^= (UChar)*s;
325+
hash *= FNV_PRIME;
326+
}
327+
return hash;
318328
}
319329

320-
321330
static const HChar* anonymous_obj = "???";
322331

323332
static __inline__
324-
obj_node* new_obj_node(DebugInfo* di, obj_node* next)
333+
obj_node* new_obj_node(DebugInfo* di, UInt objname_full_hash,
334+
obj_node* next)
325335
{
326336
Int i;
327337
obj_node* obj;
@@ -330,6 +340,7 @@ obj_node* new_obj_node(DebugInfo* di, obj_node* next)
330340
obj->name = di ? VG_(strdup)( "cl.fn.non.2",
331341
VG_(DebugInfo_get_filename)(di) )
332342
: anonymous_obj;
343+
obj->name_hash = objname_full_hash;
333344
for (i = 0; i < N_FILE_ENTRIES; i++) {
334345
obj->files[i] = NULL;
335346
}
@@ -359,35 +370,39 @@ obj_node* new_obj_node(DebugInfo* di, obj_node* next)
359370
obj_node* CLG_(get_obj_node)(DebugInfo* di)
360371
{
361372
obj_node* curr_obj_node;
373+
UInt objname_full_hash;
362374
UInt objname_hash;
363375
const HChar* obj_name;
364376

365377
obj_name = di ? VG_(DebugInfo_get_filename)(di) : anonymous_obj;
366378

367379
/* lookup in obj hash */
368-
objname_hash = str_hash(obj_name, N_OBJ_ENTRIES);
380+
objname_full_hash = str_hash_full(obj_name);
381+
objname_hash = objname_full_hash % N_OBJ_ENTRIES;
369382
curr_obj_node = obj_table[objname_hash];
370-
while (NULL != curr_obj_node &&
371-
VG_(strcmp)(obj_name, curr_obj_node->name) != 0) {
383+
while (NULL != curr_obj_node &&
384+
(curr_obj_node->name_hash != objname_full_hash ||
385+
VG_(strcmp)(obj_name, curr_obj_node->name) != 0)) {
372386
curr_obj_node = curr_obj_node->next;
373387
}
374388
if (NULL == curr_obj_node) {
375389
obj_table[objname_hash] = curr_obj_node =
376-
new_obj_node(di, obj_table[objname_hash]);
390+
new_obj_node(di, objname_full_hash, obj_table[objname_hash]);
377391
}
378392

379393
return curr_obj_node;
380394
}
381395

382396

383397
static __inline__
384-
file_node* new_file_node(const HChar *filename,
398+
file_node* new_file_node(const HChar *filename, UInt filename_full_hash,
385399
obj_node* obj, file_node* next)
386400
{
387401
Int i;
388402
file_node* file = (file_node*) CLG_MALLOC("cl.fn.nfn.1",
389403
sizeof(file_node));
390404
file->name = VG_(strdup)("cl.fn.nfn.2", filename);
405+
file->name_hash = filename_full_hash;
391406
for (i = 0; i < N_FN_ENTRIES; i++) {
392407
file->fns[i] = NULL;
393408
}
@@ -403,6 +418,7 @@ file_node* CLG_(get_file_node)(obj_node* curr_obj_node,
403418
const HChar *dir, const HChar *file)
404419
{
405420
file_node* curr_file_node;
421+
UInt filename_full_hash;
406422
UInt filename_hash;
407423

408424
/* Build up an absolute pathname, if there is a directory available */
@@ -414,15 +430,17 @@ file_node* CLG_(get_file_node)(obj_node* curr_obj_node,
414430
VG_(strcat)(filename, file);
415431

416432
/* lookup in file hash */
417-
filename_hash = str_hash(filename, N_FILE_ENTRIES);
433+
filename_full_hash = str_hash_full(filename);
434+
filename_hash = filename_full_hash % N_FILE_ENTRIES;
418435
curr_file_node = curr_obj_node->files[filename_hash];
419-
while (NULL != curr_file_node &&
420-
VG_(strcmp)(filename, curr_file_node->name) != 0) {
436+
while (NULL != curr_file_node &&
437+
(curr_file_node->name_hash != filename_full_hash ||
438+
VG_(strcmp)(filename, curr_file_node->name) != 0)) {
421439
curr_file_node = curr_file_node->next;
422440
}
423441
if (NULL == curr_file_node) {
424442
curr_obj_node->files[filename_hash] = curr_file_node =
425-
new_file_node(filename, curr_obj_node,
443+
new_file_node(filename, filename_full_hash, curr_obj_node,
426444
curr_obj_node->files[filename_hash]);
427445
}
428446

@@ -433,12 +451,13 @@ file_node* CLG_(get_file_node)(obj_node* curr_obj_node,
433451
static void resize_fn_array(void);
434452

435453
static __inline__
436-
fn_node* new_fn_node(const HChar *fnname,
454+
fn_node* new_fn_node(const HChar *fnname, UInt fnname_full_hash,
437455
file_node* file, fn_node* next)
438456
{
439457
fn_node* fn = (fn_node*) CLG_MALLOC("cl.fn.nfnnd.1",
440458
sizeof(fn_node));
441459
fn->name = VG_(strdup)("cl.fn.nfnnd.2", fnname);
460+
fn->name_hash = fnname_full_hash;
442461

443462
CLG_(stat).distinct_fns++;
444463
fn->number = CLG_(stat).distinct_fns;
@@ -481,20 +500,23 @@ fn_node* get_fn_node_infile(file_node* curr_file_node,
481500
const HChar *fnname)
482501
{
483502
fn_node* curr_fn_node;
503+
UInt fnname_full_hash;
484504
UInt fnname_hash;
485505

486506
CLG_ASSERT(curr_file_node != 0);
487507

488508
/* lookup in function hash */
489-
fnname_hash = str_hash(fnname, N_FN_ENTRIES);
509+
fnname_full_hash = str_hash_full(fnname);
510+
fnname_hash = fnname_full_hash % N_FN_ENTRIES;
490511
curr_fn_node = curr_file_node->fns[fnname_hash];
491-
while (NULL != curr_fn_node &&
492-
VG_(strcmp)(fnname, curr_fn_node->name) != 0) {
512+
while (NULL != curr_fn_node &&
513+
(curr_fn_node->name_hash != fnname_full_hash ||
514+
VG_(strcmp)(fnname, curr_fn_node->name) != 0)) {
493515
curr_fn_node = curr_fn_node->next;
494516
}
495517
if (NULL == curr_fn_node) {
496518
curr_file_node->fns[fnname_hash] = curr_fn_node =
497-
new_fn_node(fnname, curr_file_node,
519+
new_fn_node(fnname, fnname_full_hash, curr_file_node,
498520
curr_file_node->fns[fnname_hash]);
499521
}
500522

callgrind/global.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ struct _BBCC {
408408

409409
struct _fn_node {
410410
HChar* name;
411+
UInt name_hash; /* full 32-bit hash for fast lookup */
411412
UInt number;
412413
Context* last_cxt; /* LRU info */
413414
Context* pure_cxt; /* the context with only the function itself */
@@ -442,6 +443,7 @@ struct _fn_node {
442443

443444
struct _file_node {
444445
HChar* name;
446+
UInt name_hash; /* full 32-bit hash for fast lookup */
445447
fn_node* fns[N_FN_ENTRIES];
446448
UInt number;
447449
obj_node* obj;
@@ -454,6 +456,7 @@ struct _file_node {
454456
*/
455457
struct _obj_node {
456458
const HChar* name;
459+
UInt name_hash; /* full 32-bit hash for fast lookup */
457460
UInt last_slash_pos;
458461

459462
Addr start; /* Start address of text segment mapping */

0 commit comments

Comments
 (0)