Data-PerfectHash-Shared
view release on metacpan or search on metacpan
for (uint64_t d=0; d<=dcap; d++) {
int good=1; uint64_t m=ph_mix(seed0,d);
for (uint64_t j=0;j<cnt;j++){ uint64_t s=ph_hash_of(b, mem[boff[bk]+j], m)%n; tmp_slots[j]=s;
if (occ[s]) { good=0; break; }
for (uint64_t p=0;p<j;p++) if (tmp_slots[p]==s){ good=0; break; }
if(!good) break; }
if (good){ for(uint64_t j=0;j<cnt;j++) occ[tmp_slots[j]]=1; disp[bk]=(uint32_t)d; found=1; break; }
}
if(!found) ok=0;
}
/* WARNING: must break here, not just set placed=1 and fall through.
* The for-loop's increment (seed0 = seed0*...+1) still runs once more
* even on the successful iteration -- only the *next* condition check
* is skipped. Falling through would silently advance seed0 past the
* value actually used to compute disp[]/bkt_of[] above, so the header
* would record a seed inconsistent with the written displacement array -- has() would then fail for essentially every key. */
if (ok) { placed = 1; break; }
}
if (!placed) { snprintf(err,el,"CHD build failed to converge (pathological key set)");
PHB_FREEALL(); return -1; }
/* --- pick the minimal byte width holding every displacement (r*disp_width
* bytes instead of r*4). In practice max_d ~ n, so disp_width is 1..3 for
* any realistic set (4 is the safety ceiling); always >=1, even for the empty set (r==1,d==0). */
uint32_t max_d = 0;
for (uint64_t k2=0;k2<r;k2++) if (disp[k2] > max_d) max_d = disp[k2];
uint32_t disp_width = max_d <= 0xFFu ? 1u : max_d <= 0xFFFFu ? 2u : max_d <= 0xFFFFFFu ? 3u : 4u;
/* --- compute the FINAL slot for each key, lay out the image, write it --- */
uint64_t disp_off = sizeof(PhHeader);
uint64_t disp_bytes = r * (uint64_t)disp_width;
uint64_t slots_off = (disp_off + disp_bytes + 7) & ~(uint64_t)7; /* 8-byte align slots[] (int64_t/PhStrSlot); disp[] is byte-packed, needs none */
uint64_t slot_sz = (b->key_type==PH_TYPE_INT) ? sizeof(int64_t) : sizeof(PhStrSlot);
uint64_t arena_off = slots_off + n*slot_sz;
uint64_t arena_len = (b->key_type==PH_TYPE_STR) ? ph_arena_len_final(b, idx, N) : 0; /* sum of surviving key lens */
uint64_t file_size = arena_off + arena_len;
img = (uint8_t*)calloc(file_size?file_size:1,1); if (!img) PHB_OOM();
PhHeader *H = (PhHeader*)img;
H->magic=PH_MAGIC; H->version=PH_VERSION; H->key_type=b->key_type; H->disp_width=disp_width;
H->endian=PH_ENDIAN_MARK; H->n=n; H->bucket_count=r; H->seed0=seed0;
H->disp_off=disp_off; H->disp_len=disp_bytes; H->slots_off=slots_off; H->slots_len=n*slot_sz;
H->arena_off=arena_off; H->arena_len=arena_len; H->file_size=file_size;
/* Emit disp[] byte-packed: disp_width little-endian bytes per entry, exactly
* what ph_load_uint_le reads back. img is calloc'd, so all padding stays zero (deterministic image, no heap disclosure). */
for (uint64_t bkt=0; bkt<r; bkt++) {
uint32_t d = disp[bkt];
for (uint32_t wi=0; wi<disp_width; wi++) img[disp_off + bkt*disp_width + wi] = (uint8_t)(d >> (8u*wi));
}
/* place each key at slot = hash(key, mix(seed0, disp[bucket])) % n */
uint64_t awrite = 0;
for (size_t i=0;i<N;i++){
uint64_t bk = ph_hash_of(b, idx[i], seed0) % r;
uint64_t slot = ph_hash_of(b, idx[i], ph_mix(seed0, disp[bk])) % n;
if (b->key_type==PH_TYPE_INT) ((int64_t*)(img+slots_off))[slot] = b->ints[idx[i]];
else { PhStrSlot *ss=&((PhStrSlot*)(img+slots_off))[slot]; size_t len=b->sslots[idx[i]].len;
memcpy(img+arena_off+awrite, b->arena+b->sslots[idx[i]].off, len);
ss->off=awrite; ss->len=len; awrite+=len; }
}
/* Atomic write: build a temp file in the SAME directory, then rename(2) it
* over `path`. Readers mmap `path`, and O_TRUNC on a mapped file would
* SIGBUS them; rename instead leaves the old inode alive until they unmap.
* mkstemp creates it O_CREAT|O_EXCL 0600 (no symlink to follow, so unlike
* ph_open's read side this needs no O_NOFOLLOW); fchmod applies the requested mode after. */
size_t plen = strlen(path);
char *tmp = (char*)malloc(plen + 12);
if (!tmp) { snprintf(err,el,"out of memory"); PHB_FREEALL(); return -1; }
memcpy(tmp, path, plen); memcpy(tmp+plen, ".tmpXXXXXX", 11); /* 10 chars + NUL */
int fd = mkstemp(tmp); /* creates 0600, O_EXCL; fills XXXXXX */
if (fd < 0) { snprintf(err,el,"mkstemp %s: %s", tmp, strerror(errno)); free(tmp); PHB_FREEALL(); return -1; }
int rc = 0;
if (fchmod(fd, mode) != 0) { snprintf(err,el,"fchmod: %s", strerror(errno)); rc=-1; }
else { size_t off=0; while(off<file_size){ ssize_t w=write(fd,img+off,file_size-off); if(w<=0){snprintf(err,el,"write: %s",strerror(errno)); rc=-1; break;} off+=w; } }
close(fd);
if (rc==0 && rename(tmp, path) != 0) { snprintf(err,el,"rename %s: %s", path, strerror(errno)); rc=-1; }
if (rc!=0) unlink(tmp); /* leave no partial temp on failure */
free(tmp);
PHB_FREEALL();
return rc;
}
#undef PHB_OOM
#undef PHB_FREEALL
#endif
( run in 1.037 second using v1.01-cache-2.11-cpan-14f38c9f855 )