【发布时间】:2012-07-13 01:15:02
【问题描述】:
我正在玩弄一些使用欧几里得算法来计算两个数字的 GCD 的东西。我像往常一样实现了标准的单线,它工作得很好。它用于计算序列并在n 变大时每个元素调用gcd() 多次的算法。我决定看看我是否可以通过记忆做得更好,所以我尝试了以下方法:
size_t const gcd(size_t const a, size_t const b) {
return b == 0 ? a : gcd(b, a % b);
}
struct memoized_gcd : private std::unordered_map<unsigned long long, size_t> {
size_t const operator()(size_t const a, size_t const b) {
unsigned long long const key = (static_cast<unsigned long long>(a) << 32) | b;
if (find(key) == end()) (*this)[key] = b == 0 ? a : (*this)(b, a % b);
return (*this)[key];
}
};
//std::function<size_t (size_t, size_t)> gcd_impl = gcd<size_t,size_t>;
std::function<size_t (size_t, size_t)> gcd_impl = memoized_gcd();
稍后我通过std::function 实例调用所选函数。有趣的是,例如当 n = 10,000 时,在这台计算机上的计算运行时间为 8 秒,而使用记忆的版本接近一分钟,其他一切都相同。
我是否遗漏了一些明显的东西?我将key 用作权宜之计,这样我就不需要专门针对哈希映射使用std::hash。我唯一能想到的可能是 memoized 版本没有获得 TCO 而gcd() 获得,或者通过std::function 调用对于仿函数来说很慢(即使我同时使用它),或者也许我迟钝了。大师们,给我指路。
注意事项
我已经在带有 g++ 4.7.0 的 win32 和 win64 以及带有 g++ 4.6.1 和 4.7.1 的 linux x86 上尝试过这个。
我还尝试了一个带有std::map<std::pair<size_t, size_t>, size_t> 的版本,其性能与未记忆的版本相当。
【问题讨论】:
-
你可能想好好看看记忆版本的内存使用情况。一旦它滑出缓存,你的基于哈希的随机访问就会受到伤害 - 非常糟糕。
-
Euclid 的算法在 a 和 b 的不同值的复杂度上反弹了很多...我感觉 gcd 的最坏情况输入通常不在缓存中,但我认为其他输入将从记忆中获得一些好处。我想我是唯一一个对我观察到的时间差异感到惊讶的人。
-
static_cast<unsigned long long>(a) << 32基本上是无意义的——在 x64 上,size_t通常已经是unsigned long long的类型定义,所以你只是在丢弃位。此外,显示您的实际基准测试的完整代码(包括n的最高值);这和这个一样可能是错误的。 -
好点,在我最初看到这种行为的 x86 上很快就完成了,所以程序的其余部分在 x64 上给出了正确的结果实际上是非常令人惊讶的。没有严格的基准,只是我认为是奇怪的行为。就像我在问题中所说的那样,我主要使用 n = 10000 进行了测试......并且在 x86 上,该语句 not 荒谬,运行时间的差异更大,尽管肯定有其他未考虑的因素。
-
...呃,我的意思是,不,这一点都不荒谬,我可以在这个例子中移出这些位,因为我正在处理相对较小的 a 和 b 值,这严格
标签: c++ performance optimization c++11