指针和数组之间的效率(较少的汇编指令不会花费更少的时间)

aja*_*xhe 6 c assembly visual-studio-2010

有些人说:"任何可以通过数组下标实现的操作也可以通过指针来完成.指针版本通常会更快".

我怀疑上面的结果,所以我做了以下测试:

在下面的文章中,我们不关心编译器优化.关于编译器优化如何影响指针和数组之间的效率,请注意:效率:数组与指针

(Visual Studio 2010,调试模式,无优化)

#include <windows.h>
#include <stdio.h>

int main()
{
    int a[] = {10,20,30};
    int* ap = a;

    long counter;

    int start_time, end_time;
    int index;

    start_time = GetTickCount();
    for (counter = 1000000000L; counter>0; counter--)
    {
        *(ap+1) = 100;
    }
    end_time = GetTickCount();
    printf("10 billion times of *ap = %d\n", end_time-start_time);

    start_time = GetTickCount();
    for (counter = 1000000000L; counter>0; counter--)
    {
        a[1] = 101;
    }
    end_time = GetTickCount();
    printf("10 billion times of a[0] = %d\n", end_time-start_time);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

结果是:

10 billion times of *ap = 3276
10 billion times of a[0] = 3541
Run Code Online (Sandbox Code Playgroud)

指针似乎有点快.但是在我对比拆解后,我陷入了更深层次的困惑.

(Visual Studio 2010,调试模式,无优化)

; 17   :         *(ap+1) = 100;
mov eax, DWORD PTR _ap$[ebp]
mov DWORD PTR [eax+4], 100          ; 00000064H

; 25   :         a[1] = 101;
mov DWORD PTR _a$[ebp+4], 101       ; 00000065H
Run Code Online (Sandbox Code Playgroud)

从汇编输出,通过指针的存储器访问需要2条指令,而数组只需要1条指令.

为什么数组执行较少的指令,但它不比指针花费更少的时间?

它与cpu缓存有关吗?如何修改我的测试代码来证明它?

aut*_*tic 2

首先也是最重要的是,C 语言没有速度。这是 C 的实现引入的一个属性。例如,C 没有速度,但 GCC 编译器生成的代码可能与 Clang 编译器生成的代码在速度上有所不同,并且它们都可能生成的代码的性能优于 Cint 或 Ch 解释器生成的行为。所有这些都是 C 实现。其中一些比其他的慢,但是速度无论如何不能归因于C

\n\n

C标准的6.3.2.1说:

\n\n
\n

除非它是 sizeof 运算符、_Alignof\n 运算符或一元 & 运算符的操作数,或者是用于\n 初始化数组的字符串文字、类型为 \xe2\x80\x98\xe2\ 的表达式\xe2\x80\x99\xe2\x80\x99 类型的 x80\x98 数组\n 转换为 \n 类型为 \xe2\x80\x98\xe2\x80\x98 的表达式,指向类型\xe2\x80\x99\xe2\x80 的指针\x99 指向数组对象的初始元素,并且不是左值。

\n
\n\n

这应该表明您的代码中的 和 都是*(ap+1)指针操作。此转换将在 Visual Studio 中的编译阶段进行。因此,这不会对运行时产生任何影响。a[1]

\n\n

6.5.2.1 关于“数组下标”说:

\n\n
\n

其中一个表达式应具有类型 \xe2\x80\x98\xe2\x80\x98pointer 来完成对象\n 类型\xe2\x80\x99\xe2\x80\x99,另一个表达式应具有整数类型,结果\ n 的类型为 \xe2\x80\x98\xe2\x80\x98type\xe2\x80\x99\xe2\x80\x99。这似乎表明数组下标\n运算符实际上是一个指针运算符......

\n
\n\n

正如我们之前假设的那样,这确认了ap[1]确实是指针操作。然而,在运行时,数组已经被转换为指针。性能应该是相同的。

\n\n

...那么,为什么它们不相同?

\n\n

您正在使用的操作系统有哪些特点?它不是一个多任务、多用户操作系统吗?假设操作系统要不间断地完成第一个循环,但随后中断第二个循环并将控制权切换到不同的进程。这种中断不会导致你的实验无效吗?如何衡量任务切换造成的中断的频率和时间?请注意,这对于不同的操作系统会有所不同,并且操作系统是实现的一部分。

\n\n

您使用的 CPU 有哪些特性?它有自己的快速机器代码内部缓存吗?假设您的整个第一个循环及其包含的计时机制很好地适合代码缓存,但第二个循环被截断了。这不会导致缓存未命中,并且在 CPU 从 RAM 中获取剩余代码时需要长时间等待吗?如何衡量缓存未命中造成的中断时间?请注意,这对于不同的 CPU 会有所不同,并且 CPU 是实现的一部分。

\n\n

这些问题应该提出一些问题,例如“这个微优化基准是否解决了有意义或重要的问题?”。优化的成功与否取决于问题的规模和复杂性。找到一个重要问题,解决它,分析解决方案,优化它并再次分析它。这样,您就可以提供有关优化版本快了多少的有意义的信息。正如我之前提到的,只要您不透露优化可能只与您的实施相关,您的老板会对您感到更加满意。我确信您会发现您最不用担心的是数组取消引用与指针取消引用

\n