使用记忆功能观察到的奇怪性能
Strange performance observed with memoized function
我在玩一个用欧几里得算法计算两个数字的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=10000时,计算在这台计算机上运行8秒,而对于记忆版本,它接近一分钟,其他一切都是相等的。
我错过了什么显而易见的东西吗?我使用key
作为权宜之计,这样我就不需要为哈希图专门化std::hash
。我唯一能想到的可能是,记忆版本没有获得TCO,而gcd()
获得了TCO,或者通过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>
的版本,它的性能与未移动版本相当。
GCD版本的主要问题是,根据使用模式的不同,它可能会消耗大量内存。
例如,如果计算所有对0<=的GCD(a,b)a<10000,0<=b<10000,则存储表最终将包含100000000个条目。由于在x86上,每个条目是12个字节,因此哈希表将占用至少1.2GB的内存。使用这么多内存会很慢。
当然,如果你用>=10000的值来评估GCD,你可以让这个表任意大。。。至少在地址空间或提交限制用完之前。
摘要:一般来说,存储GCD是个坏主意,因为它会导致无限制的内存使用。
有一些细节可以讨论:
- 随着表超过各种大小,它将存储在越来越慢的内存中:首先是L1缓存,然后是L2缓存、L3缓存(如果存在)、物理内存、磁盘。显然,随着表的增长,存储的成本会急剧增加
- 如果您知道所有输入都在一个小范围内(例如,0<=x<100),那么使用内存化或预计算表仍然是一种优化。很难确定——你必须根据自己的具体情况进行衡量
- 还有其他可能优化GCD的方法。例如,我不确定g++在这个例子中是否自动识别尾部递归。如果没有,可以通过将递归重写为循环来提高性能
但正如我所说,你发布的算法表现不佳一点也不奇怪。
这并不奇怪。在现代CPU上,内存访问非常慢,尤其是在不在缓存中的情况下。重新计算一个值通常比将其存储在内存中更快。
频繁的堆分配(创建新条目时)。还有std::unordered_map查找开销(虽然它可能是恒定时间,但肯定比普通数组偏移量慢)。缓存未命中也是(访问模式和大小的函数)。
如果您想进行"纯"比较,可以尝试将其转换为使用静态、堆栈分配的纯数组;这可能是一个使用更多内存的稀疏查找表,但它将更能代表内存化iff您可以将整个内存化数组放入CPU缓存中。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- OpenMP阵列性能较差
- 递归列出所有目录中的C++与Python与Ruby的性能
- 大小相等但成员数量不同的结构之间的性能差异
- 为什么constexpr的性能比正常表达式差
- 在类中使用随机生成器时出现性能问题
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 海湾合作委员会 ARM 性能下降
- GCC 和 Clang 代码性能的巨大差异
- 在容量内调整矢量大小时的性能影响
- 如何设计具有不同类型的通知和观察器的观察者模式?
- 了解算法的性能差异(如果以不同的编程语言实现)
- 未达到的情况会影响开关外壳性能
- QStringList vs list<shared_ptr<QString>> 性能比较C++
- 是否总是可以将使用递归编写的程序重写为不使用递归的程序C++,性能观点是什么?
- 哪种方法更好,性能明智
- C++ 特征库:引用的性能开销<>
- 与多个 for 循环与单个 for 循环 wrt 相关的性能从多映射获取数据
- 使用记忆功能观察到的奇怪性能
- 对象,引用对象,引用带函数和不带函数的向量元素-观察到的性能差异