在VC2005以上版本(包括2005)有非常丰富的针对x86架构处理器指令集的内建函数,包括典型的BFS、RDTSC、以及MMX、3DNOW!(+)、SSE、SSE2。到VC2008以后又支持了SSE3、SSSE3、以及SSE4A、SSE4.1和SSE4.2。
然而,当这些搬到GCC上时用起来就显得十分麻烦。下面我就先举一个VC2008上的例子:
#include <stdio.h> #include <mmintrin.h> #include <emmintrin.h> int main(void) { __m64 m64Value1 = _mm_cvtsi32_si64(0x302077fe); // MOVD __m64 m64Value2 = _mm_cvtsi32_si64(0x10203040); // MOVD m64Value1 = _mm_insert_pi16(m64Value1, 0xab, 3); m64Value2 = _mm_add_pi8(m64Value2, m64Value1); m64Value2 = _mm_packs_pu16(m64Value2, m64Value1); printf("The value is 0x%x/n", _mm_cvtsi64_si32(m64Value2)); _mm_empty(); return 0; }
还是相同的代码,放到GCC上看看:
#include <stdio.h> typedef char __attribute__((vector_size(8))) v8qi; typedef short __attribute__((vector_size(8))) v4hi; typedef int __attribute__((vector_size(8))) v2si; int main() { v8qi m64Value1 = { 0xfe, 0x77, 0x20, 0x30 }; v8qi m64Value2 = { 0x40, 0x30, 0x20, 0x10 }; m64Value1 = (v8qi)__builtin_ia32_pinsrw((v4hi)m64Value1, 0xab, 3); m64Value2 = __builtin_ia32_paddb(m64Value2, m64Value1); m64Value2 = __builtin_ia32_packuswb((v4hi)m64Value2, (v4hi)m64Value1); //这里目前实在没办法使用MOVD reg32, regMMX的方式 printf("The value is: 0x%x/n", *(unsigned*)&m64Value2); return 0; }
上述两段代码的输出结果均为0xab00ff00。
VC代码没什么可讲,这里主要谈一下GCC的代码。
首先,最上面的三个类型定义是定义向量数据类型。v8qi表示向量有8个单元,并且每个单元都占用1个字节。这里比较奇怪的是这些基本类型的定义GCC没有默认支持也没有放在某个特定的头文件中。这样要去一个个定义也是比较麻烦的。
然后,由于很多操作所基于的单元大小是不同的,比如说PADDB的每个单元是1个字节,而PADDW就是2个字节,因此在要做其它与变量定义不同的单元个数的操作时需要进行类型转换。可以看到上面有很多类型转换。
另外还有一点比较讨厌的是GCC没有提供MOVD这样的基本操作,使得基本类型与向量类型直接的转换也比较麻烦。这里的做法是直接通过访问变量地址,这样做会带来性能上的降低。不过目前也没有其它什么好的办法。
不过幸好GCC的内联汇编功能很强,我对于GCC在这方面的做法还是非常看好的。所以本人基于GCC的内联汇编的便利性以及灵活性自己定义了一套常用的MMX以及SSE的指令访问接口: