IOS/iPad/iPhone的最大速度是多少?

11

我使用OpenCV开发了一个计算密集型的iOS应用程序。当然速度很慢。但是相对于我的个人电脑原型,它慢了200倍左右。因此,我进行了优化。从一开始的15秒中,我成功地将速度提高到了0.4秒。我在想是否已经找到了所有可以优化的点,以及其他人可能想分享的内容。我所做的:

  1. OpenCV内部的"double"数据类型替换为"float"。Double是64位的,32位的CPU不能轻易处理它们,因此float给了我一些速度。OpenCV经常使用double。

  2. 将"-mpfu=neon"添加到编译器选项中。副作用是模拟器编译器不再工作,任何东西都只能在本机硬件上测试。

  3. 使用90个值的查找表替换sin()cos()实现。加速效果非常明显!这与PC相反,因为这样的优化并不能使PC更快。还有一段代码,它的计算单位是角度,而这个值必须转换成弧度才能使用sin()cos()。这段代码也被删除了。但是查找表起到了作用。

  4. 启用"thumb optimizations"。有些博客文章推荐恰恰相反,但这是因为thumb在armv6上通常会使事情变慢。而armv7没有任何问题,可以使事情更快,更小。

  5. 为了确保thumb优化和-mfpu=neon发挥最佳效果并且不会引起崩溃,我完全删除了armv6目标。我的所有代码都编译成armv7,这也被列为应用商店的要求。这意味着最低支持的iPhone将是3GS。我认为放弃旧设备是可以接受的。无论如何,旧设备的CPU速度较慢,如果在旧设备上安装CPU密集型的应用程序,会给用户带来糟糕的体验。

  6. 当然我使用-O3标志

  • 我从OpenCV中删除了"无用代码"。通常情况下,当我优化OpenCV时,我发现有些代码明显对我的项目而言是不必要的。例如,经常会有额外的"if()"语句来检查像素大小是8位还是32位,而我知道我只需要8位即可。这样可以减少一些代码,让优化器更有机会删除更多的代码或者将其替换为常量。同时也可以更好地适应缓存。

  • 还有其他的技巧和想法吗?对我而言,启用thumb模式并使用查找表代替三角函数是加速的关键,让我感到惊喜。也许你知道更多能够使应用程序飞快的方法?

  • 2个回答

    14
    如果你正在进行大量的浮点数计算,使用苹果的Accelerate框架将对你大有裨益。它被设计用于利用浮点硬件并行计算向量。
    我也会逐一解答你的问题: 1) 这不是因为CPU,而是因为从armv7时代开始,只有32位浮点操作会在浮点处理器硬件中计算(因为苹果替换了硬件)。64位操作将在软件中计算。作为交换,32位操作变得更快了。 2) NEON是新的浮点处理器指令集的名称。 3) 是的,这是一个众所周知的方法。另一种方法是使用上面提到的苹果框架。它提供sin和cos函数,可以同时计算4个值。算法经过汇编和NEON微调,可以在最小耗电下提供最大性能。 4) 新的armv7实现的thumb没有armv6的缺点。禁用推荐仅适用于v6。 5) 是的,考虑到80%的用户现在都在iOS 5.0或以上版本(armv6设备的支持在4.2.1结束),这对大多数情况来说是完全可以接受的。 6) 当你在发布模式下构建时,这会自动发生。 7) 是的,这不会像上述方法那样产生如此大的影响。
    我的建议是查看Accelerate。这样你就可以确保充分利用浮点处理器的全部功率。

    这个Accelerate对我来说是新的。它仍然有点难以使用,因为需要汇编级别的思考。但仍然是可能的,也许会尝试一下。我稍后会将其标记为已接受,因为我想看看我们是否能在这里获得更多有用的提示。 - Tõnu Samuel
    1
    在WWDC 2012视频中有一个完全涉及Accelerate框架的会话。你应该去看一下^^ - borrrden
    http://adcdownload.apple.com//wwdc_2012/wwdc_2012_session_pdfs/session_708__the_accelerate_framework.pdf 和 https://developer.apple.com/videos/wwdc/2012/#708 似乎是相关链接。 - Tõnu Samuel

    1

    我对之前的帖子提供一些反馈。这解释了我试图在第7点中提供有关死代码的一些想法。这意味着稍微更广泛的想法。我需要格式化,因此无法使用评论表单。这样的代码出现在OpenCV中:

    for( kk = 0; kk < (int)(descriptors->elem_size/sizeof(vec[0])); kk++ ) {
        vec[kk] = 0;
    }
    

    我想看看它在汇编中的样子。为了确保我能在汇编中找到它,我这样包装它:

    __asm__("#start");
    for( kk = 0; kk < (int)(descriptors->elem_size/sizeof(vec[0])); kk++ ) {
        vec[kk] = 0;
    }
    __asm__("#stop");
    

    现在我按下“产品->生成输出->装配文件”,然后我得到的是:
        @ InlineAsm Start
        #start
        @ InlineAsm End
    Ltmp1915:
        ldr r0, [sp, #84]
        movs    r1, #0
        ldr r0, [r0, #16]
        ldr r0, [r0, #28]
        cmp r0, #4
        mov r0, r4
        blo LBB14_71
    LBB14_70:
    Ltmp1916:
        ldr r3, [sp, #84]
        movs    r2, #0
    Ltmp1917:
        str r2, [r0], #4
        adds    r1, #1
    Ltmp1918:
    Ltmp1919:
        ldr r2, [r3, #16]
        ldr r2, [r2, #28]
        lsrs    r2, r2, #2
        cmp r2, r1
        bgt LBB14_70
    LBB14_71:
    Ltmp1920:
        add.w   r0, r4, #8
        @ InlineAsm Start
        #stop
        @ InlineAsm End
    

    很多代码。我使用printf打印了(int)(descriptors->elem_size/sizeof(vec[0]))的值,它总是64。所以我将其硬编码为64,并通过汇编器再次传递:
        @ InlineAsm Start
        #start
        @ InlineAsm End
    Ltmp1915:
        vldr.32 s16, LCPI14_7
        mov r0, r4
        movs    r1, #0
        mov.w   r2, #256
        blx _memset
        @ InlineAsm Start
        #stop
        @ InlineAsm End
    

    正如你现在所看到的,优化器已经得到了这个想法,代码变得更短了。它能够将其向量化。问题在于编译器并不总是知道输入是否为常量,如果这是像网络摄像头尺寸或像素深度之类的东西,但实际上在我的情况下它们通常是常量,我关心的只是速度。

    我还尝试了加速,按建议用三行替换:

    __asm__("#start");
    vDSP_vclr(vec,1,64);
    __asm__("#stop");
    

    汇编现在看起来是:

        @ InlineAsm Start
        #start
        @ InlineAsm End
    Ltmp1917:
        str r1, [r7, #-140]
    Ltmp1459:
    Ltmp1918:
        movs    r1, #1
        movs    r2, #64
        blx _vDSP_vclr
    Ltmp1460:
    Ltmp1919:
        add.w   r0, r4, #8
        @ InlineAsm Start
        #stop
        @ InlineAsm End
    

    不确定这是否比bzero更快。在我的情况下,这部分用时不多,两种变体似乎以相同的速度工作。
    我学到的另一件事是使用GPU。详细信息请参见http://www.sunsetlakesoftware.com/2012/02/12/introducing-gpuimage-framework

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接