C-64位整数中的快速Bloom过滤器,高频初始化/查询/销毁循环

C-64位整数中的快速Bloom过滤器,高频初始化/查询/销毁循环,c,hashtable,bloom-filter,C,Hashtable,Bloom Filter,我需要一个布鲁姆过滤器的实施,为一个大型项目的一部分。整个项目都是C语言的(而且只有C语言!没有C++),不幸的是,我还没有找到任何合适的基于C的BloomFilter实现(除了一个实现) 我的布卢姆过滤器要求: 1.包含布卢姆过滤器的模块每50毫秒运行一次 整个模块需要在5-6ms内完成执行, 这意味着整个布卢姆过滤器代码必须在3毫秒内完成 2.元素是64位整数 3.我总共少于8k个元素(包括插入/查询) 常见的情况是向过滤器中插入几百个,以及1000-1500个查询 每50毫秒,我会收到两组

我需要一个布鲁姆过滤器的实施,为一个大型项目的一部分。整个项目都是C语言的(而且只有C语言!没有C++),不幸的是,我还没有找到任何合适的基于C的BloomFilter实现(除了一个实现)

我的布卢姆过滤器要求:
1.包含布卢姆过滤器的模块每50毫秒运行一次
整个模块需要在5-6ms内完成执行,
这意味着整个布卢姆过滤器代码必须在3毫秒内完成
2.元素是64位整数
3.我总共少于8k个元素(包括插入/查询)
常见的情况是向过滤器中插入几百个,以及1000-1500个查询

每50毫秒,我会收到两组(W,R)64位整数。我需要找到这个历元中接收到的W&R之间的交点(注意,bloom过滤器必须为每个历元重新启动)。下面的代码显示了一般控制流程

sleep(50ms)
...module code..
clear(bloomfilter) /* basically a memset(0) on bloomfilter bitmap */
W = getListW()
for each entry in W
  insert(bloomfilter, entry)
R = getListR()
for each entry in R
   if (present(bloomfilter, entry))
      ..do something with entry..
..rest of module code..

现在,我看到了几篇论文,它们声称在非常大的数据集上进行快速布鲁姆过滤器操作。但我的要求不同。我需要快速种子(插入W)和快速查询。散列函数是另一个关注点。由于时间限制,我负担不起像SHA1这样的重载哈希函数。

您希望保持这种简单。因为您处理的是少量元素,它们是64位整数(在32位机器上比较快,在64位机器上比较快)。首先,我将使用一个包含64K元素的哈希表。插入时,对64位int进行16位“散列”,方法是将每个16位片段异或到一起以获得表索引。如果速度不够快,请分析它以找出原因

这听起来不像用bloom过滤器做某事那么性感。但实际上,您只处理8K整数。下面是我现在编写的一些代码(没有尝试编译)。假设插入的数字是随机分布的,这可能会非常快,如果任何一个插入为0,这将不起作用

uint64_t table[65536] = {0};

void clear()
{
    memset(table, 0, sizeof(table));
}

uint16_t hash(uint64_t val)
{
    assert(ele != 0);
    uint16_t *parts = (uint16_t*)&ele;
    uint16_t h = 0x5AA5;
    h = h * 131 + parts[0];
    h = h * 131 + parts[1];
    h = h * 131 + parts[2];
    h = h * 131 + parts[3];
    return h;
}

void insert(uint64_t ele)
{
    uint16_t h = hash(ele);
    while (table[h])
        ++h;
    table[h] = ele;
}

int find(uint64_t ele) 
{
    int res = 0;
    uint16_t h = hash(ele);
    while (table[h] != ele)
    {
        if (!table[h])
            return 0;
        ++h;
    }
    return 1;
}
如果插入内容不是随机分布的,则需要更好的冲突解决方案。你也可以想出一个更好的散列方法。

如果我理解你的意思:

  • 将每个bloom过滤器实现为大小为N的位图
  • 假设一个散列函数均匀地分布元素
  • 如果有~1000个元素,则可以调整bloom filter位集的大小,以便只设置其中一些可容忍的负载因子,可能平均为1/8,以保持集的交集误报率较低。然而,你可能总是会得到一些误报。例如,对于bloom过滤器集交叉点,当
    set1={e1}
    set2={e2}
    e1!=e2
    ,即
    set1与set2={}
    相交,但
    bf(set1)与bf(set2){}
    。注意,你永远不会得到假阴性——如果
    bf(set1)与bf(set2)={}
    相交,那么必然
    set1与set2={}

    我认为您的算法应该为R和W形成BFs,然后一次使它们相交尽可能多的位,如下面的变量2所示

    快砍,生锈的C:

    const unsigned N = 1024 * 8;
    const unsigned BPW = 8 * sizeof ulong;
    typedef unsigned long ulong;
    typedef struct BF { ulong bits[N/BPW]; } BF;
    
    unsigned hash(ulong e) { return foo(e) % N; }
    void clear(BF* pbf) { memset(pbf->bits, 0, sizeof(pbf->bits)); }
    void add(BF* pbf, ulong e) { unsigned h = hash(e); bf.bits[h/BPW] |= 1 << (h%BPW); }
    bool hit(BF* pbf, ulong e) { unsigned h = hash(e); return (bf.bits[h/BPW]>>(h%BPW)) & 1; }
    bool intersect(BF* pbfResult, BF* pbf1, BF* pbf2) {
        bool empty = TRUE;
        for (unsigned i = 0; i < N/BPW; i++)
            if ((pbfResult->bits[i] = pbf1->bits[i] & pbf2->bits[i]) != 0)
                empty = FALSE;
        return !empty;
    }
    void intersectRW(unsigned nr, ulong* r, unsigned nw, ulong* w) {
        BF bfR, bfW, bfIntesection;
        unsigned i;
    
        clear(&bfR);
        for (i = 0; i < nr; i++)
             add(&bfR, r[i]);
    
        // variant 1: enumerate elements of W that hit in BF(R)
        for (i = 0; i < nw; i++)
             if (hit(&bfR, w[i]))
                 ... w[i] ...
    
        // variant 2: determine if intersection of BFs is empty and get intersection BF
        clear(&bfW);
        for (i = 0; i < nw; i++)
             add(&bfW, w[i]);
        bool any = intersect(&bfIntersection, &bfR, &bfW);
        ...
    }
    
    const无符号N=1024*8;
    常量无符号BPW=8*sizeof ulong;
    typedef无符号长ulong;
    typedef结构BF{ulong bits[N/BPW];}BF;
    无符号散列(ulonge){返回foo(e)%N;}
    无效清除(BF*pbf){memset(pbf->bits,0,sizeof(pbf->bits));}
    void add(BF*pbf,ulong e){unsigned h=hash(e);BF.bits[h/BPW]|=1>(h%BPW))&1;}
    布尔相交(BF*pbfResult,BF*pbf1,BF*pbf2){
    bool empty=TRUE;
    for(无符号i=0;ibits[i]=pbf1->bits[i]&pbf2->bits[i])!=0)
    空=假;
    返回!空;
    }
    无效相交RW(无符号nr、ulong*r、无符号nw、ulong*w){
    BF-bfR、bfW、BF段;
    未签名的i;
    清除(&bfR);
    对于(i=0;i
    预期运行时间

  • 每次调用初始化3个1KB的BF,例如128个Ulong,这些小位图位于TOS上,应该很容易放入L1$,并且无论如何都具有很大的空间局部性
  • 向bfR添加100-1000个元素,例如约1000次add内联调用、一些位移位和存储
  • 命中测试bfR的100-1000个元素,例如约1000次命中的内联调用、一些位移位、掩码、测试
  • 或变体2,仅对约128对Ulong执行元素级AND
  • (当然,请注意,上面代码中的所有和/和%都优化为移位和掩码。)

    总共可能有几万条指令和几千次一级或二级缓存命中;对于一台2GHz的循环时间机器,我会感到惊讶,如果这需要超过几毫秒的预热时间

    至于散列函数,您没有告诉我们这些64位元素的分布情况。如果它们已经分布得很好,您可以通过几个移位、异或和掩码将64位折叠到16位

    *今天的奇怪事实——MS VC++4.0的细粒度“最小重建”功能(http://msdn.microsoft.com/en-us/library/kfz8ad09(VS.80).aspx)依赖于大量的bloom过滤器,但我们当时从未听说过bloom过滤器。相反,我们认为我们已经发明了一个具有概率隶属度测试数据结构的新集合*

    你觉得怎么样

    快乐的黑客

    等等,我忘了说:

  • 过度杀戮,但您可以使用向量SI加速清除和相交操作