我正在玩弄使用Euclid算法来计算两个数字的GCD。我像往常一样实施标准单线,并且工作正常。它用于计算序列的算法中,并且随着gcd()
变大,每个元素调用n
几次。我决定通过回忆来看看我能做得更好,所以这就是我的尝试:
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
调用对于仿函数来说很慢(即使我将它用于两者) ),或者也许是我迟钝了。大师,给我指路。
备注
我在win32和win64上用g ++ 4.7.0和linux x86用g ++ 4.6.1和4.7.1试过这个。
我还尝试了一个std::map<std::pair<size_t, size_t>, size_t>
的版本,其性能与未版本化的版本相当。
答案 0 :(得分:6)
您的GCD版本的主要问题是它可能会占用大量内存,具体取决于使用模式。
例如,如果计算所有对的GCD(a,b)0&lt; = a&lt; 10,000,0 <&lt; = b&lt; 10,000,memoization表最终将有100,000,000个条目。由于在x86上每个条目都是12个字节,因此哈希表将占用至少1.2 GB的内存。使用这么多内存会很慢。
当然,如果您使用值> = 10,000评估GCD,则可以使表格任意大...至少在您用完地址空间或提交限制之前。
摘要:一般来说,记忆GCD是一个坏主意,因为它会导致无限制的内存使用。
可以讨论一些更好的观点:
但正如我所说,你发布的算法表现不佳并不奇怪。
答案 1 :(得分:0)
这并不令人惊讶。在现代CPU上,内存访问速度非常慢,特别是如果它不在缓存中。重新计算一个值通常比将它存储在内存中要快得多。
答案 2 :(得分:0)
频繁的堆分配(创建新条目时)。还有std :: unordered_map查找开销(虽然它可能是常量时间,但肯定比普通数组偏移慢)。缓存未命中(访问模式和大小的函数)。
如果要进行“纯”比较,可以尝试将其转换为使用静态的,堆栈分配的普通数组;这可能是一个使用更多内存的稀疏查找表,但它更能代表memoization iff ,您可以将整个memoized数组放入CPU缓存中。