计算Collat​​z序列的长度 - 自定义迭代器会产生减速吗?

时间:2015-07-22 07:12:48

标签: c++ performance iterator

我一直在解决UVA问题#100 - "The 3n + 1 problem"。这是他们的样本"问题,有一个非常宽容的时间限制(限制为3秒,他们的sample solution在0.738秒没有缓存,我的最佳解决方案到目前为止在0.016秒运行),所以我想我不是试验代码,我应该总是符合极限。好吧,我错了。

问题规范很简单:每行输入都有两个数字ij,输出应打印这些数字,然后是Collat​​z序列的最大长度,其开头位于{ {1}}和i包含。

我准备了四种解决方案。它们非常相似,都能产生很好的答案。

The first solutionj中缓存最多0x100000个序列长度。长度vector表示尚未计算从该特定数字开始的序列的长度。它运行得足够快 - 0.03秒。

The second solution非常相似,只是它在用0实现的稀疏数组中缓存每个单一长度。它运行速度比之前的解决方案慢,但仍然适合极限:0.28秒。

作为练习,我还写了the third solution,基于第二个。目标是使用unordered_map函数,该函数只接受迭代器。我无法使用max_element,因为增量这样的迭代器是AFAIK,在地图大小上是线性的;因此,我编写了一个自定义迭代器,在一个抽象的容器上运行" "持有"每个可能数字的序列长度(但实际上只计算它们并根据需要缓存它们)。在它的核心,它实际上是相同的unordered_map::iterator解决方案 - 只有在顶部添加了额外的迭代器层。该解决方案不适合3秒。限制。

现在我无法理解。虽然显然第三种解决方案有目的地过于复杂,但我几乎不相信额外的迭代器层会产生这样的减速。为了解决这个问题,我在unordered_map解决方案中添加了一个相同的迭代器层。这是my fourth solution。从这个迭代器的想法对我的vector解决方案的作用来看,我预计这里也会出现相当大的放缓;但奇怪的是,这根本没有发生。此解决方案的运行速度几乎与普通unordered_map一样快,为0.035秒。

这怎么可能?究竟是什么导致第三种解决方案的放缓?怎么可能以完全相同的方式过度复杂化两个类似的解决方案,使其中一个减慢很多,并且几乎不会伤害另一个?为什么将迭代器层添加到vector解决方案使得它不及时,并且对unordered_map解决方案做同样的事情几乎没有减慢它的速度?

编辑:

我发现如果输入包含许多重复行,问题似乎最明显。我根据vector的输入测试了我机器上的所有四种解决方案,重复了200次。具有普通向量的解决方案在1.531秒内处理所有这些。向量和附加迭代器层的解决方案花费3.087秒。普通无序地图的解决方案耗时33.821秒。无序地图和额外的迭代器层管理的解决方案花了超过半小时 - 我在31分0.482秒后暂停了它!我在Linux mint 17.2 64 bit,g ++版本Ubuntu 4.8.4-2ubuntu1~14.04上测试了它,标志为-std = c ++ 11 -O2,处理器Celeron 2955U @ 1.4 GHz x 2

1 个答案:

答案 0 :(得分:2)

appears to be GCC 4.8中存在问题。它不会发生在4.9上。由于某种原因,后续外部循环(使用填充的unordered_map缓存)运行速度逐渐变慢,而不是更快。我不知道为什么,因为unordered_map并没有变大。

如果您按照该链接并将GCC 4.8切换到4.9,那么您将看到后续运行在同一数值范围内的预期行为,因为它们利用了缓存,因此增加了很少的时间。

总而言之,保守和#34;编译器更新已经过时了很长时间。今天的编译器经过严格的测试,您应该使用最新的(或至少是最近的一些)版本进行日常开发。

对于一个让你受到长期修复错误的网上法官来说,这是非常残忍的。