有效地存储一组数字

时间:2012-01-19 18:27:16

标签: c# data-structures

我正在寻找存储整数集合的最有效方法。现在它们被存储在HashSet<T>中,但是分析表明这些集合在一些性能关键代码中占很大比重,我怀疑有更好的选择。

更多细节:

  • 随机查找必须为O(1)或接近它。
  • 收藏品可能会变大,因此需要空间效率。
  • 这些值均匀分布在64位空间中。
  • 不需要可变性。
  • 大小上没有明确的上限,但数以千万计的元素并不少见。

目前最痛苦的表现就是创造它们。这似乎与分配有关 - 清除和重用HashSet在基准测试中有很大帮助,但不幸的是,这在应用程序代码中不是一个可行的选项。

(已添加)实施适合任务的数据结构很好。哈希表还有可行吗?乍一看似乎是一种可能性,但我对它们没有任何实际经验。

5 个答案:

答案 0 :(得分:1)

在这种情况下,

HashSet通常是最好的通用收藏品。

如果您有关于您的收藏的任何具体信息,您可能有更好的选择。

如果你有一个不太大的固定上限,你可以使用合适大小的位向量。

如果你有一个非常密集的集合,你可以改为存储缺失值。

如果你有非常小的集合,&lt; = 4项左右,你可以将它们存储在一个常规数组中。对这种小型阵列的完全扫描可能比使用散列集所需的散列更快。

如果您的数据没有任何更具体的特征,那么“intHashSet的大集合是可行的方式。

答案 1 :(得分:1)

如果值的大小有限,则可以使用bitset。它每整数存储一位。总的来说,内存使用将是log n位,n是最大整数。

另一种选择是布隆过滤器。 Bloom过滤器非常紧凑,但您必须为查找中的偶然误报做好准备。您可以在维基百科中找到更多相关信息。

第三个选项是使用simle排序数组。查找是log n,n是整数。它可能足够快。

答案 2 :(得分:1)

我决定尝试实现一个特殊用途的基于散列的集合类,它使用线性探测来处理冲突:

  • 支持商店是一个简单的long s
  • 数组
  • 数组的大小应大于要存储的预期元素数。
  • 对于值的哈希码,请使用最低有效位31位。

使用基本线性探针搜索后备存储中值的位置,如下所示:

int FindIndex(long value)
{
    var index = ((int)(value & 0x7FFFFFFF) % _storage.Length;
    var slotValue = _storage[index];

    if(slotValue == 0x0 || slotValue == value) return index;

    for(++index; ; index++)
    {
        if (index == _storage.Length) index = 0;
        slotValue = _storage[index];
        if(slotValue == 0x0 || slotValue == value) return index;
    }
}

(我能够确定存储的数据永远不会包含0,因此该数字可以安全地用于空插槽。)

数组需要大于存储的元素数。 (加载因子小于1.)如果该组已被完全填充,则FindIndex()将进入无限循环,如果它用于搜索该组中尚未存在的值。事实上,它需要有相当多的空白空间,否则搜索和检索可能会受到影响,因为数据开始形成大块。

我确信仍有优化空间,我可能会因为大型集合上的后备存储使用某种BigArray<T>或分片而陷入困境。但初步结果很有希望。它的负载系数为0.5时,其执行速度是HashSet<T>的两倍,加载系数为0.8时几乎快两倍,即使在0.9,我的测试仍然可以快40%。

开销是1 / load factor,所以如果这些性能数据在现实世界中保持不变,那么我相信它的内存效率也会高于HashSet<T>。我还没有做过正式的分析,但从HashSet<T>的内部结构来看,我很确定它的开销远高于10%。

-

所以我对这个解决方案很满意,但是如果还有其他可能性我仍然很好奇。也许某种特里?

-

后记:最后还是在实时数据上与HashSet<T>进行了一些竞争性基准测试。 (在我使用合成测试集之前。)它甚至超过了我以前的乐观期望。现实世界的性能比HashSet<T>快6倍,具体取决于集合大小。

答案 3 :(得分:0)

我要做的只是创建一个足够大小的整数数组来处理你需要的整数。是否有任何理由远离通用List<T>http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

答案 4 :(得分:0)

  

目前最痛苦的表现就是创造它们......

正如您明显观察到的那样,HashSet<T>没有一个构造函数,它使用capacity参数来初始化其容量。

我认为可行的一个技巧如下:

int capacity = ... some appropriate number;
int[] items = new int[capacity];
HashSet<int> hashSet = new HashSet<int>(items);
hashSet.Clear();
...

查看使用反射器的实现,这会将容量初始化为items数组的大小,而忽略了此数组包含重复项的事实。但是,它实际上只会添加一个值(零),所以我假设初始化和清算应该合理有效。

我没有对此进行测试,因此您必须对其进行基准测试。并且愿意承担取决于未记录的内部实施细节的风险。

知道为什么Microsoft没有像其他集合类型那样为capacity参数提供构造函数,这将会很有趣。