C-64位Ints中的快速布隆过滤器,高频初始化/查询/销毁周期

时间:2010-12-03 23:06:17

标签: c hashtable bloom-filter

对于大型项目的一部分,我需要一个bloom过滤器实现。整个项目都在C(只有C!没有C ++),不幸的是,我还没有找到任何像样的基于C的布隆过滤器实现(除了proof-of-concept实现)。

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

每50ms,我收到两组(W,R)的64位整数。我需要找到W& amp; R收到了这个时代(IOW,布隆过滤器必须在每个时代开始新鲜)。下面的代码显示了一般控制流程

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这样的重型哈希函数。

3 个答案:

答案 0 :(得分:3)

你想要保持这个简单。由于您处理的是少量元素,它们是64位整数(在32位机器上可以快速比较,在64位机器上可以快速进行比较)。作为第一个镜头,我会使用64K元素的哈希表。插入时,通过将每个16位片段连接在一起得到一个表索引,对64位int进行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;
}

如果插入不是随机分布的,则需要更好的冲突解决方案。你也可能想出一个更好的哈希方法。

答案 1 :(得分:2)

如果我理解你:

  1. 您将每个bloom过滤器实现为大小为N的位图。
  2. 您假设散列函数均匀分布元素。
  3. 如果您有~1000个元素,则可以调整布隆过滤器的位集大小,以便只设置它们的一些可容忍的加载因子,或者平均为1/8,以保持设置的交叉误报率低。尽管如此,你可能总会得到一些误报。例如,对于布隆过滤器集合交集,在set1 = { e1 }set2 = { e2 }e1 != e2set1 intersect set2 = { }bf(set1) interesect bf(set2) <> {}时可能会出现一些误报。请注意,永远不会出现错误否定 - 如果bf(set1) intersect bf(set2) = {}则必须set1 intersect set2 = {}

    我认为你的算法应该为R和W形成BF,然后尽可能多地将它们相交,如下面的变量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);
        ...
    }
    

    预期的运行时间?

    1. 每次调用初始化1 KB的3个BF,例如128个ulongs,这些小位图位于服务条款上,应该很容易融入L1 $,无论如何都有很大的空间位置;
    2. 向bfR添加100-1000个元素,例如~1000内联调用添加,一些位移和存储;
    3. 点击测试bfR的100-1000个元素,例如~1000内联调用,一些位移,掩码,测试;
    4. 或变体2,仅在~128对ulongs上执行元素和“
    5. (当然注意上面代码中的所有/和%都被优化为移位和掩码。)

      总的来说,这可能是成千上万的指令和几千个L1或L2缓存命中;使用2 GHz循环时间机器,如果一旦预热需要超过几毫秒,我会感到惊讶。

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

      *今天奇怪的事实 - MS VC ++ 4.0的细粒度'最小重建'功能(http://msdn.microsoft.com/en-us/library/kfz8ad09(VS.80).aspx)取决于盛开的过滤器 - 但我们当时从未听说过过滤器。相反,我们认为我们发明了一套具有概率 - 成员 - 测试数据结构的新集...... *

      您怎么看?

      快乐的黑客攻击!

      等等,我忘了提及:

      1. 过度杀伤,但您可以使用矢量SIMD指令(例如SSE)加快清除和交叉操作。
      2. 您可以利用数据的其他属性。例如,如果每个调用的R和W数组之间存在任何相似性,您可以将强力算法转换为增量算法,尽管您可能必须使用计数布隆过滤器。
      3. 根据加载因子和元素本身的重复性,您可能不需要在每次迭代时清除位图。当你最终得到一个非空的交叉点时,你只需要清除它们(然后重新运行add()和intersect()。)
      4. 你的问题大小在这里不需要它,但如果你有数百万个元素,你可以将输入R和W列表分成子列表,将它们分配给多个核心,为R和W构建BF的私有副本,然后将BF(R)s和BF(W)一起折叠(或)。

答案 2 :(得分:0)

你有一个相对较少的整数和3毫秒来处理它们。

你的CPU是否足够快以保持这种简单并对两个列表进行排序?排序应该很快,因为一切都将舒适地放在缓存中。通过两个列表来查找交集非常快,您将永远不必担心像使用布隆过滤器那样处理误报。