Tôi đã tự hỏi liệu có ai đó có thể chỉ cho tôi cách sử dụng vòng lặp/ngăn chặn vòng lặp cho phép nhân ma trận dày đặc lớn một cách hiệu quả hay không. Tôi đang làm C = AB với ma trận 1000x1000. Tôi đã làm theo ví dụ trên Wikipedia cho tiling vòng lặp nhưng tôi nhận được kết quả tồi tệ hơn bằng cách sử dụng ốp lát hơn là không có.loop tiling/blocking cho phép nhân số lớn dày đặc
http://en.wikipedia.org/wiki/Loop_tiling
tôi đã cung cấp một số mã dưới đây. Phương pháp ngây thơ là rất chậm do nhớ cache. Phương pháp chuyển vị tạo ra việc chuyển đổi B vào bộ đệm. Phương pháp này cho kết quả nhanh nhất (phép nhân ma trận đi như O (n^3) và transpose là O (n^2) do đó làm transpose là ít nhất 1000x nhanh hơn). Phương pháp wiki mà không chặn cũng nhanh và không cần bộ đệm. Phương pháp chặn chậm hơn. Một vấn đề khác với việc chặn là nó phải cập nhật khối nhiều lần. Đây là một thách thức đối với luồng/OpenMP vì nó có thể gây ra các điều kiện chủng tộc nếu không cẩn thận.
Tôi nên chỉ ra rằng bằng cách sử dụng AVX với một sửa đổi phương pháp chuyển vị, tôi nhận được kết quả nhanh hơn Eigen. Tuy nhiên, kết quả của tôi với SSE là một chút chậm hơn so với Eigen vì vậy tôi nghĩ rằng tôi có thể sử dụng bộ nhớ đệm tốt hơn.
Chỉnh sửa: Tôi nghĩ mình có ý tưởng mình muốn làm. Nó xuất phát từ thuật toán Cannon từ 1969.
http://en.wikipedia.org/wiki/Matrix_multiplication#Communication-avoiding_and_distributed_algorithms
tôi cần phải làm ma trận khối nhân và có mỗi thread xử lý một tiểu ma trận của C hơn Một và B . Ví dụ nếu tôi chia ma trận thành bốn khối. Tôi sẽ làm:
[C1 C2 [A1 A2 [B1 B2
C3 C4] = A3 A4] x B3 B4]
thread 1: C1 = A1B1 + A2B3
thread 2: C2 = A1B2 + A2B4
thread 3: C3 = A3B1 + A4B3
thread 4: C4 = A3B2 + A4B4
Điều đó loại bỏ điều kiện chủng tộc. Tôi sẽ phải suy nghĩ về điều này.
void matrix_mult_naive(const float*A , const float* B, float* C, const int N, const int M, const int K) {
#pragma omp parallel for
for(int i=0; i<N; i++) {
for(int j=0; j<K; j++) {
float tmp = 0;
for(int l=0; l<M; l++) {
tmp += A[M*i+l]*B[K*l+j];
}
C[K*i + j] = tmp;
}
}
}
void matrix_mult_transpose(const float*A , const float* B, float* C, const int N, const int M, const int K) {
float *B2 = (float*)aligned_malloc(M*K*sizeof(float), 32);
transpose(B, B2, M, K, 1);
#pragma omp parallel for
for(int i=0; i<N; i++) {
for(int j=0; j<K; j++) {
float tmp = 0;
for(int l=0; l<M; l++) {
tmp += A[M*i+l]*B2[M*j+l];
}
C[K*i + j] = tmp;
}
}
aligned_free(B2);
}
void matrix_mult_wiki(const float*A , const float* B, float* C, const int N, const int M, const int K) {
for(int i=0; i<N; i++) {
for(int j=0; j<K; j++) {
C[K*i + j] = 0;
}
}
#pragma omp parallel for
for(int i=0; i<N; i++) {
for(int l=0; l<M; l++) {
float a = A[M*i+l];
for(int j=0; j<K; j++) {
C[K*i + j] += a*B[K*l+j];
}
}
}
}
void matrix_mult_wiki_block(const float*A , const float* B, float* C, const int N, const int M, const int K) {
const int block_size = 8; //I have tried several different block sizes
for(int i=0; i<N; i++) {
for(int j=0; j<K; j++) {
C[K*i + j] = 0;
}
}
for(int l2=0; l2<M; l2+=block_size) {
for(int j2=0; j2<K; j2+=block_size) {
#pragma omp parallel for
for(int i=0; i<N; i++) {
for(int l=l2; l<min(M, l2+block_size); l++) {
for(int j=j2; j<min(K, j2+block_size); j++) {
C[K*i + j] += A[M*i+l]*B[K*l+j];
}
}
}
}
}
}
Bạn muốn song song để đi vòng vòng ngoài (gạch), chứ không phải bên trong chúng. Ý tưởng là cho mỗi lõi để có thể làm phép nhân gạch của nó trong bộ nhớ cache cục bộ nhanh, và đối với một số lõi để có thể làm điều đó cùng một lúc. –
Điều đó tạo ra một điều kiện chủng tộc. C [K * i + j] đang được ghi nhiều lần. –
Ý tôi là ví dụ, đối với i = 0, j = 0 C [0] được ghi thành nhiều lần trong phương thức khối. –