C++ 遍历数组的简单任务。以下哪种解决方案最有效?

C++ 遍历数组的简单任务。以下哪种解决方案最有效?,c++,c,x86,C++,C,X86,最近,我一直在思考迭代数组的所有方法,并想知道其中哪种方法效率最高,哪种效率最低。我写了一个假设问题和五个可能的解决方案 问题 给定元素数为len的int数组arr,将任意数42分配给每个元素的最有效方法是什么 解决方案0:显而易见 解决方案2:地址和迭代器 解决方案4:解决疯狂问题 猜想 显而易见的解决方案几乎总是被使用,但我想知道下标运算符是否会产生乘法指令,就好像它被写成*arr+I*sizeofint=42一样 相反的解决方案试图利用将i与0(而不是len)进行比较的方式来减轻减法运算。

最近,我一直在思考迭代数组的所有方法,并想知道其中哪种方法效率最高,哪种效率最低。我写了一个假设问题和五个可能的解决方案

问题

给定元素数为len的int数组arr,将任意数42分配给每个元素的最有效方法是什么

解决方案0:显而易见

解决方案2:地址和迭代器

解决方案4:解决疯狂问题

猜想

显而易见的解决方案几乎总是被使用,但我想知道下标运算符是否会产生乘法指令,就好像它被写成*arr+I*sizeofint=42一样

相反的解决方案试图利用将i与0(而不是len)进行比较的方式来减轻减法运算。因此,我更喜欢解决方案3而不是解决方案2。此外,我还了解到,由于阵列存储在缓存中的方式,阵列被优化为可向前访问,这可能会给解决方案1带来问题

我不明白为什么解决方案4的效率会比解决方案2低。解决方案2增加地址和迭代器,而解决方案4只增加地址

最后,我不确定我更喜欢哪种解决方案。我想答案也会因编译器的目标体系结构和优化设置而有所不同

如果有的话,您更喜欢哪一种?

只需使用std::fill即可

在你提出的解决方案中,在一个好的编译器上,两者都不应该比其他的更快

只需使用std::fill


在你提出的解决方案中,在一个好的编译器上,两者都不应该比其他的更快

除了某些收集算法的某些big-O类型的东西外,ISO标准并没有规定代码中不同处理方式的效率,它只是规定了它的工作方式

除非你的数组有几十亿个元素,或者你想每分钟对它们进行数百万次的设置,否则使用哪种方法通常不会有丝毫的区别

如果您真的想知道并且我仍然认为这几乎肯定是不必要的,那么您应该在目标环境中对各种方法进行基准测试。测量,不要猜

至于我更喜欢哪一种,我的第一个倾向是优化可读性。只有当有一个特定的性能问题时,我才会考虑其他的可能性。这很简单,就像:

for (size_t idx = 0; idx < len; idx++)
    arr[idx] = 42;

ISO标准并不要求在代码中使用不同的方式来处理某些收集算法的某些大O类型的东西以外的事情,它只是要求它如何工作

除非你的数组有几十亿个元素,或者你想每分钟对它们进行数百万次的设置,否则使用哪种方法通常不会有丝毫的区别

如果您真的想知道并且我仍然认为这几乎肯定是不必要的,那么您应该在目标环境中对各种方法进行基准测试。测量,不要猜

至于我更喜欢哪一种,我的第一个倾向是优化可读性。只有当有一个特定的性能问题时,我才会考虑其他的可能性。这很简单,就像:

for (size_t idx = 0; idx < len; idx++)
    arr[idx] = 42;

我不认为性能是这里的一个问题——如果我能想象编译器为它们中的大多数生成相同的程序集的话,那么微优化几乎是不必要的

选择最具可读性的解决方案;标准库为您提供std::fill或更复杂的赋值

for(unsigned k = 0; k < len; ++k)
{
    // whatever
}

不要在没有任何必要的情况下试图混淆你的代码。

我不认为性能是一个问题——如果我能想象编译器会为大多数代码生成相同的程序集的话,那么微优化几乎是不必要的

选择最具可读性的解决方案;标准库为您提供std::fill或更复杂的赋值

for(unsigned k = 0; k < len; ++k)
{
    // whatever
}

只是不要在没有任何必要的情况下试图混淆代码。

对于几乎所有有意义的情况,编译器都会将所有建议的情况优化为相同的情况,并且不太可能产生任何影响

过去有一个技巧,如果你向后运行循环,你可以避免自动预取数据,这在一些奇怪的情况下实际上使它更有效。我记不起确切的情况,但我希望现代处理器能够识别向后循环和向前循环,以便自动预取

如果应用程序在大量元素上执行此操作非常重要,那么查看阻塞访问和使用非临时存储将是最有效的。但在您这样做之前,请确保您已将阵列的填充确定为一个重要的性能点,然后进行测量 当前代码和改进代码的配置


我可能会带着一些实际的基准来证明它在一点上没有什么区别,但我有一件事要做,以免一天中太晚了

对于几乎所有有意义的情况,编译器都会将所有建议的情况优化为相同的情况,而且不太可能有任何区别

过去有一个技巧,如果你向后运行循环,你可以避免自动预取数据,这在一些奇怪的情况下实际上使它更有效。我记不起确切的情况,但我希望现代处理器能够识别向后循环和向前循环,以便自动预取

如果应用程序在大量元素上执行此操作非常重要,那么查看阻塞访问和使用非临时存储将是最有效的。但在执行此操作之前,请确保已将阵列的填充确定为一个重要的性能点,然后对当前代码和改进的代码进行度量


我可能会带着一些实际的基准来证明它在一点上没有什么区别,但我有一件事要做,以免一天中太晚了

您是否尝试过对它们进行基准测试?如果您决定对它们进行基准测试,您也可以尝试使用memset。@Kiril,memset设置字符而不是整数,因此它在这里并不真正合适。unsigned i=len-1;i>=0-我希望在编译器上给你一个警告,因为它是一个永恒的循环。由于无符号值始终大于0,因此捕捉效果很好。至于解决方案4对2,约翰是对的。变量i只是作为循环控制变量,可以用arr上的指针算法替换。此外,由于缓存结构的原因,前向数组访问不一定更好。具有负步长的步长预取器可以向后预取缓存块。但是,对于存储指令(如MVCDid),前向访问通常由架构ISA进行优化。您是否尝试对它们进行基准测试?如果您决定对它们进行基准测试,您也可以尝试使用memset。@Kiril,memset设置字符而不是整数,因此它在这里并不真正合适。unsigned i=len-1;i>=0-我希望在编译器上给你一个警告,因为它是一个永恒的循环。由于无符号值始终大于0,因此捕捉效果很好。至于解决方案4对2,约翰是对的。变量i只是作为循环控制变量,可以用arr上的指针算法替换。此外,由于缓存结构的原因,前向数组访问不一定更好。具有负步长的步长预取器可以向后预取缓存块。然而,对于存储指令(如MVCHow),前向访问通常由ISA体系结构进行优化。该方法与问题中公布的方法相比如何?除了易用性之外的优点和缺点?@VivekS对于初学者来说,它是一个单行程序。这应该是足够的理由。就性能而言,应该没有区别。@VivekS Performance,因为它取决于标准库的优化级别。它可能会执行与手动循环非常相似的操作,但是从理论上讲,在这种情况下,实现可能会有一些手动优化的矢量化解决方案,这可能比任何合理的手动方法都要好。@Grizzly:这里的优化矢量化解决方案不仅仅是理论上的;gcc内嵌了一个矢量化的std::fill,就像我可以测试的两个编译器一样。这个方法与问题中的方法相比如何?除了易用性之外的优点和缺点?@VivekS对于初学者来说,它是一个单行程序。这应该是足够的理由。就性能而言,应该没有区别。@VivekS Performance,因为它取决于标准库的优化级别。它可能会执行与手动循环非常相似的操作,但是从理论上讲,在这种情况下,实现可能会有一些手动优化的矢量化解决方案,这可能比任何合理的手动方法都要好。@Grizzly:这里的优化矢量化解决方案不仅仅是理论上的;gcc内嵌了一个矢量化的std::fill,就像我可以测试的两个编译器一样。
int* end = arr + len;
for (; arr < end; ++arr)
    *arr = 42;
std::fill(arr, arr + len, 42);
for (size_t idx = 0; idx < len; idx++)
    arr[idx] = 42;
for(unsigned k = 0; k < len; ++k)
{
    // whatever
}
for(auto & elem : arr)
{
    // whatever
}