我一直读到,在C语言中,使用指针算法通常比订阅数组访问更快.即使使用现代(据说是优化的)编译器,这也是真的吗?

如果是这样的话,当我开始从学习C转向学习Objective-C和Mac上的Cocoa时,情况仍然如此吗?

在C和Objective-C中,哪种是数组访问的首选编码样式?(由各自语言的专业人士)认为哪个更易读、更"正确"(因为没有更好的术语)?

推荐答案

你需要理解这种说法背后的原因.你有没有问过自己为什么它更快?让我们比较一些代码:

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]);
}

它们都是零,真是令人惊讶:-问题是,在低级机器代码中,a[i]到底意味着什么?这意味着

  1. 以内存中a的地址为例.

  2. 向该地址添加i倍于a的单个项目的大小(int通常是四个字节).

  3. 从该地址获取值.

因此,每次从a中提取一个值时,a的基址就会被加到i乘以4的结果中.如果只是取消引用指针,请执行步骤1.二,.不需要执行,只需执行步骤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++"与第一步不同.第二步."b++"将使指针增加4.

(important for newbies: run ++分)

好吧,但为什么会更快呢?因为给指针加四比把i乘以四再加上指针要快.这两种情况都有加法运算,但在第二种情况下,没有乘法运算(避免了一次乘法所需的CPU时间).考虑到现代CPU的速度,即使数组是1百万个元素,我想知道您是否真的可以对差异进行基准测试.

通过查看编译器生成的程序集输出,可以判断现代编译器是否可以将其中任何一个优化为同样快的速度.您可以通过将"-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),这也使他们能够更好地优化程序流以提高内部速度.事实上,第一条单指令和下面的另外两条指令可能会产生same set of sub-instructions,在这种情况下,没有任何可测量的速度差.

关于Objective-C,它只是带有扩展的C.因此,在指针和数组方面,对C适用的一切对Objective-C也适用.如果另一方面使用对象(例如,NSArrayNSMutableArray),则这是一种完全不同的野兽.但是,在这种情况下,无论如何都必须使用方法访问这些数组,没有指针/数组访问可供 Select .

Objective-c相关问答推荐

日期更改时的 UIDatePicker

如何以编程方式获取iphone的IP地址

TWTweetComposeViewController 在 IOS6 中已弃用

如何在运行时向对象添加属性?

使用 arc4random() 时如何 Select 值的范围

Objective C for循环中断并继续

AppDelegate、rootViewController 和 presentViewController

定义缓存变量时在objective-c中使用static关键字

如何以编程方式暂停 NSTimer?

NSMutableArray 判断对象是否已经存在

[Facebook-iOS-SDK 4.0]如何从FBSDKProfile获取用户邮箱

Interface Builder 中的 IBOutletCollection 集排序

避免NSArray 在被枚举时发生Mutations

如何判断字符串是否仅包含目标C中的字母数字字符?

用于 iOS 开发的 LLVM 与 GCC

自定义 UINavigationBar 背景

代码问题:格式字符串不是字符串文字

遍历 NSString 中所有字符的最有效方法

为什么 LLDB 不能打印 view.bounds?

围绕其中心旋转 UIView 保持其大小