计算Collatz序列的长度-自定义迭代器会导致速度变慢
Counting the length of the Collatz sequence - custom iterator produces slowdown?
我一直在解决UVA问题#100 - " 3n + 1问题"。这是他们的"样本"问题,具有非常宽容的时间限制(限制为3秒,他们的示例解决方案在没有缓存的情况下运行在0.738秒,到目前为止我的最佳解决方案运行在0.016秒),所以我认为无论我对代码进行什么实验,我都应该始终符合限制。好吧,我错了。
问题说明很简单:输入的每一行都有两个数字i
和j
,输出应该输出这些数字,然后输出开始于i
和j
之间的Collatz序列的最大长度。
我准备了四种溶液。它们很相似,都能给出很好的答案。
第一种解决方案在vector
中缓存最多0x100000
序列长度。0
的长度表示从这个特定数字开始的序列的长度尚未计算。它运行得足够快- 0.03秒。
第二种解决方案非常相似,只是它缓存了用unordered_map
实现的稀疏数组中的每个长度。它的运行速度比之前的解决方案要慢,但仍然符合极限:0.28秒。
作为练习,我还编写了基于第二个解决方案的第三个解决方案。目标是使用max_element
函数,该函数只接受迭代器。我不能使用unordered_map::iterator
,因为增量迭代器在地图大小上是线性的;因此,我编写了一个自定义迭代器,操作一个抽象"容器",该容器"保存"每个可能数字的序列长度(但实际上只在需要时计算它们并缓存它们)。在它的核心,它实际上是相同的unordered_map
解决方案-只是在顶部添加了额外的迭代器层。解决方案不符合3秒的限制。
现在出现了我无法理解的事情。虽然第三种解决方案显然是故意过于复杂的,但我很难相信一个额外的迭代器层会产生这样的减速。为了验证这一点,我在vector
解决方案中添加了一个相同的迭代器层。这是我的第四个解。从这个迭代器的想法对我的unordered_map
解决方案的影响来看,我预计这里也会有相当大的减速;但奇怪的是,这根本没有发生。此解决方案的运行速度几乎与普通vector
解决方案一样快,为0.035秒。
这怎么可能?究竟是什么导致了第三种解决方案的减速?以完全相同的方式将两个相似的解决方案过度复杂化,这怎么可能会大大降低其中一个的速度,而几乎不会损害另一个呢?为什么将迭代器层添加到unordered_map
解决方案中会使其无法及时适应,而对vector
解决方案做同样的事情几乎不会减慢它的速度?
我发现,如果输入包含许多重复的行,这个问题似乎最明显。我在我的机器上针对1 1000000
输入测试了所有四种解决方案,重复了200次。使用普通向量的解决方案在1.531秒内处理了所有这些问题。使用向量和附加迭代器层的解决方案花费了3.087秒。使用普通无序映射的解决方案花费了33.821秒。使用无序映射和附加迭代器层的解决方案花费了超过半个小时 -我在31分钟0.482秒后停止了它!我在Linux mint 17.2 64位,g++版本Ubuntu 4.8.4-2ubuntu1~14.04上测试了它,标志-std=c++11 -O2,处理器赛扬2955U @1.4 GHz x 2
这似乎是GCC 4.8中的一个问题。在4.9中没有出现。由于某种原因,后续的外部循环(使用填充的unordered_map
缓存)运行得越来越慢,而不是更快。我不知道为什么,因为unordered_map
没有变大。
如果您遵循该链接并将GCC 4.8切换到4.9,那么您将看到预期的行为,即在相同数值范围内的后续运行增加的时间很少,因为它们利用了缓存。
总的来说,"保守"编译器更新的理念已经过时很长时间了。今天的编译器经过严格的测试,您应该使用最新的(或至少是最近的)版本进行日常开发。对于一个在线裁判来说,让你面对长期修复的bug是很残酷的。
- 使用std::multimap迭代器创建std::list
- 来自 std::list 的迭代器 .end() 按预期返回"0xcdcdcdcdcdcdcdcd"但 .begin()
- C++中带有List类的迭代器Segfault
- 如何在c++迭代器类型中包装std::chrono
- 集合上的输出迭代器:assign和increment迭代器
- Boost Spirit,获取迭代器内部语义动作
- 对于set上的循环-获取next元素迭代器
- 为什么output_editor Concept不需要output_e迭代器标记
- c++17文件系统::recursive_directory迭代器()在mac上没有给出这样的目录,但在windows上
- 使用迭代器时如何访问对象在向量中的位置?
- std::vector::迭代器是否可以合法地作为指针
- 跟随整数索引列表的自定义类迭代器
- 不明白迭代器,引用和指针失效,一个例子
- 我可以使用反向迭代器作为ForwardIt吗
- ESP8266单片机矢量迭代器的C++问题
- 如何在C++中将迭代器作为函数参数传递
- 是否应避免从非常量迭代器转换为常量迭代器?
- 前向迭代器与多个绑定相结合的迭代速度太快
- c++速度比较迭代器与索引
- 计算Collatz序列的长度-自定义迭代器会导致速度变慢