这是我进行的一些测试的结果。我尝试了 3 个版本的代码,一个没有展开,一个有展开,一个使用 sse 扩展。由于您没有说明数组的类型,因此我使用了整数,顺序为 1..ARRAY_SIZE。
首先是设置的东西,emmintrin.h 是 sse 内在函数的头文件。
#include <stdio.h>
#include <stdlib.h>
#include <emmintrin.h> // SSE2
#include <time.h>
int main(){
const size_t ARRAY_SIZE = 9973;
const size_t N_TIMES = 200000;
int * array = malloc(ARRAY_SIZE*sizeof(*array));
for (size_t i=0;i<ARRAY_SIZE;++i) array[i] = i;
clock_t start, stop;
接下来是一个简单的循环。如果优化器很聪明,这应该和其他任何东西一样快,编译器知道您正在将相邻的数组元素相加,并且可以根据需要展开、使用 sse 等。
// Using normal loop
start = clock();
int sum = 0;
for (size_t i=0;i<N_TIMES;++i){
for (size_t j=0;j<ARRAY_SIZE;++j){
sum += array[j];
}
}
stop = clock();
printf("normal %d\t%e\n",sum,difftime(stop,start));
下一步比较你的循环
// Using unwrapped loop
start = clock();
sum = 0;
for (size_t i=0;i<N_TIMES;++i){
size_t j=0;
for (;j<ARRAY_SIZE-7;j+=8){
sum += array[j+0] + array[j+1] + array[j+2] +
array[j+3] + array[j+4] + array[j+5] +
array[j+6] + array[j+7];
}
for (;j<ARRAY_SIZE;++j){
sum += array[j];
}
}
stop = clock();
printf("unrolled %d\t%e\n",sum,difftime(stop,start));
最后是一个使用 sse 的循环。 __m128i 类型指定一个 128 位整数数组,必须使用 _mm 内在函数对其进行操作。此代码一次将数组 4 个整数加载到寄存器中,将其添加到求和寄存器,然后在最后解压缩求和寄存器。
// Using sse
start = clock();
sum = 0;
__m128i sse_sum = _mm_setzero_si128();
for (size_t i=0;i<N_TIMES;++i){
size_t j=0;
for (;j<ARRAY_SIZE-3;j+=4){
__m128i slice = _mm_load_si128((__m128i*)(array+j));
sse_sum = _mm_add_epi32(sse_sum,slice);
}
for (;j<ARRAY_SIZE;++j){
sum += array[j];
}
}
int sse_result[4];
_mm_store_si128((__m128i*)sse_result,sse_sum);
for (int i=0;i<4;++i)
sum += sse_result[i];
stop = clock();
printf("sse %d\t%e\n",sum,difftime(stop,start));
}
这是我可用的几个编译器的结果,它们都针对 64 位 OSX 10.6。第一列是循环版本,然后是总和,然后是所用时间:
$ gcc --version
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
$ gcc loop.c -o loop -msse2 -O3 -std=c99 -ftree-vectorize && ./loop
normal -2068657536 7.525430e+05
unrolled -2068657536 9.638840e+05
sse -2068657536 4.929820e+05
$ gcc-4.5 --version
gcc-4.5 (GCC) 4.5.0
$ gcc-4.5 loop.c -o loop -msse2 -O3 -std=c99 -ftree-vectorize && ./loop
normal -2068657536 9.721320e+05
unrolled -2068657536 9.610700e+05
sse -2068657536 1.196051e+06
$ clang --version
clang version 2.0 (trunk 103456)
$ clang loop.c -o loop -msse2 -O3 -std=c99 -ftree-vectorize && ./loop
normal -2068657536 1.155552e+06
unrolled -2068657536 9.613550e+05
sse -2068657536 1.195248e+06
如您所见,编译器的结果差异很大。在 gcc 中,正常循环看起来得到了很好的优化,性能比展开循环好,几乎与 Apple 编译器中的 sse 一样好,而对于基线 gcc,sse 版本落后了很多(可能是因为 Apple 设置了更好的默认值)编译器的优化)。 Clang 优化的不是很好,虽然我的版本有点旧,展开循环最适合这种情况。
在我看来,最好的方法是使用基本循环,前提是您的编译器执行自动矢量化(这就是 -ftree-vectorize 的用途),因为这是最容易理解的。与手动展开循环相比,您将从摆弄编译器优化级别中获得更多收益。