[Cython] Hash-based vtables

Dag Sverre Seljebotn d.s.seljebotn at astro.uio.no
Sat Jun 30 12:57:49 CEST 2012


My time is rather limited but I'm slowly trying to get another SEP 200 
in place.

Something that hit me, when I tried to make up my mind about whether to 
have (key, ptr) entries or (key, flags, ptr), is that the fast hash 
table entries can actually be arbitrary size. So we could make the table 
itself

void *table[n]

and then n would be a power of 2 (TBD: benchmark cost of allowing other 
sizes). Since we have the d[i] displacements, it's no problem at all to 
construct displacements to account for variable-size entries.

Proposal:

C-source for an un-initialized table (signature string is placeholder 
and not up for discussion now):

  { "3:method:foo:i4i4->i4", (void*)EXCEPT_STAR_FLAG, &foo_method,
    "2:numpy:SHAPE", &get_shape_method,
    "2:fieldoffset:barfield", (void*)5, 0 /*padding to n=2^k*/ }

I.e. all keys are prepended by the number of slots they use. So methods 
get to use 3 sizeof(void*) slots since they need the flags, but entries 
that don't need flags use only 2 slots. (In this case, "numpy:SHAPE" is 
a protocol defined by NumPy and so doesn't need any flags; or the flags 
are stored under "numpy:FLAGS" by that protocol.)

Then, PyExtensibleType_Ready parses this and rearranges the table to a 
perfect hash-table. As part of that, it parses the string literal keys 
and interns them, so that the number of slots becomes available in a 
more coder-friendly manner:

typedef {
     uint64_t hash; /* lower-64 bit of md5 */
     uint32_t strlen; /* we allow \0 in key */
     uint8_t nslots; /* set to 3 for first example */
     char *key; /* set to "method:foo:i4i4->i4" */
} fasttable_key_t;

Then, the interned keys for the table is the fasttable_key_t*.

Storing the hash inside the key has two pros:
  - Caching the md5 work (provided the interner uses a faster hash 
function to go from string to

Lookup would happen like this:

typedef {
     fasttable_key_t *key;
     uintptr_t flags;
     void *funcptr
} method_t;

(method_t*)PyCustomSlots_Find(mykey, mykey->hash);
/* or, faster: */
(method_t*)PyCustomSlots_Find(mykey, 0x45343453453fabaULL);

If you want to scan the table linearly (to avoid having to bother with 
getting an interned key), you would scan a table of void*, and for every 
entry cast the key to fasttable_key_t* and check nslots for how much to 
skip to get to the next entry.

Too complicated?

Dag


More information about the cython-devel mailing list