我正在尝试优化计算密集型算法,但遇到了一些缓存问题。我有一个巨大的缓冲区,它偶尔会随机写入,并且在应用程序结束时只读取一次。显然,写入缓冲区会产生大量缓存未命中,并且还会污染随后再次需要进行计算的缓存。我尝试使用非时间移动内在函数,但缓存未命中(由 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 使用 movntps,实际上至少在我检查 -O0 时它已插入到程序集中。在函数 4 中,我尝试同时发出多个 movntps 指令来帮助 CPU 进行写组合。我编译了代码gcc -g -lrt -std=gnu99 -OX -msse4.1 test.c
where 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.
正如您所看到的,当程序未经过 gcc 优化时,_mm_stream_ps 比其他程序要快一些,但当 gcc 优化打开时,_mm_stream_ps 明显无法达到其目的。 Valgrind 仍然报告大量缓存写入未命中。
因此,问题是:为什么即使我使用 NTA 流指令,那些 (L1+LL) 缓存未命中仍然会发生?为什么特别是 func4 这么慢?!有人可以解释/推测这里发生了什么吗?