精通安卓性能优化-第三章(四)

Tips

接下来是你可以在你的代码中做的几个事情,可以相对的达到更好的性能。

inline函数

因为函数调用可能是很昂贵的操作,inline函数(即,将函数调用替换为函数本身)可以使你的代码运行更快。使一个函数是inline函数很简单,只要在定义的时候添加"inline"关键字就可以了。Listing 3-30给出了inline函数的一个示例。
你需要小心的使用这个feature,因为它会导致臃肿的代码,否定指令高速缓存的优点。通常,inline对小的函数工作很好,即该调用的本身开销显著。

NOTE: 或者,使用宏

展开循环

优化循环的一个经典方式是展开他们,有时候部分展开。结果会有很到的变动,你需要有测量收益的经验,如果有的话。保证循环体不会变得太大,否则会对指令Cache造成负面影响。
Listing 3-34给出了展开循环的一个示例。

void add_buffers_unrolled (int* dst, const int* src, int size)
{
    int i;
    
    for (i=0;i<size/4;i++) {
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        // GCC not really good at that though... No LDM/STM generated
    }
    
    // leftovers
    if (size & 0x3) {
        switch (size & 0x3) {
            case 3: *dst++ += *src++;
            case 2: *dst++ += *src++;
            case 1:
            default: *dst += *src;
        }
    }
}

预加载内存

当你有一定的信心,具体的数据将被访问或者具体的指令将被执行,你可以在一些数据或者指令被使用之前预加载(或者预取)他们。

因为从外部存储将数据移动到缓存需要时间,给出足够的时间将数据从外部存储移动到缓存可以导致更好的性能,因为这将在最终访问数据或者指令的时候产生高速缓存命中。

预加载数据,你可以使用:
(1) GCC的_builtin_prefetch()
(2) PLD和PLDW ARM指令在汇编代码

你同样可以使用PLI ARM指令(ARMv7或者更高)去预加载指令。

有些CPU自动预加载内存,所以你不是经常可以看到收益。然而,因为你有一个更好的知识关于你的代码怎么访问数据,预加载数据可以产生很好的结果。

TIP: 你可以使用PLI ARM指令(ARMv7或者更高)去预加载指令。

Listing 3-35给出了怎么样利用预加载built-in函数的优势。

Listing 3-35 预加载内存

void add_buffers_unrolled_prefetcch (int* dst, const int* src, int size)
{
    int i;
    
    for (int i=0;i<size/8;i++) {
        __builtin_prefetch(dst+8, 1, 0); // 准备去写
        __builtin_prefetch(src+8, 0, 0); // 准备去读
        
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
        *dst++ += *src++;
    }
    
    // leftovers
    for (i=0;i<(size&0x7);i++) {
        *dst++ += *src++;
    }
}

你需要认真对待预加载内存,尽管有些情况下它会降低性能。移动到Cache的任何东西将会导致其他东西移除cache,很可能造成负面的性能影响。保证你预加载的东西很像被代码需要的,否则你将使用无用的数据污染缓存。

NOTE:ARM支持PLD、PLDW、和PLI指令,而x86支持PREFETCHT0、PREFETCHT1、PREFETCHT2和PREFETCHNTA指令。参考ARM和x86的文档。改变_builtin_prefetch()的最后一个参数,并且编译为x86去看哪个指令被使用到了。

LDM/STM而不是LDR/STD

使用一个LDM指令加载多个寄存器,比使用多个LDR指令加载寄存器要快。相似的,使用STM指令保存多个寄存器比使用多个STR指令要快。
编译器有能力产生这些指令(即使内存访问在你的代码是分散的),尝试去帮助编译器可以被编译器更简单的优化。比如,在Listing 3-36给出了编译器可以更加简单的识别产生LDM和STM指令(假设是ARM ABI)。理想的,访问内存最好被组织到一块,编译器可以产生更好的代码。

Listing 3-36 产生LDM和STM的模式

unsigned int a, b, c, d;

// 假设src和dst是指向int的指针

// 读取源值
a = *src++;
b = *src++;
c = *src++;
d = *src++;

// 在这里使用a, b, c,d做一些事情

// 把结果写到目标buffer
*dst++ = a;
*dst++ = b;
*dst++ = c;
*dst++ = d;

NOTE: 展开循环和inline函数同样可以帮助编译器更简单的产生LDM或者STM指令。

不幸的是,GCC编译器不总是可以产生LDM和STM指令。查看产生的汇编代码并且自己去写汇编代码,如果你认为使用LDM和STM指令将显著的提升性能。

Summary

Dennis Ritchie,C语言之父,说C有汇编的能量和方便。在本章可以看到有些情况下需要使用汇编代码达到目标。尽管汇编是一个非常强大的语言,提供一个是非模糊的机器兼容性,它可以使维护显著的困难,定义汇编语言一个指定的目标。然而,汇编代码或者built-in函数通常在你的应用中性能严格的地方找到,因此维护将会相对的简单。如果你认真的选择哪部分应用用Java实现,哪部分用C实现,哪部分用汇编实现,你可以保证你的应用的性能惊人并且打动你的使用者。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值