您需要了解此声明背后的原因。你有没有问过自己为什么它更快?让我们比较一些代码:
int i;
int a[20];
// Init all values to zero
memset(a, 0, sizeof(a));
for (i = 0; i < 20; i++) {
printf("Value of %d is %d\n", i, a[i]);
}
它们都为零,真是令人惊讶:-P 问题是,a[i] 实际上在低级机器代码中是什么意思?这意味着
-
在内存中取a的地址。
-
将i 乘以a 的单个项目的大小添加到该地址(int 通常为四个字节)。
-
从该地址获取值。
因此,每次从a 获取值时,a 的基地址都会与i 乘以四的结果相加。如果你只是取消引用一个指针,步骤 1. 和 2. 不需要执行,只需要执行步骤 3。
考虑下面的代码。
int i;
int a[20];
int * b;
memset(a, 0, sizeof(a));
b = a;
for (i = 0; i < 20; i++) {
printf("Value of %d is %d\n", i, *b);
b++;
}
这段代码可能更快...但即使是这样,差异也很小。为什么会更快? "*b" 与上面的第 3 步相同。但是“b++”和第1步和第2步是不一样的。“b++”会将指针增加4。
(对新手很重要:运行++
上一个指针不会增加
指针在内存中的一个字节!它会
将指针增加尽可能多的字节
在内存中,因为它指向的数据是
在尺寸方面。它指向 int 和
int 在我的机器上是四个字节,所以 b++
b 增加 4!)
好的,但为什么会更快呢?因为向指针添加四比将 i 乘以四并将其添加到指针要快。在任何一种情况下你都有一个加法,但在第二种情况下,你没有乘法(你避免了一次乘法所需的 CPU 时间)。考虑到现代 CPU 的速度,即使数组是 1 个 mio 元素,我想知道您是否真的可以对差异进行基准测试。
现代编译器可以将任何一个优化为同样快,您可以通过查看它产生的汇编输出来检查这一点。为此,您可以将“-S”选项(大写 S)传递给 GCC。
这是第一个 C 代码的代码(已使用优化级别 -Os,这意味着优化代码大小和速度,但不要进行会显着增加代码大小的速度优化,与 -O2 不同-O3):
_main:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
subl $108, %esp
call ___i686.get_pc_thunk.bx
"L00000000001$pb":
leal -104(%ebp), %eax
movl $80, 8(%esp)
movl $0, 4(%esp)
movl %eax, (%esp)
call L_memset$stub
xorl %esi, %esi
leal LC0-"L00000000001$pb"(%ebx), %edi
L2:
movl -104(%ebp,%esi,4), %eax
movl %eax, 8(%esp)
movl %esi, 4(%esp)
movl %edi, (%esp)
call L_printf$stub
addl $1, %esi
cmpl $20, %esi
jne L2
addl $108, %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
与第二个代码相同:
_main:
pushl %ebp
movl %esp, %ebp
pushl %edi
pushl %esi
pushl %ebx
subl $124, %esp
call ___i686.get_pc_thunk.bx
"L00000000001$pb":
leal -104(%ebp), %eax
movl %eax, -108(%ebp)
movl $80, 8(%esp)
movl $0, 4(%esp)
movl %eax, (%esp)
call L_memset$stub
xorl %esi, %esi
leal LC0-"L00000000001$pb"(%ebx), %edi
L2:
movl -108(%ebp), %edx
movl (%edx,%esi,4), %eax
movl %eax, 8(%esp)
movl %esi, 4(%esp)
movl %edi, (%esp)
call L_printf$stub
addl $1, %esi
cmpl $20, %esi
jne L2
addl $124, %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
嗯,这是不同的,这是肯定的。 104 和 108 的数字差异来自变量b(在第一个代码中,堆栈上少了一个变量,现在我们多了一个,改变了堆栈地址)。 for循环中真正的代码区别是
movl -104(%ebp,%esi,4), %eax
相比
movl -108(%ebp), %edx
movl (%edx,%esi,4), %eax
实际上,在我看来,第一种方法似乎更快(!),因为它发出一个 CPU 机器代码来执行所有工作(CPU 为我们完成所有工作),而不是有两个机器代码。另一方面,下面的两个汇编命令的运行时间可能比上面的要短。
作为结束语,我会说取决于您的编译器和 CPU 功能(CPU 提供什么命令以何种方式访问内存),结果可能是任何一种方式。任何一个都可能更快/更慢。除非您将自己完全限制在一个编译器(也意味着一个版本)和一个特定的 CPU 上,否则您无法确定。由于 CPU 可以在单个汇编命令中执行越来越多的操作(很久以前,编译器确实必须手动获取地址,将 i 乘以 4,然后在获取值之前将两者相加),这些陈述曾经是绝对正确的古往今来,如今越来越成问题。还有谁知道 CPU 在内部是如何工作的?上面我比较了一个汇编指令和另外两个。
我可以看到指令的数量不同,并且此类指令所需的时间也可能不同。此外,这些指令在它们的机器表示中需要多少内存(毕竟它们需要从内存传输到 CPU 缓存)是不同的。但是,现代 CPU 不会按照您提供指令的方式执行指令。他们将大指令(通常称为 CISC)拆分为小的子指令(通常称为 RISC),这也使他们能够更好地优化程序流程以提高内部速度。事实上,下面的第一条指令和另外两条指令可能会产生相同的子指令集,在这种情况下,无论如何都没有可测量的速度差异。
关于 Objective-C,它只是带有扩展的 C。因此,所有适用于 C 的东西都适用于 Objective-C,就指针和数组而言也是如此。另一方面,如果您使用对象(例如,NSArray 或NSMutableArray),这是一个完全不同的野兽。但是在这种情况下,无论如何您都必须使用方法访问这些数组,没有可供选择的指针/数组访问。