标准::tr1::unordered_map::运算符[]的时间效率
Time efficiency on std::tr1::unordered_map::operator[]
我正在优化Visual Studio 2008 SP1中的一段代码。知道unorder_map
在恒定时间插入/删除/查找时非常棒,所以我通过使用unordered_map
作为我的主要数据结构来优化代码。请看下面的代码。
....
typedef std::tr1::unordered_map <__int64, int> umap_id;
const int text1_length = text1.GetLength();
const int text2_length = text2.GetLength();
const int max_d = text1_length + text2_length - 1;
const bool doubleEnd = (64 < max_d);
vector<set<pair<int, int> > > v_map1;
vector<set<pair<int, int> > > v_map2;
vector<int> v1(2 *max_d, 0);
vector<int> v2(2 *max_d, 0);
int x, y;
__int64 footstep;
umap_id footsteps(max_d * 2);
bool done = false;
const bool forward = ((text1_length + text2_length) % 2 == 1);
for (int d = 0; d < max_d; ++d)
{
// Walk forward path one step
v_map1.push_back(set<pair<int, int> >());
for (int k = -d; k <= d; k += 2)
{
if (k == -d || (k != d && v1[k - 1 + max_d] < v1[k + 1 + max_d]))
x = v1[k + 1 + max_d];
else
x = v1[k - 1 + max_d] + 1;
y = x - k;
if (doubleEnd)
{
footstep = (__int64) ((__int64)x << 32 | y);
if (!forward)
footsteps[footstep] = d;
else if (footsteps.find(footstep) != footsteps.end())
done = true;
}
....
}
}
....
但事实证明它仍然很慢。鉴于我的输入相对较小(max_d
=946),它运行超过 20 秒。
我对发布版本进行了探查器分析,探查器揭示了这一行:footsteps[footstep] = d;
是运行447931次并花费了大约 20 秒的主要罪魁祸首。
请注意,在同一循环体中还有另一行代码:else if (footsteps.find(footstep) != footsteps.end())
执行相同次数(即 447931 次),但花费的秒数要少得多。
unordered_map
的operator::[]
对我来说似乎是一个黑匣子。我不知道为什么需要这么长时间。这是一个 32 位应用程序。任何帮助,不胜感激。
> 在没有 SP1 的 VS 2008 中(但使用为您提供 TR1 库的功能包),tr1::unordered_map<>
的默认哈希函数仅考虑键值的较低 32 位。至少这是我对<functional>
标头中template<class _Kty> class hash::operator()
实现的阅读。
作为键的footstep
变量使用为y
计算的任何内容作为其较低的 32 位 - y 中是否有足够的变化,以至于它会自己产生一个很好的哈希值(我无法判断正在计算y
的代码在做什么)? 否则,您可能会将比您想要的更多的项目放入特定的哈希桶中,并生成太多冲突。
如果是这种情况,您可能需要考虑提供自己的哈希函数。
顺便说一下,看起来VS 2010在与64位整数一起使用时具有hash::operator()
的专用化,因此它将对所有64位进行哈希处理-如果您使用的是VS 2010,我的答案中的推测应该不适用。
更新:
经过一些测试,我确信这就是问题所在(VS 2008 SP1中也存在该问题)。您可以通过将编译器升级到VS 2010来解决此问题,VS 2010具有更好的64位类型的哈希函数,或者使用您自己的哈希函数自己处理此问题。以下是我在VS2008中快速测试的一个,它似乎有效:
class hash_int64
: public unary_function<__int64, size_t>
{
public:
typedef __int64 key_t;
typedef unsigned int half_t;
size_t operator()(const key_t& key) const
{
// split the 64-bit key into 32-bit halfs, hash each
// then combine them
half_t x = (half_t) (key & 0xffffffffUL);
half_t y = (half_t) (((unsigned __int64) key) >> 32);
return (hash<half_t>()(x) ^ hash<half_t>()(y));
}
};
然后将typedef
更改为:
typedef std::tr1::unordered_map <__int64, int, hash_int64> umap_id;
在调试构建中,Visual Studio 附带的 STL 大量使用迭代器检查和小型嵌套函数,这些函数在发布版本中全部内联。这就是为什么与发布代码相比,使用 STL 的调试代码非常慢的原因。
可能你遇到了很多碰撞。如果unordered_map是使用哈希函数实现的,并且您遇到很多冲突,则必须遍历列表才能到达您的项目。这可能是一个原因,但我从未看过unordered_map实现。
哈希映射具有相当高的常量开销。只有 946 个元素,具有基本免费的比较运算符,可能应该使用 std::map
的 O(log(n)) 查找。但是,除非存在实现错误,否则operator[]
没有理由花费比find()
更多的时间。
在Visual Studio 2005和2008中,应手动设置_SECURE_SCL=0
。它默认为启用,即使在发布版本中也是如此,这会增加大量的运行时检查,这在某些情况下可能非常昂贵。
Visual Studio 2010 修复了此问题,默认为实际快速发布。
除此之外,可能值得尝试用普通的旧std::map
替换数据结构。当然,只要您在 32 位构建中使用 64 位整数键也是非最佳选择。
以 x64 为目标可能会显着改善事情,但如果您坚持使用 32 位,您可以考虑是否可以用双精度替换整数键,因为 CPU 可以本机处理这些键(我不知道双打的默认哈希函数是什么样子的,而且总体上可能会更慢, 但至少值得测试)
- C++为构建时间获取QDateTime的可靠方法
- 从持续时间构造std::chrono::system_clock::time_point
- 向量 <int> a {N, 0} 和 int arr a[N] = {0} 的时间复杂度有什么区别
- while循环中while循环的时间复杂度是多少
- 使用简单类型列表实现的指数编译时间.为什么
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- 在已经使用Git的情况下减少编译时间
- 有没有一种方法可以创建一个带有哈希表的数据库,该哈希表具有恒定时间查找功能
- 如何将包含epoch时间的十六进制字符串转换为time_t
- 从文本文件中读取时钟时间和事件时间并进行处理
- 具有未知值时的时间复杂性
- 如何减少花费的时间
- C++在变量给定的指定时间内关闭电脑
- rcpp函数中的清理时间很长
- 在SQLITE数据库中写入记录需要花费大量时间.如何提高刀片操作效率?
- 提高驱动程序程序的时间效率
- 时间效率的设计模型,用于从所有MPI流程发送和接收:MPI全部2个通信
- c++线程的安全性和时间效率:为什么有互斥检查的线程有时比没有它的线程工作得更快
- 帮助改进代码,提高时间效率
- 标准::tr1::unordered_map::运算符[]的时间效率