用少量重复键排序大数组

时间:2015-07-21 10:31:16

标签: algorithm performance sorting c++11 stdvector

我想对一个巨大的数组进行排序,比如10 {8个X类型的条目,最多N个不同的密钥,其中N是~10 ^ 2。因为我不知道元素的范围或间距,所以不能选择计数排序。所以到目前为止我最好的猜测是使用哈希映射来计算这样的计数

std::unordered_map< X, unsigned > counts;
for (auto x : input)
    counts[x]++;

这个工作正常,比3-way quicksort快4倍,但我是一个紧张的人,而且还不够快。

我想知道:我错过了什么吗?我可以更好地利用N提前知道的事实吗?或者是否可以根据我的需要调整哈希映射?

编辑另一个前提条件是输入序列排序严格,键的频率大致相同。

3 个答案:

答案 0 :(得分:2)

STL的实施在性能方面往往并不完美(请不要进行神圣的战争)。

如果您知道唯一元素( N )的数量有保证且合理的上限,那么您可以轻松实现自己的大小为 2 ^ s 的哈希表&gt ;&GT; 名词的。以下是我通常自己做的事情:

int size = 1;
while (size < 3 * N) size <<= 1;
//Note: at least 3X size factor, size = power of two
//count = -1 means empty entry
std::vector<std::pair<X, int>> table(size, make_pair(X(), -1));
auto GetHash = [size](X val) -> int { return std::hash<X>()(val) & (size-1); };

for (auto x : input) {
  int cell = GetHash(x);
  bool ok = false;
  for (; table[cell].second >= 0; cell = (cell + 1) & (size-1)) {
    if (table[cell].first == x) { //match found -> stop
      ok = true;
      break;
    }
  }
  if (!ok) {             //match not found -> add entry on free place
    table[cell].first = x;
    table[cell].second = 0;
  }
  table[cell].second++;  //increment counter
}

在MSVC2013上,与代码相比,它将时间从0.62秒提高到0.52秒,因为 int 用作 X 类型。

此外,我们可以选择更快的哈希函数。但请注意,散列函数的选择在很大程度上取决于输入的属性。我们来看Knuth's multiplicative hash

auto GetHash = [size](X val) -> int { return (val*2654435761) & (size-1); };

它进一步将时间缩短到0.34秒。

作为结论:你真的想重新实现标准数据结构以实现2倍的速度提升吗?

注意:另一台编译器/机器上的加速可能完全不同。如果你的 X 类型不是POD,你可能需要做一些黑客攻击。

答案 1 :(得分:2)

计数排序确实是最好的,但由于未知的范围或间距而不适用。

似乎可以使用fork-join轻松并行化,例如boost::thread

您还可以尝试更高效的手动哈希映射。 Unorded_map通常使用链接列表来对抗潜在的错误哈希函数。如果散列表不适合L1高速缓存,则链接列表的内存开销可能会损害性能。 Closed Hashing可能会使用更少的内存。一些优化提示:

  • 使用线性探测关闭哈希并且不支持删除
  • 用于位移而不是模数的两个大小哈希表的功率(除法需要多个周期,每个核心只有一个硬件分频器)
  • Low LoadFactor(通过大小输入)以最大限度地减少冲突。这是内存使用和冲突数量之间的交易。应避免使用超过0.5的LoadFactor。哈希表大小为256似乎适合100个条目。
  • cheapo哈希函数。你没有显示X的类型,所以也许更便宜的哈希函数可能会超过更多的冲突。

答案 2 :(得分:0)

我希望将项目存储在已排序的向量中,因为大约100个密钥,意味着插入到向量中只会出现10 ^ 6个条目中的1个。查找将是向量中的处理器效率bsearch