我正在优化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次)但耗费的秒数要少得多。
operator::[]
的{{1}}对我来说似乎是一个黑盒子。我无法弄清楚为什么需要这么长时间。这是一个 32位应用程序。任何帮助表示赞赏。
答案 0 :(得分:2)
在调试版本中,随Visual Studio一起使用的STL大量使用迭代器检查和小型嵌套函数,这些函数在发布版本中全部内联。这就是为什么使用STL的调试代码与发布代码相比非常慢。
答案 1 :(得分:2)
在没有SP1的VS 2008中(但是使用为您提供TR1库的Feature Pack),tr1::unordered_map<>
的默认哈希函数仅考虑键值的低32位。至少这是我在template<class _Kty> class hash::operator()
标题中阅读<functional>
实现的内容。
密钥使用的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;
答案 2 :(得分:0)
可能你会遇到很多碰撞。如果使用哈希函数实现unordered_map来创建索引并且您遇到大量冲突,则必须遍历列表才能到达您的项目。这可能是一个原因,但我从未查看过unordered_map实现。
答案 3 :(得分:0)
已知哈希映射具有相当高的常量开销。只有946个元素(基本上是免费的比较运算符)应该使用std::map
的O(log(n))查找。但是,operator[]
没有理由比find()
花费更多时间,除非存在实施错误。
答案 4 :(得分:0)
在Visual Studio 2005和2008中,您应手动设置_SECURE_SCL=0
。它默认启用,即使在发布版本中,也会增加大量的运行时检查,在某些情况下这可能会非常昂贵。
Visual Studio 2010修复此问题,默认为实际发布版本快速。
除此之外,可能值得尝试用普通的std::map
替换数据结构。当然,只要在32位版本中使用64位整数键也是非最佳的。
定位x64可能会明显改善,但是如果你坚持使用32位,你可以考虑是否可以用双精度替换整数键,因为CPU可以原生地处理这些(我不知道是什么虽然看起来像双打的默认散列函数,但它总体上可能会更慢,但至少可能值得测试)