Robin Hood hash table for 32-bit membership

This commit is contained in:
Marshall Lochbaum 2023-11-01 15:43:26 -04:00
parent cb8be206be
commit ed03720441
5 changed files with 153 additions and 69 deletions

View File

@ -24,6 +24,10 @@
#include "../utils/talloc.h"
#include "../utils/calls.h"
// From selfsearch.c
extern NOINLINE void memset32(u32* p, u32 v, usz l);
extern NOINLINE void memset64(u64* p, u64 v, usz l);
RangeFn getRange_fns[el_f64+1];
#if SINGELI
extern RangeFn* const simd_getRangeRaw;
@ -345,6 +349,13 @@ B memberOf_c2(B t, B w, B x) {
TABLE(x, w, i8, 0, 1)
return taga(cpyBitArr(r));
}
if (we==el_i32 && xe==el_i32) {
i8* rp; B r = m_i8arrc(&rp, w);
if (memberOf_c2_hash32(rp, tyany_ptr(x), xia, tyany_ptr(w), wia)) {
decG(w); decG(x); return taga(cpyBitArr(r));
}
decG(r);
}
}
H_Sb* set = m_Sb(64);

View File

@ -43,8 +43,8 @@ extern B mul_c2(B, B, B);
extern B scan_add_bool(B x, u64 ia);
extern B scan_max_num(B x, u8 xe, u64 ia);
static NOINLINE void memset32(u32* p, u32 v, usz l) { for (usz i=0; i<l; i++) p[i]=v; }
static NOINLINE void memset64(u64* p, u64 v, usz l) { for (usz i=0; i<l; i++) p[i]=v; }
extern NOINLINE void memset32(u32* p, u32 v, usz l) { for (usz i=0; i<l; i++) p[i]=v; }
extern NOINLINE void memset64(u64* p, u64 v, usz l) { for (usz i=0; i<l; i++) p[i]=v; }
#if SINGELI
#define SINGELI_FILE selfsearch

View File

@ -0,0 +1,64 @@
local include 'skin/cext'
local include './cbqnDefs' # talloc/tfree
# Defined in C
def memset{p:pT, v, l} = {
emit{void, merge{'memset',fmtnat{elwidth{pT}}}, p, v, l}
}
# These hashes are stored in tables and must be invertible!
# Murmur3
def hash_val{x0:u32} = {
x := x0
x ^= x >> 16; x *= 0x85ebca6b
x ^= x >> 13; x *= 0xc2b2ae35
x ^= x >> 16; x
}
def hash_val{x0:u64} = {
x := x0
x ^= x >> 33; x *= 0xff51afd7ed558ccd
x ^= x >> 33; x *= 0xc4ceb9fe1a85ec53
x ^= x >> 33; x
}
# CRC32
def hash_val{x:u32 & hasarch{'SSE4.2'}} = {
emit{u32, '_mm_crc32_u32', 0x973afb51, x}
}
# Allocate and initialize resizing hash table
# Initial size sz+ext and maximum size msz+ext
# One region for each type in Ts initialized with value from v0s
def hash_alloc{sz, msz, ext, Ts, v0s} = {
def ws = each{width,Ts}
each{assert, slice{ws,0,-1} >= slice{ws,1}} # Doesn't do alignment
def add{}=0; def add{a,...r} = a+add{...r}
halloc := talloc{u8, (msz+ext)*add{...ws/8}}
szo := msz-sz # Beginning of allocation to initial table
sze := sz+ext # Initial table to end of allocation
def pe{{}} = halloc; def pl{{}} = tup{}
def pe{{..._, p}} = p+sze # Next unallocated space given pointers so far
def pl{{...R, T}} = { def ps=pl{R}; tup{...ps, *T~~pe{ps}+szo} }
ptrs := pl{Ts}
each{memset{., ., sze}, ptrs, v0s}
tup{ptrs, {}=>tfree{halloc}}
}
def hash_resize{hash,vals, x0,sh,sz,ext,cc, m} = {
dif := sz*((1<<m)-1)
sh -= m; sz <<= m
hash -= dif
j:usz = 0
cc = 0
memset{hash, x0, dif}
each{{val} => { val -= dif; memset{val, 0, dif} }, vals}
@for (j from dif to sz + ext) {
h := hash->j
if (h != x0) {
hash <-{j} x0
k0 := h>>sh; k := k0; while (hash->k!=x0) ++k
cc += cast_i{usz, k-k0}
hash <-{k} h
each{{val} => { v := val->j; val <-{j} 0; val <-{k} v }, vals}
}
}
}

View File

@ -1,6 +1,7 @@
include './base'
include './mask'
include './vecfold'
include './hashmap'
def findFirst{C, M, F, ...v1} = {
def exit = makelabel{}
@ -329,3 +330,73 @@ fn getRange{E}(x0:*void, res:*i64, n:u64) : u1 = {
}
exportT{'simd_getRangeRaw', each{getRange, tup{i8,i16,i32,f64}}}
# Hash tables
fn hashtab_memberOf{T}(rp:*i8, ip:*T, m:usz, fp:*T, n:usz) = {
def wt = width{T}
def stop = m
log := clzc{m}
msl := clzc{m} + 2
sh := wt - tern{msl<14, msl, 12+(msl&1)} # Shift to fit to table
sz := usz~~1 << (wt - sh) # Initial size
msz := usz~~1 << msl # Max sz
b:usz = 64 # Block size
# Filling e slots past the end requires e*(e+1)/2 collisions, so
# m entries with <2 each can fill <sqrt(4*m)
ext := tern{m<=b, m, b + (usz~~1 << (log/2 + 1))}
maxh := T~~maxvalue{T}
def {{hash,...vals}, hash_free} = hash_alloc{
sz, msz, ext, tup{T}, tup{maxh}
}
def lookup=makelabel{}; def abort=makelabel{}; def dup=makelabel{}
cc:usz = 0 # Collision counter
has_maxh:u1 = 0
i:usz=0; while (1) {
e := tern{m-i>b, i+b, m}
while (i < e) {
h := hash_val{load{ip,i}}; j := h>>sh
k := load{hash, j}
# Robin Hood insertion
if (k == maxh) {
has_maxh |= h==maxh
store{hash, j, h}
} else if (k != h) {
j0 := j; ji := j # Save value; insertion point
do {
++j; kn := load{hash,j}; if (kn==h) goto{dup}
def c = promote{T, h >= k}
store{hash, j-c, k}
ji += c
k = kn
} while (k != maxh)
store{hash, ji, h}
cc += cast_i{usz, j-j0}
setlabel{dup}
}
++i
}
if (i == m) goto{lookup}
# Check collision counter and possibly resize (needs tuning)
if (cc >= stop and sz == msz) goto{abort}
if (16*cc >= stop and sz < msz) {
hash_resize{hash,vals, maxh,sh,sz,ext,cc, 2} # Resize hash, factor of 4
if (cc >= stop) goto{abort}
}
}
setlabel{lookup}
end := maxh>>sh + promote{T,ext} # Unreachable
if (has_maxh) { end=maxh>>sh; while (load{hash,end}!=maxh) ++end }
@for (rp, fp over n) {
h := hash_val{fp}; j := h>>sh
k:=undefined{T}; while ((k=load{hash,j}) < h) ++j
rp = promote{i8, (k==h) & (j<end)}
}
setlabel{abort}
hash_free{}
i == m # Whether it finished
}
export{'memberOf_c2_hash32', hashtab_memberOf{u32}}

View File

@ -1,68 +1,6 @@
include './base'
include './cbqnDefs'
local include 'skin/cext'
# Defined in C
def memset{p:pT, v, l} = {
emit{void, merge{'memset',fmtnat{elwidth{pT}}}, p, v, l}
}
# These hashes are stored in tables and must be invertible!
# Murmur3
def hash_val{x0:u32} = {
x := x0
x ^= x >> 16; x *= 0x85ebca6b
x ^= x >> 13; x *= 0xc2b2ae35
x ^= x >> 16; x
}
def hash_val{x0:u64} = {
x := x0
x ^= x >> 33; x *= 0xff51afd7ed558ccd
x ^= x >> 33; x *= 0xc4ceb9fe1a85ec53
x ^= x >> 33; x
}
# CRC32
def hash_val{x:u32 & hasarch{'SSE4.2'}} = {
emit{u32, '_mm_crc32_u32', 0x973afb51, x}
}
# Allocate and initialize resizing hash table
# Initial size sz+ext and maximum size msz+ext
# One region for each type in Ts initialized with value from v0s
def hash_alloc{sz, msz, ext, Ts, v0s} = {
def ws = each{width,Ts}
each{assert, slice{ws,0,-1} >= slice{ws,1}} # Doesn't do alignment
def add{}=0; def add{a,...r} = a+add{...r}
halloc := talloc{u8, (msz+ext)*add{...ws/8}}
szo := msz-sz # Beginning of allocation to initial table
sze := sz+ext # Initial table to end of allocation
def pe{{}} = halloc; def pl{{}} = tup{}
def pe{{..._, p}} = p+sze # Next unallocated space given pointers so far
def pl{{...R, T}} = { def ps=pl{R}; tup{...ps, *T~~pe{ps}+szo} }
ptrs := pl{Ts}
each{memset{., ., sze}, ptrs, v0s}
tup{halloc, ptrs}
}
def hash_resize{hash,vals, x0,sh,sz,ext,cc, m} = {
dif := sz*((1<<m)-1)
sh -= m; sz <<= m
hash -= dif
j:usz = 0
cc = 0
memset{hash, x0, dif}
each{{val} => { val -= dif; memset{val, 0, dif} }, vals}
@for (j from dif to sz + ext) {
h := hash->j
if (h != x0) {
hash <-{j} x0
k0 := h>>sh; k := k0; while (hash->k!=x0) ++k
cc += cast_i{usz, k-k0}
hash <-{k} h
each{{val} => { v := val->j; val <-{j} 0; val <-{k} v }, vals}
}
}
}
include './hashmap'
def names = tup{'memberOf', 'count', 'indexOf'}
def prims = tup{'∊', '⊒', '⊐' }
@ -108,8 +46,8 @@ fn selfhashtab{T, name}(rp:*rty{name}, xp:*T, n:usz) = {
x0 := hash_val{xp->0}
rp <-{0} res0
def aux = prim!='∊'
def {halloc,{hash,...vals}} = hash_alloc{
sz, msz, ext, merge{tup{T}, aux**u32}, merge{tup{x0}, aux**0}
def {{hash,...vals}, hash_free} = hash_alloc{
sz, msz, ext, tup{T, ...aux**u32}, tup{x0, ...aux**0}
}
def {output, write_res} = match (prim) {
@ -160,8 +98,8 @@ fn selfhashtab{T, name}(rp:*rty{name}, xp:*T, n:usz) = {
}
}
setlabel{break}
tfree{halloc}
output{i == n} # Whether it finished
hash_free{}
output{i == n} # Whether it finished, and unique count if ⊐
}
def exp_hash{T, name} = {