问题描述
我正在尝试优化一种计算密集型算法,并遇到某些缓存问题.我有一个巨大的缓冲区,偶尔和随机编写,并且在应用程序的末尾只读一次.显然,写入缓冲区会产生许多缓存错过,除了污染后来再次需要进行计算所需的缓存.我试图使用非颞型移动仪器,但仍会发生缓存错过(由Valgrind报告,并由运行时测量支持).但是,为了进一步调查非时空的动作,我编写了一个小测试程序,您可以在下面看到.顺序访问,大型缓冲区,仅写作.
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <smmintrin.h> void tim(const char *name, void (*func)()) { struct timespec t1, t2; clock_gettime(CLOCK_REALTIME, &t1); func(); clock_gettime(CLOCK_REALTIME, &t2); printf("%s : %f s.\n", name, (t2.tv_sec - t1.tv_sec) + (float) (t2.tv_nsec - t1.tv_nsec) / 1000000000); } const int CACHE_LINE = 64; const int FACTOR = 1024; float *arr; int length; void func1() { for(int i = 0; i < length; i++) { arr[i] = 5.0f; } } void func2() { for(int i = 0; i < length; i += 4) { arr[i] = 5.0f; arr[i+1] = 5.0f; arr[i+2] = 5.0f; arr[i+3] = 5.0f; } } void func3() { __m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f); for(int i = 0; i < length; i += 4) { _mm_stream_ps(&arr[i], buf); } } void func4() { __m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f); for(int i = 0; i < length; i += 16) { _mm_stream_ps(&arr[i], buf); _mm_stream_ps(&arr[4], buf); _mm_stream_ps(&arr[8], buf); _mm_stream_ps(&arr[12], buf); } } int main() { length = CACHE_LINE * FACTOR * FACTOR; arr = malloc(length * sizeof(float)); tim("func1", func1); free(arr); arr = malloc(length * sizeof(float)); tim("func2", func2); free(arr); arr = malloc(length * sizeof(float)); tim("func3", func3); free(arr); arr = malloc(length * sizeof(float)); tim("func4", func4); free(arr); return 0; }
函数1是幼稚的方法,功能2使用循环展开.函数3使用MOVNTP,实际上至少在我检查-O0时,至少在组件中插入了MOVNTP.在功能4中,我试图立即发布多个MOVNTP指令,以帮助CPU完成其写入组合.我用gcc -g -lrt -std=gnu99 -OX -msse4.1 test.c编译了代码,其中X是[0..3]之一.结果很有趣,充其量是:
-O0 func1 : 0.407794 s. func2 : 0.320891 s. func3 : 0.161100 s. func4 : 0.401755 s. -O1 func1 : 0.194339 s. func2 : 0.182536 s. func3 : 0.101712 s. func4 : 0.383367 s. -O2 func1 : 0.108488 s. func2 : 0.088826 s. func3 : 0.101377 s. func4 : 0.384106 s. -O3 func1 : 0.078406 s. func2 : 0.084927 s. func3 : 0.102301 s. func4 : 0.383366 s.
您可以看到_mm_stream_ps比其他GCC优化程序时要快一点,但是当GCC优化打开时,其目的会显着失败. valgrind仍然报告很多缓存写作.
因此,问题是:即使我使用NTA流媒体说明,为什么(L1+LL)缓存失误仍会发生?为什么尤其是func4这么慢?有人可以解释/推测这里发生了什么?
推荐答案
- 可能,您的基准测量主要是内存分配性能,而不仅仅是写入性能.您的操作系统可能不是在malloc中分配内存页面,而是在第一次触摸中分配func*函数内部的内存页面. OS在分配大量内存后也可能会进行一些内存混合,因此在内存分配后执行的任何基准测试都可能不可靠.
- 您的代码具有 Aliasing 问题:编译器无法保证您的阵列指针在此过程中不会发生变化填充此数组,因此它必须始终从内存中加载arr值,而不是使用寄存器.这可能会使性能下降.避免混叠的最简单方法是将arr和length复制到本地变量,并仅使用局部变量填充数组.有许多众所周知的建议可以避免全球变量.混叠是原因之一.
- _mm_stream_ps如果数组由64个字节对齐,则效果更好.在您的代码中,不保证对齐(实际上,malloc将其对齐16个字节).此优化仅在短数组中引人注目.
- 完成_mm_stream_ps后,致电_mm_mfence是个好主意.这是正确性所需的,而不是表现.
其他推荐答案
不应该func4是这样:
void func4() { __m128 buf = _mm_setr_ps(5.0f, 5.0f, 5.0f, 5.0f); for(int i = 0; i < length; i += 16) { _mm_stream_ps(&arr[i], buf); _mm_stream_ps(&arr[i+4], buf); _mm_stream_ps(&arr[i+8], buf); _mm_stream_ps(&arr[i+12], buf); } }