Robin Hood hash table for 32-bit membership
This commit is contained in:
parent
cb8be206be
commit
ed03720441
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
64
src/singeli/src/hashmap.singeli
Normal file
64
src/singeli/src/hashmap.singeli
Normal 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}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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}}
|
||||
|
||||
@ -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} = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user