Boojum是正确的 - 如果你的编译器有一个良好的优化器并且你已启用它。如果不是这种情况,或者你对数组的使用不是连续的且难以优化,使用数组偏移量可能会慢得多。
这里有一个例子。大约在1988年,我们在Mac II上实现了一个带有简单电传打字机接口的窗口。它由24行80个字符组成。当你从股票中获得新的一行时,你将前23行向上滚动,并在底部显示新的一行。当电传打字机上有东西时(并不是一直有),它以300波特率进入,加上串行协议开销,大约每秒30个字符。所以我们谈论的并不是应该完全耗尽16 MHz 68020的东西!
但是写这篇文章的人却像这样写:
char screen[24][80];
并使用二维数组偏移量来滚动字符,就像这样:
int i, j;
for (i = 0; i < 23; i++)
for (j = 0; j < 80; j++)
screen[i][j] = screen[i+1][j];
这样六个窗口就把机器搞崩了!
为什么? 因为在那个时代编译器比较低能,所以在机器语言中,每个内部循环赋值的实例,screen[i][j] = screen[i+1][j]
,看起来有点像这样(Ax和Dx是CPU寄存器);
Fetch the base address of screen from memory into the A1 register
Fetch i from stack memory into the D1 register
Multiply D1 by a constant 80
Fetch j from stack memory and add it to D1
Add D1 to A1
Fetch the base address of screen from memory into the A2 register
Fetch i from stack memory into the D1 register
Add 1 to D1
Multiply D1 by a constant 80
Fetch j from stack memory and add it to D1
Add D1 to A2
Fetch the value from the memory address pointed to by A2 into D1
Store the value in D1 into the memory address pointed to by A1
所以,对于1840个内循环迭代,我们需要13个机器语言指令,总共需要23920个指令,其中包括3680个CPU密集型整数乘法。
我们对C源代码进行了一些更改,然后它看起来像这样:
int i, j;
register char *a, *b;
for (i = 0; i < 22; i++)
{
a = screen[i];
b = screen[i+1];
for (j = 0; j < 80; j++)
*a++ = *b++;
}
仍有两个机器语言乘法,但它们在外循环中,因此只有46个整数乘法而不是3680个。而内部循环的 *a++ = *b++
语句只包含两个机器语言操作。
Fetch the value from the memory address pointed to by A2 into D1, and post-increment A2
Store the value in D1 into the memory address pointed to by A1, and post-increment A1.
考虑到有1840个内部循环迭代,这总共需要3680个CPU廉价指令——少了6.5倍——而且没有整数乘法。在此之后,我们再也没有遇到过6个电传窗口的死机问题,因为我们先用完了电传数据源。还有更多的优化方法可以进一步提高效率。
现代编译器可以为你做这种优化——但是,只有当你要求它这样做,而且你的代码结构允许时才会这样。
但是,在某些情况下,编译器无法为您完成优化——例如,如果您在数组中执行非顺序操作。
因此,我发现尽可能使用指针而不是数组引用对我很有帮助。性能肯定不会更差,而且通常会更好。