Move self-search hash table code to Singeli

This commit is contained in:
Marshall Lochbaum 2023-10-30 19:25:05 -04:00
parent b78b654b56
commit 0fb845d336
3 changed files with 174 additions and 100 deletions

View File

@ -648,11 +648,11 @@ cachedBin‿linkerCache ← {
"xa.""src/builtins/arithd.c""dyarith", "xa.""src/builtins/cmp.c""cmp",
"xa.""src/builtins/squeeze.c""squeeze", "xa.""src/utils/mut.c""copy",
"xa.""src/utils/bits.c""bits", "xag""src/builtins/transpose.c""transpose",
"xag""src/builtins/search.c""search", "xa.""src/builtins/fold.c""fold",
"xag""src/builtins/search.c""search", "xag""src/builtins/selfsearch.c""selfsearch"
"xag""src/builtins/scan.c""scan", "xa.""src/builtins/fold.c""fold",
"xag""src/builtins/sort.c""bins"
"2..""src/builtins/select.c""select", "xag""src/builtins/scan.c""scan",
"2a.""src/builtins/slash.c""constrep",
"2..""src/builtins/select.c""select", "2a.""src/builtins/slash.c""constrep",
"xag""src/builtins/slash.c""slash", "2..""src/builtins/slash.c""count"
objs

View File

@ -63,6 +63,19 @@ static inline u64 hash64(u64 x) {
return x;
}
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; }
#if SINGELI
#define SINGELI_FILE selfsearch
#include "../utils/includeSingeli.h"
#define TRY_HASHTAB_RET(NAME, W, RET) \
if (NAME##_c1_hash##W(rp, (u##W*)xv, n)) { decG(x); return RET; }
#else
#define TRY_HASHTAB_RET(NAME, W, RET) (void)0;
#endif
static inline bool use_sorted(B x, u8 logw) {
if (!FL_HAS(x, fl_asc|fl_dsc)) return 0;
if (logw==6) return TI(x, elType) == el_f64;
@ -146,73 +159,6 @@ u8 radix_offsets_2_usz(usz* c0, u32* v0, usz n) {
} \
decG(x); TFREE(alloc);
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; }
// Resizing hash table, with fallback
#define SELFHASHTAB(T, W, RAD, STOP, RES0, RESULT, RESWRITE, THRESHMUL, THRESH, AUXSIZE, AUXINIT, AUXEXTEND, AUXMOVE) \
usz log = 64 - CLZ(n); \
usz msl = (64 - CLZ(n+n/2)) + 1; if (RAD && msl>20) msl=20; \
usz sh = W - (msl<14? msl : 12+(msl&1)); /* Shift to fit to table */ \
usz sz = 1 << (W - sh); /* Initial size */ \
usz msz = 1ull << msl; /* Max sz */ \
usz b = 64; /* Block size */ \
/* Resize or abort if more than 1/2^thresh collisions/element */ \
usz thresh = THRESH; \
/* Filling e slots past the end requires e*(e+1)/2 collisions, so */ \
/* n entries with <2 each can fill <sqrt(4*n) */ \
usz ext = n<=b? n : b + (1ull << (log/2 + 1)); \
TALLOC(u8, halloc, (msz+ext)*(sizeof(T)+AUXSIZE)); \
T* hash = (T*)halloc + msz-sz; \
T x0 = hash##W(xp[0]); rp[0] = RES0; \
memset##W(hash, x0, sz+ext); \
AUXINIT \
usz cc = 0; /* Collision counter */ \
usz i=1; while (1) { \
usz e = n-i>b? i+b : n; \
for (; i < e; i++) { \
T h = hash##W(xp[i]); T j0 = h>>sh, j = j0; T k; \
while (k=hash[j], k!=h & k!=x0) j++; \
cc += j-j0; \
RESWRITE \
} \
if (i == n) break; \
i64 dc = (i64)cc - ((THRESHMUL*i)>>thresh); \
if (dc >= 0) { \
if (sz == msz || (RAD && i < n/2 && sz >= 1<<18)) break; /*Abort*/ \
/* Avoid resizing if close to the end */ \
if (cc<STOP && (n-i)*dc < (i*((i64)n+i))>>(5+log-(W-sh))) continue;\
/* Resize hash, factor of 4 */ \
usz m = 2; \
usz dif = sz*((1<<m)-1); \
sh -= m; sz <<= m; \
hash -= dif; \
usz j = 0; \
cc = 0; \
memset##W(hash, x0, dif); \
AUXEXTEND \
for (j = dif; j < sz + ext; j++) { \
T h = hash[j]; if (h==x0) continue; hash[j] = x0; \
T k0 = h>>sh, k = k0; while (hash[k]!=x0) k++; \
cc += k-k0; \
hash[k] = h; AUXMOVE \
} \
if (cc >= STOP) break; \
thresh = THRESH; \
} \
} \
TFREE(halloc); \
if (i==n) { decG(x); return RESULT; }
#define SELFHASHTAB_VAL(T, W, RAD, STOP, RES0, RESULT, RESWRITE, THRESHMUL, THRESH, INIT) \
SELFHASHTAB(T, W, RAD, STOP, RES0, RESULT, RESWRITE, THRESHMUL, THRESH, \
/*AUXSIZE*/sizeof(u32), \
/* AUXINIT */ \
u32* val = (u32*)(hash+sz+ext) + msz-sz; \
memset32(val, 0, sz+ext); \
INIT , \
/*AUXEXTEND*/val -= dif; memset32(val, 0, dif); , \
/*AUXMOVE*/u32 v = val[j]; val[j] = 0; val[k] = v;)
extern void (*const simd_mark_firsts_u8)(void*,uint64_t,void*,void*);
extern u64 (*const simd_deduplicate_u8)(void*,uint64_t,void*,void*);
@ -268,14 +214,11 @@ B memberOf_c1(B t, B x) {
} }
if (lw==4) { if (n<8) { BRUTE(16); } else { LOOKUP(16); } }
#undef LOOKUP
#define HASHTAB(T, W, RAD, STOP, THRESH) T* xp = (T*)xv; SELFHASHTAB( \
T, W, RAD, STOP, \
1, taga(cpyBitArr(r)), hash[j]=h; rp[i]=k!=h;, \
1, THRESH, 0,,,)
#define TRY_HASHTAB(W) TRY_HASHTAB_RET(memberOf, W, taga(cpyBitArr(r)))
if (lw==5) {
if (n<12) { BRUTE(32); }
i8* rp; B r = m_i8arrv(&rp, n);
HASHTAB(u32, 32, 1, n/2, sz==msz? 1 : sz>=(1<<15)? 3 : 5)
TRY_HASHTAB(32)
// Radix-assisted lookup when hash table gives up
usz rx = 256, tn = 1<<16; // Radix; table length
@ -301,10 +244,10 @@ B memberOf_c1(B t, B x) {
if (lw==6 && canCompare64_norm(&x, &xv, n)) {
if (n<20) { BRUTE(64); }
i8* rp; B r = m_i8arrv(&rp, n);
HASHTAB(u64, 64, 0, n, sz==msz? 0 : sz>=(1<<18)? 0 : sz>=(1<<14)? 3 : 5)
TRY_HASHTAB(64)
decG(r); // Fall through
}
#undef HASHTAB
#undef TRY_HASHTAB
#undef BRUTE
if (RNK(x)>1) {
@ -377,18 +320,11 @@ B count_c1(B t, B x) {
if (lw==3) { if (n<12) { BRUTE(8); } else { LOOKUP(8); } }
if (lw==4) { if (n<12) { BRUTE(16); } else { LOOKUP(16); } }
#undef LOOKUP
#define HASHTAB(T, W, RAD, STOP, THRESH) T* xp = (T*)xv; SELFHASHTAB_VAL( \
T, W, RAD, STOP, \
/*RES0*/0, /*RETURN*/num_squeeze(r), \
/* RESWRITE */ \
bool e0=h==x0; rp[i]=val[j]+(ctr0&-(u32)e0); \
hash[j]=h; val[j]+=!e0; ctr0+=e0; , \
/*THRESHMUL*/1, THRESH, \
/*INIT*/u32 ctr0 = 1;)
#define TRY_HASHTAB(W) TRY_HASHTAB_RET(count, W, num_squeeze(r))
if (lw==5) {
if (n<20) { BRUTE(32); }
i32* rp; B r = m_i32arrv(&rp, n);
HASHTAB(u32, 32, 1, n/2, sz==msz? 1 : sz>=(1<<14)? 3 : 5)
TRY_HASHTAB(32)
// Radix-assisted lookup
usz rx = 256, tn = 1<<16; // Radix; table length
u32* v0 = (u32*)xv;
@ -413,10 +349,10 @@ B count_c1(B t, B x) {
if (lw==6 && canCompare64_norm(&x, &xv, n)) {
if (n<20) { BRUTE(64); }
i32* rp; B r = m_i32arrv(&rp, n);
HASHTAB(u64, 64, 0, n, sz==msz? 0 : sz>=(1<<18)? 0 : sz>=(1<<14)? 3 : 5)
TRY_HASHTAB(64)
decG(r); // Fall through
}
#undef HASHTAB
#undef TRY_HASHTAB
#undef BRUTE
if (RNK(x)>1) {
@ -485,18 +421,19 @@ B indexOf_c1(B t, B x) {
if (lw==3) { if (n<12) { BRUTE(8); } else { LOOKUP(8); } }
if (lw==4) { if (n<12) { BRUTE(16); } else { LOOKUP(16); } }
#undef LOOKUP
#define HASHTAB(T, W, THRESH) SELFHASHTAB_VAL(T, W, 0, 2*n, \
/*RES0*/0, /*RETURN*/reduceI32WidthBelow(r, ctr), \
/* RESWRITE */ \
if (k!=h) { val[j]=ctr++; hash[j]=h; } rp[i]=val[j]; , \
/*THRESHMUL*/2, THRESH, \
/*INIT*/u32 ctr = 1;)
#if SINGELI
#define TRY_HASHTAB(W) \
u32 ctr = indexOf_c1_hash##W(rp, (u##W*)xv, n); \
if (ctr>0) { decG(x); return reduceI32WidthBelow(r, ctr); }
#else
#define TRY_HASHTAB(W) (void)0;
#endif
if (lw==5) {
if (n<12) { BRUTE(32); }
B r;
i32* rp; r = m_i32arrv(&rp, n);
i32* xp = tyany_ptr(x);
i32* xp = xv;
i32 min=I32_MAX, max=I32_MIN;
for (usz i = 0; i < n; i++) {
i32 c = xp[i];
@ -512,17 +449,16 @@ B indexOf_c1(B t, B x) {
decG(x);
return reduceI32WidthBelow(r, u);
}
HASHTAB(u32, 32, sz==msz? 0 : sz>=(1<<18)? 1 : sz>=(1<<14)? 4 : 6)
TRY_HASHTAB(32)
decG(r); // Fall through
}
if (lw==6 && canCompare64_norm(&x, &xv, n)) {
if (n<16) { BRUTE(64); }
i32* rp; B r = m_i32arrv(&rp, n);
u64* xp = tyany_ptr(x);
HASHTAB(u64, 64, sz==msz? 0 : sz>=(1<<17)? 1 : sz>=(1<<13)? 4 : 6)
TRY_HASHTAB(64)
decG(r); // Fall through
}
#undef HASHTAB
#undef TRY_HASHTAB
#undef BRUTE
#undef DOTAB

View File

@ -0,0 +1,138 @@
include './base'
include './cbqnDefs'
local include 'skin/cext'
# Size-specific functions defined in C
def hash_val{v:T} = emit{T, merge{'hash',fmtnat{width{T}}}, v}
def memset{p:pT, v, l} = {
emit{void, merge{'memset',fmtnat{elwidth{pT}}}, p, v, l}
}
def names = tup{'memberOf', 'count', 'indexOf'}
def prims = tup{'∊', '⊒', '⊐' }
def map{f,t,v} = if (v==tupsel{0,f}) tupsel{0,t} else map{slice{f,1}, slice{t,1}, v}
def to_prim = map{names, prims, .}
# Resizing hash table, with fallback
def rty{name} = if (to_prim{name}=='∊') i8 else i32
fn selfhashtab{T, name}(rp:*rty{name}, xp:*T, n:usz) = {
def wt = width{T}
def prim = to_prim{name}
def has_radix = if (prim=='⊐') 0 else wt==32
def {res0,stop} = if (prim=='∊') tup{1,n>>has_radix} else tup{0,2*n}
log := clzc{n}
msl := clzc{n+n/2} + 1; if (has_radix and msl>20) msl=20
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
# Resize or abort if more than 1/2^thresh collisions/element
def {set_thresh, div_thresh} = {
t:usz = 0 # Shift amount
def o = prim=='⊐' # Offset to allow effective t<0
def div_thresh{i} = ((1<<o)*i)>>t
def t0= has_radix*(prim=='∊')
def w = if (prim=='⊒') 1<<6 else wt<<o
# Threshold setter, re-applied on resize
def set_thresh{} = {
if (sz==msz) t = t0
else if ((not has_radix) and sz>=(1<<24)/w) t = 0+o
else if ( sz>=(1<<20)/w) t = 3+o
else t = 5+o
}
tup{set_thresh, div_thresh}
}
set_thresh{}
# Filling e slots past the end requires e*(e+1)/2 collisions, so
# n entries with <2 each can fill <sqrt(4*n)
ext := tern{n<=b, n, b + (usz~~1 << (log/2 + 1))}
x0 := hash_val{xp->0}; rp <-{0} res0
def aux = prim!='∊'
halloc := talloc{u8, (msz+ext)*((wt + aux*width{u32})/8)}
szo := msz-sz # Beginning of allocation to initial table
sze := sz+ext # Initial table to end of allocation
hash := *T~~halloc + szo
memset{hash, x0, sze}
val := *u32~~(hash+sze) + szo
if (aux) { memset{val, 0, sze} }
def {output, write_res} = match (prim) {
{('∊')} => tup{{b}=>b, {j,h,k,x0} => { hash<-{j}h; k!=h }}
{('⊒')} => {
ctr0:u32 = 1
tup{
{b}=>b,
{j,h,k,x0} => {
e0:=promote{u32,h==x0}; vj:=val->j; c0:=ctr0
hash<-{j}h; val<-{j}vj+(e0^1); ctr0+=e0
vj + (c0 & -e0)
}
}
}
{('⊐')} => {
ctr:u32 = 1
tup{
{b}=>promote{u32,b}*ctr,
{j,h,k,x0} => {
if (k!=h) { val<-{j}ctr; ++ctr; hash<-{j}h }
val->j
}
}
}
}
def break=makelabel{}
cc:usz = 0 # Collision counter
i:usz=1; while (1) {
e := tern{n-i>b, i+b, n}
while (i < e) {
h := hash_val{xp->i}; j0 := h>>sh; j := j0
k:=undefined{T}; while (((k=hash->j)!=h) & (k!=x0)) ++j
cc += cast_i{usz, j-j0}
rp <-{i} write_res{j,h,k,x0}
++i
}
if (i == n) goto{break}
def p64{a} = i64~~promote{u64,a}
dc := p64{cc} - p64{div_thresh{i}}
if (dc >= 0) {
if (sz == msz) goto{break} # Abort
if (has_radix and i < n/2 and sz >= 1<<18) goto{break}
# Avoid resizing if close to the end
if (not (cc<stop and p64{n-i}*dc < (p64{i}*p64{n+i})>>(5+log-(wt-sh)))) {
# Resize hash, factor of 4
def m = 2
dif := sz*((1<<m)-1)
sh -= m; sz <<= m
hash -= dif
j:usz = 0
cc = 0
memset{hash, x0, dif}
if (aux) { val -= dif; memset{val, 0, dif} }
@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
if (aux) { v := val->j; val <-{j} 0; val <-{k} v }
}
}
if (cc >= stop) goto{break}
set_thresh{}
}
}
}
setlabel{break}
tfree{halloc}
output{i == n} # Whether it finished
}
def exp_hash{T, name} = {
export{merge{name,'_c1_hash',fmtnat{width{T}}}, selfhashtab{T, name}}
}
each{{n}=>each{exp_hash{.,n},tup{u32,u64}}, names}