我很难用内联汇编来击败我的编译器.
什么是一个好的,非人为的函数示例,编译器很难制作真正,快速和简单的函数?但是使用内联汇编制作相对简单.
如果您不考虑SIMD操作作弊,您通常可以编写比编译器自动向量化能力更好的SIMD程序集(如果它甚至具有自动向量化!)
这是一个非常基本的SSE(x86的SIMD指令集之一)教程.它适用于Visual C++内联汇编.
编辑:如果您想亲自尝试,这里有一对小功能.它是n长度点积的计算.一种是在线使用SSE 2指令(GCC在线语法),另一种是非常基本的C.
它非常简单,如果一个好的编译器无法对简单的C循环进行矢量化,我会感到非常惊讶,但如果没有,你会看到SSE2中的速度加快.如果我使用更多的寄存器,SSE 2版本可能会更快但我不想延伸我非常弱的SSE技能:).
float dot_asm(float *a, float*b, int n) { float ans = 0; int i; // I'm not doing checking for size % 8 != 0 arrays. while( n > 0) { float tmp[4] __attribute__ ((aligned(16))); __asm__ __volatile__( "xorps %%xmm0, %%xmm0\n\t" "movups (%0), %%xmm1\n\t" "movups 16(%0), %%xmm2\n\t" "movups (%1), %%xmm3\n\t" "movups 16(%1), %%xmm4\n\t" "add $32,%0\n\t" "add $32,%1\n\t" "mulps %%xmm3, %%xmm1\n\t" "mulps %%xmm4, %%xmm2\n\t" "addps %%xmm2, %%xmm1\n\t" "addps %%xmm1, %%xmm0" :"+r" (a), "+r" (b) : :"xmm0", "xmm1", "xmm2", "xmm3", "xmm4"); __asm__ __volatile__( "movaps %%xmm0, %0" : "=m" (tmp) : :"xmm0", "memory" ); for(i = 0; i < 4; i++) { ans += tmp[i]; } n -= 8; } return ans; } float dot_c(float *a, float *b, int n) { float ans = 0; int i; for(i = 0;i < n; i++) { ans += a[i]*b[i]; } return ans; }
因为它与iPhone和汇编代码有关,所以我将给出一个与iPhone世界相关的例子(而不是某些sse或x86 asm).如果有人决定为某些真实世界的应用程序编写汇编代码,那么很可能这将是某种数字信号处理或图像处理.示例:转换RGB像素的颜色空间,将图像编码为jpeg/png格式,或将声音编码为mp3,amr或g729以用于voip应用程序.在声音编码的情况下,有许多例程无法由编译器转换为有效的asm代码,它们在C语言中没有等价物.声音处理中常用的东西的例子:饱和数学,乘法累加例程,矩阵乘法.
饱和加法示例:32位有符号整数的范围为:0x8000 0000 <= int32 <= 0x7fff ffff.如果添加两个整数,结果可能会溢出,但在某些情况下,这在数字信号处理中可能是不可接受的.基本上,如果结果溢出或下溢饱和,则应返回0x8000 0000或0x7fff ffff.这将是一个完整的c功能来检查.饱和添加的优化版本可以是:
int saturated_add(int a, int b) { int result = a + b; if (((a ^ b) & 0x80000000) == 0) { if ((result ^ a) & 0x80000000) { result = (a < 0) ? 0x80000000 : 0x7fffffff; } } return result; }
你也可以做多个if/else来检查溢出,或者在x86上你可以检查溢出标志(这也要求你使用asm).iPhone使用具有dsp asm的armv6或v7 cpu.因此,saturated_add
具有多个早午餐(if/else语句)和2个32位常量的函数可以是一个仅使用一个cpu循环的简单asm指令.因此,简单地使饱和_add使用asm指令可以使整个算法快两到三倍(并且尺寸更小).这是QADD手册:
QADD
通常在长循环中执行的代码的其他示例是
res1 = a + b1*c1; res2 = a + b2*c2; res3 = a + b3*c3;
似乎没有什么不能在这里优化,但在ARM cpu上你可以使用特定的dsp指令,这些指令比简单的乘法花费更少的周期!没错,带有特定指令的+ b*c可以比简单的a*b执行得更快.对于这种情况,编译器根本无法理解代码的逻辑并且不能直接使用这些dsp指令,这就是为什么你需要手动编写asm来优化代码,但是你应该只手动编写需要的代码部分代码.优化.如果你开始手动编写简单的循环,那么几乎可以肯定你不会打败编译器!网上有很多好的论文,用于内联汇编以编码fir过滤器,编码/解码等.
除非你是一个集合大师,否则击败编译器的可能性非常低.
来自上述链接的片段,
例如,面向位的"XOR%EAX,%EAX"指令是在x86的早期阶段将寄存器设置为零的最快方法,但大多数代码是由编译器和编译器很少生成的XOR指令生成的.因此,IA设计者决定将频繁出现的编译器生成的指令移到组合解码逻辑的前面,使得文字"MOVL $ 0,%EAX"指令比XOR指令执行得更快.
我使用通用的"strait C"实现实现了一个简单的互相关.然后,当它花费的时间超过我可用的时间片时,我采用了算法的显式并行化并使用处理器内在函数来强制在计算中使用特定指令.对于这种特殊情况,计算时间从> 30ms减少到略超过4ms.在下一次数据采集发生之前,我有一个15ms的窗口来完成处理.
这是VLWI处理器上的SIMD类型优化.这只需要4个左右的处理器内在函数,它们基本上是汇编语言指令,它们在源代码中提供函数调用的外观.您可以使用内联汇编执行相同的操作,但语法和寄存器管理对于处理器内在函数来说更好一些.
除此之外,如果尺寸重要,汇编程序是王道.我和一个用不到512字节编写全屏文本编辑器的人去了学校.
我有一个校验和算法,需要将字旋转一定的位数.为了实现它,我有这个宏:
//rotate word n right by b bits #define ROR16(n,b) (((n)>>(b))|(((n)<<(16-(b)))&0xFFFF)) //... and inside the inner loop: sum ^= ROR16(val, pos);
VisualStudio版本构建扩展到:( val
在ax中,pos
在dx中,sum
在bx中)
mov ecx,10h sub ecx,edx mov ebp,eax shl ebp,cl mov cx,dx sar ax,cl add esi,2 or bp,ax xor bx,bp
更有效的等效手工生成组件将是:
mov cl,dx ror ax,cl xor bx,ax
我还没弄明白如何ror
从纯'c'代码发出指令.但是......
在编写本文时,我记得编译器内在函数.我可以用以下内容生成第二组指令:
sum ^= _rotr16(val,pos);
所以我的答案是:即使你认为你可以击败纯c编译器,在使用内联汇编之前检查内在函数.