计算Collatz序列的长度-自定义迭代器会导致速度变慢

Counting the length of the Collatz sequence - custom iterator produces slowdown?

本文关键字:迭代器 速度 自定义 Collatz 计算      更新时间:2023-10-16

我一直在解决UVA问题#100 - " 3n + 1问题"。这是他们的"样本"问题,具有非常宽容的时间限制(限制为3秒,他们的示例解决方案在没有缓存的情况下运行在0.738秒,到目前为止我的最佳解决方案运行在0.016秒),所以我认为无论我对代码进行什么实验,我都应该始终符合限制。好吧,我错了。

问题说明很简单:输入的每一行都有两个数字ij,输出应该输出这些数字,然后输出开始于ij之间的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是很残酷的。