【问题标题】:Local variable vs. array access局部变量与数组访问
【发布时间】:2013-10-15 04:02:52
【问题描述】:

其中哪一个在计算上更高效,为什么?

A) 重复数组访问:

for(i=0; i<numbers.length; i++) {
    result[i] = numbers[i] * numbers[i] * numbers[i];
}

B) 设置局部变量:

for(i=0; i<numbers.length; i++) {
    int n = numbers[i];
    result[i] = n * n * n;
}

是否必须计算重复的数组访问版本(使用指针算法),从而使第一个选项变慢,因为它正在这样做?:

for(i=0; i<numbers.length; i++) {
    result[i] = *(numbers + i) * *(numbers + i) * *(numbers + i);
}

【问题讨论】:

  • 您可以使用 Timer 对象运行代码吗?看看结果是什么会很有趣。我提到这一点是因为您已经对其进行了编码:)
  • 我想编译器会生成同样的指令。
  • @Michael 你是说好像每个平台上的每个编译器都表现相同。
  • 与所有基准测试问题一样,可靠确定答案的唯一方法是在目标环境中对性能进行基准测试。这在很大程度上取决于您的编译器优化的程度、您的目标架构是否具有支持此类操作的特殊指令以及可能的其他因素。
  • 访问numbers.length还使用订阅操作符让我觉得c标签实际上是不合适的。

标签: c++ c performance pointers optimization


【解决方案1】:

任何足够复杂的编译器都会为所有三种解决方案生成相同的代码。我把你的三个版本变成了一个小的 C 程序(稍作调整,我将访问 numbers.length 更改为一个给出数组长度的宏调用):

#include <stddef.h>

size_t i;
static const int numbers[] = { 0, 1, 2, 4, 5, 6, 7, 8, 9 };

#define ARRAYLEN(x) (sizeof((x)) / sizeof(*(x)))
static int result[ARRAYLEN(numbers)];

void versionA(void)
{
    for(i=0; i<ARRAYLEN(numbers); i++) {
        result[i] = numbers[i] * numbers[i] * numbers[i];
    }
}

void versionB(void)
{
    for(i=0; i<ARRAYLEN(numbers); i++) {
        int n = numbers[i];
        result[i] = n * n * n;
    }
}

void versionC(void)
{
    for(i=0; i<ARRAYLEN(numbers); i++) {
            result[i] = *(numbers + i) * *(numbers + i) * *(numbers + i);
    }
}

然后我使用 Visual Studio 2012 使用优化(和调试符号,以便更漂亮的反汇编)编译它:

C:\Temp>cl /Zi /O2 /Wall /c so19244189.c
Microsoft (R) C/C++ Optimizing Compiler Version 17.00.50727.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

so19244189.c

最后,这是反汇编:

C:\Temp>dumpbin /disasm so19244189.obj

[..]

_versionA:
  00000000: 33 C0              xor         eax,eax
  00000002: 8B 0C 85 00 00 00  mov         ecx,dword ptr _numbers[eax*4]
            00
  00000009: 8B D1              mov         edx,ecx
  0000000B: 0F AF D1           imul        edx,ecx
  0000000E: 0F AF D1           imul        edx,ecx
  00000011: 89 14 85 00 00 00  mov         dword ptr _result[eax*4],edx
            00
  00000018: 40                 inc         eax
  00000019: 83 F8 09           cmp         eax,9
  0000001C: 72 E4              jb          00000002
  0000001E: A3 00 00 00 00     mov         dword ptr [_i],eax
  00000023: C3                 ret

_versionB:
  00000000: 33 C0              xor         eax,eax
  00000002: 8B 0C 85 00 00 00  mov         ecx,dword ptr _numbers[eax*4]
            00
  00000009: 8B D1              mov         edx,ecx
  0000000B: 0F AF D1           imul        edx,ecx
  0000000E: 0F AF D1           imul        edx,ecx
  00000011: 89 14 85 00 00 00  mov         dword ptr _result[eax*4],edx
            00
  00000018: 40                 inc         eax
  00000019: 83 F8 09           cmp         eax,9
  0000001C: 72 E4              jb          00000002
  0000001E: A3 00 00 00 00     mov         dword ptr [_i],eax
  00000023: C3                 ret

_versionC:
  00000000: 33 C0              xor         eax,eax
  00000002: 8B 0C 85 00 00 00  mov         ecx,dword ptr _numbers[eax*4]
            00
  00000009: 8B D1              mov         edx,ecx
  0000000B: 0F AF D1           imul        edx,ecx
  0000000E: 0F AF D1           imul        edx,ecx
  00000011: 89 14 85 00 00 00  mov         dword ptr _result[eax*4],edx
            00
  00000018: 40                 inc         eax
  00000019: 83 F8 09           cmp         eax,9
  0000001C: 72 E4              jb          00000002
  0000001E: A3 00 00 00 00     mov         dword ptr [_i],eax
  00000023: C3                 ret

请注意组装在所有情况下都是完全相同的。所以你的问题的正确答案

其中哪一个在计算上更高效,为什么?

对于这个编译器是:mu。您的问题无法回答,因为它基于不正确的假设。没有一个答案比任何其他答案都快。

【讨论】:

  • 谢谢你..很高兴看到这个程序集
【解决方案2】:

理论答案:

一个相当好的优化编译器应该将版本 A 转换为版本 B,并且只执行一次内存加载。如果启用优化,应该没有性能差异。

如果禁用优化,版本 A 会更慢,因为地址必须计算 3 次,并且有 3 次内存加载(其中 2 次被缓存并且非常快,但仍然比重用寄存器慢)。

实际上,答案取决于您的编译器,您应该通过基准测试来检查这一点。

【讨论】:

    【解决方案3】:

    这取决于编译器,但它们都应该相同。

    首先让我们看一下B 的情况,智能编译器将生成代码以仅将值加载到寄存器中一次,因此无论您是否使用其他变量都没有关系,编译器会为mov 指令生成操作码,并将值放入登记。所以BA 是一样的。

    现在让我们比较AC。我们应该看看运营商[] 内联实现。 a[b] 实际上是 *(a + b) 所以 *(numbers + i)numbers[i] 相同,这意味着案例 AC 是相同的。

    所以我们有(A==B) &amp;&amp; (A==C) 总而言之(A==B==C) 如果你明白我的意思:)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-04
      • 2011-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-19
      • 2020-07-30
      • 2011-03-14
      相关资源
      最近更新 更多