Data-PerfectHash-Shared

 view release on metacpan or  search on metacpan

chd.h  view on Meta::CPAN

            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 )