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指令。