Tôi đang cố gắng tối ưu hóa thuật toán chuyên sâu tính toán và có một số vấn đề về bộ nhớ cache. Tôi có một bộ đệm rất lớn được viết đôi khi và một cách ngẫu nhiên và chỉ đọc một lần ở phần cuối của ứng dụng. Rõ ràng, việc ghi vào bộ đệm tạo ra rất nhiều bộ nhớ đệm và bên cạnh đó gây ô nhiễm các bộ nhớ đệm mà sau đó cần một lần nữa để tính toán. Tôi đã cố gắng sử dụng các công cụ di chuyển không theo thời gian, nhưng bộ nhớ cache bị thiếu (được báo cáo bằng valgrind và được hỗ trợ bởi các phép đo thời gian chạy) vẫn xảy ra. Tuy nhiên, để tiếp tục điều tra các động thái phi thời gian, tôi đã viết một chương trình thử nghiệm nhỏ mà bạn có thể xem bên dưới. Truy cập tuần tự, bộ đệm lớn, chỉ ghi.Tại sao _mm_stream_ps tạo bộ đệm ẩn L1/LL?
#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;
}
Chức năng 1 là cách tiếp cận ngây thơ, chức năng 2 sử dụng tính năng bỏ vòng lặp. Chức năng 3 sử dụng movntps, mà trong thực tế đã được lắp vào lắp ráp ít nhất là khi tôi kiểm tra cho -O0. Trong hàm 4, tôi đã cố gắng phát hành một số lệnh movntps cùng một lúc để giúp CPU ghi kết hợp của nó. Tôi đã biên dịch mã với gcc -g -lrt -std=gnu99 -OX -msse4.1 test.c
trong đó X
là một trong số [0,3]. Kết quả là .. thú vị để nói lúc tốt nhất:
-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.
Như bạn thấy _mm_stream_ps là một chút nhanh hơn so với những người khác khi chương trình không được tối ưu hóa bằng gcc nhưng sau đó không đáng kể mục đích của nó khi tối ưu hóa gcc được bật . Valgrind vẫn báo cáo rất nhiều ghi nhớ cache.
Vì vậy, câu hỏi đặt ra là: Tại sao bộ nhớ cache (L1 + LL) nhớ vẫn xảy ra ngay cả khi tôi đang sử dụng hướng dẫn phát trực tuyến NTA? Tại sao đặc biệt func4 quá chậm ?! Ai đó có thể giải thích/suy đoán những gì đang xảy ra ở đây?
Nếu bạn đang biên dịch với tối ưu hóa được bật, bạn sẽ cần phải nhìn vào hội đồng để thực sự biết những gì đang xảy ra. – RussS
Tôi đang xem hội đồng, mà btw ngày càng khó đọc hơn với mỗi mức tối ưu hóa, nhưng nó không cho tôi biết tại sao gợi ý phi thời gian bị bỏ qua. Ít nhất tôi đoán nó được bỏ qua như valgrind vẫn báo cáo bộ nhớ cache nhớ nơi tôi mong đợi không. Dù sao, tôi biết câu hỏi là khá không đặc biệt, vì vậy tôi sẽ thực sự đánh giá cao bất kỳ đầu vào về những gì có thể xảy ra ở đây. –