最近,我一直在思考一个人可以迭代数组的各种方式,并想知道哪些是最有效(和最低效)的。我写了一个假设的问题和五种可能的解决方案。
问题
如果int
数组arr
的元素数量为len
,那么为每个元素分配任意数42
的最有效方法是什么?
解决方案0:明显
for (unsigned i = 0; i < len; ++i)
arr[i] = 42;
解决方案1:反向显而易见
for (unsigned i = len - 1; i >= 0; --i)
arr[i] = 42;
解决方案2:地址和迭代器
for (unsigned i = 0; i < len; ++i)
{ *arr = 42;
++arr;
}
解决方案3:反向地址和迭代器
for (unsigned i = len; i; --i)
{ *arr = 42;
++arr;
}
解决方案4:解决疯狂问题
int* end = arr + len;
for (; arr < end; ++arr)
*arr = 42;
猜想
几乎总是使用明显的解决方案,但我想知道下标运算符是否会产生乘法指令,就像它被写成*(arr + i * sizeof(int)) = 42
一样。
反向解决方案尝试利用比较i
与0
而不是len
的方式来减轻减法操作。因此,我更喜欢解决方案3 而非解决方案2 。此外,我已经读过,数组已经过优化,可以向前访问,因为它们存储在缓存中,这可能会出现解决方案1 的问题。
我不明白为什么解决方案4 的效率低于解决方案2 。 解决方案2 增加地址和迭代器,而解决方案4 仅增加地址。
最后,我不确定我更喜欢哪种解决方案。我认为答案也会因编译器的目标架构和优化设置而异。
如果有的话,您更喜欢哪一种?
答案 0 :(得分:10)
只需使用std::fill
。
std::fill(arr, arr + len, 42);
在你提出的解决方案中,在一个好的编译器上,它们都不应该比其他的快。
答案 1 :(得分:6)
ISO标准没有规定代码中不同方式的效率(除了某些收集算法的某些大O类型的东西),它只是强制它如何运作。
除非您的阵列大小是数十亿个元素,或者您希望每分钟设置数百万次,否则通常不会对您使用哪种方法产生丝毫影响。
如果确实想要知道(并且我仍然维护它几乎肯定是不必要的),您应该对目标环境中的各种方法进行基准测试。 测量,不要猜测!
至于我更喜欢哪个,我的第一个倾向是优化可读性。只有在存在特定性能问题时,我才会考虑其他可能性。这就像是:
for (size_t idx = 0; idx < len; idx++)
arr[idx] = 42;
答案 2 :(得分:1)
我不认为性能在这里是一个问题 - 如果有的话(我可以想象编译器为大多数人生成相同的程序集),几乎不需要微观优化。
使用最易读的解决方案;标准库为您提供std::fill
或更复杂的作业
for(unsigned k = 0; k < len; ++k)
{
// whatever
}
所以对其他人来说很明显,你正在看你的代码。使用C ++ 11,你也可以
for(auto & elem : arr)
{
// whatever
}
只是不要试图在没有必要的情况下混淆你的代码。
答案 3 :(得分:0)
对于几乎所有有意义的情况,编译器都会优化所有建议的情况,并且不太可能产生任何差异。
曾经有一个技巧,你可以避免自动预取数据,如果你向后运行循环,这在一些奇怪的情况下实际上使它更有效。我不记得确切的情况,但我希望现代处理器能够识别向后循环以及转发循环以进行自动预取。
如果您的应用程序对大量元素执行此操作非常重要,那么查看阻止访问和使用非临时存储将是最有效的。但在此之前,请确保已将阵列的填充标识为重要的性能点,然后对当前代码和改进的代码进行测量。
我可能会带一些实际的基准测试来证明“它几乎没有什么区别”,但是我有一个差事要在它在当天太晚之前运行......