Cách nhanh nhất để chuyển ma trận trong C ++ là gì?


80

Tôi có một ma trận (tương đối lớn) mà tôi cần chuyển đổi. Ví dụ, giả sử rằng ma trận của tôi là

a b c d e f
g h i j k l
m n o p q r 

Tôi muốn kết quả như sau:

a g m
b h n
c I o
d j p
e k q
f l r

Cách nhanh nhất để làm điều này là gì?


2
Đó gọi là "chuyển vị". Xoay 90 độ là một quan niệm hoàn toàn khác.
Andy Prowl

34
Và cách nhanh nhất không phải là xoay nó mà chỉ cần hoán đổi thứ tự chỉ mục khi bạn truy cập vào mảng.
hiệu suất cao

2
Dù tốc độ nhanh đến đâu, bạn vẫn phải truy cập tất cả các phần tử của ma trận.
taocp

10
@HighPerformanceMark: Tôi đoán điều đó còn tùy thuộc vào, nếu sau đó bạn muốn truy cập ma trận lặp đi lặp lại theo thứ tự hàng, thì việc có một cờ "hoán vị" sẽ khiến bạn bị ảnh hưởng nặng nề.
Matthieu M.

3
Việc chuyển đổi ma trận nổi tiếng với những vấn đề mà nó gây ra với bộ nhớ đệm. Nếu mảng của bạn đủ lớn để hiệu suất của chuyển vị là đáng kể và bạn không thể tránh chuyển vị bằng cách chỉ cung cấp một giao diện với các chỉ số được hoán đổi, thì tùy chọn tốt nhất của bạn là sử dụng một quy trình thư viện hiện có để chuyển vị các ma trận lớn. Các chuyên gia đã thực hiện công việc này, và bạn nên sử dụng nó.
Eric Postpischil

Câu trả lời:


130

Đây là một câu hỏi hay. Có nhiều lý do mà bạn muốn thực sự chuyển ma trận trong bộ nhớ thay vì chỉ hoán đổi tọa độ, ví dụ như trong phép nhân ma trận và bôi bẩn Gauss.

Trước tiên, hãy để tôi liệt kê một trong những hàm tôi sử dụng cho phép chuyển vị ( CHỈNH SỬA: vui lòng xem phần cuối câu trả lời của tôi, nơi tôi tìm thấy một giải pháp nhanh hơn nhiều )

void transpose(float *src, float *dst, const int N, const int M) {
    #pragma omp parallel for
    for(int n = 0; n<N*M; n++) {
        int i = n/N;
        int j = n%N;
        dst[n] = src[M*j + i];
    }
}

Bây giờ chúng ta hãy xem tại sao chuyển vị lại hữu ích. Xét phép nhân ma trận C = A * B. Chúng tôi có thể làm theo cách này.

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;
    }
}

Bằng cách đó, tuy nhiên, sẽ có rất nhiều bộ nhớ cache bị bỏ sót. Một giải pháp nhanh hơn nhiều là chuyển vị trí của B trước

transpose(B);
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*j+l];
        }
        C[K*i + j] = tmp;
    }
}
transpose(B);

Phép nhân ma trận là O (n ^ 3) và phép chuyển vị là O (n ^ 2), vì vậy việc lấy phép chuyển vị sẽ ảnh hưởng không đáng kể đến thời gian tính toán (đối với lớn n). Trong phép nhân ma trận, việc xếp gạch vòng lặp thậm chí còn hiệu quả hơn việc lấy phép chuyển vị nhưng điều đó phức tạp hơn nhiều.

Tôi ước tôi biết một cách nhanh hơn để thực hiện chuyển vị ( Chỉnh sửa: Tôi đã tìm thấy một giải pháp nhanh hơn, xem phần cuối của câu trả lời của tôi ). Khi Haswell / AVX2 ra mắt trong vài tuần tới, nó sẽ có chức năng tập hợp. Tôi không biết liệu điều đó có hữu ích trong trường hợp này hay không nhưng tôi có thể hình ảnh tập hợp một cột và viết ra một hàng. Có thể nó sẽ làm cho việc chuyển đổi không cần thiết.

Đối với cách bôi Gaussian, những gì bạn làm là bôi theo chiều ngang và sau đó bôi theo chiều dọc. Nhưng bôi bẩn theo chiều dọc có vấn đề về bộ nhớ cache, vì vậy những gì bạn làm là

Smear image horizontally
transpose output 
Smear output horizontally
transpose output

Đây là một bài báo của Intel giải thích rằng http://software.intel.com/en-us/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions

Cuối cùng, những gì tôi thực sự làm trong phép nhân ma trận (và trong phép bôi Gaussian) không phải là lấy chính xác chuyển vị mà lấy chuyển vị theo độ rộng của một kích thước vectơ nhất định (ví dụ: 4 hoặc 8 cho SSE / AVX). Đây là chức năng tôi sử dụng

void reorder_matrix(const float* A, float* B, const int N, const int M, const int vec_size) {
    #pragma omp parallel for
    for(int n=0; n<M*N; n++) {
        int k = vec_size*(n/N/vec_size);
        int i = (n/vec_size)%N;
        int j = n%vec_size;
        B[n] = A[M*i + k + j];
    }
}

BIÊN TẬP:

Tôi đã thử một số chức năng để tìm chuyển vị nhanh nhất cho ma trận lớn. Cuối cùng, kết quả nhanh nhất là sử dụng chặn vòng lặp với block_size=16( Chỉnh sửa: Tôi đã tìm thấy một giải pháp nhanh hơn bằng cách sử dụng SSE và chặn vòng lặp - xem bên dưới ). Mã này hoạt động với bất kỳ ma trận NxM nào (tức là ma trận không phải là hình vuông).

inline void transpose_scalar_block(float *A, float *B, const int lda, const int ldb, const int block_size) {
    #pragma omp parallel for
    for(int i=0; i<block_size; i++) {
        for(int j=0; j<block_size; j++) {
            B[j*ldb + i] = A[i*lda +j];
        }
    }
}

inline void transpose_block(float *A, float *B, const int n, const int m, const int lda, const int ldb, const int block_size) {
    #pragma omp parallel for
    for(int i=0; i<n; i+=block_size) {
        for(int j=0; j<m; j+=block_size) {
            transpose_scalar_block(&A[i*lda +j], &B[j*ldb + i], lda, ldb, block_size);
        }
    }
}

Các giá trị ldaldblà chiều rộng của ma trận. Chúng phải là bội số của kích thước khối. Để tìm các giá trị và cấp phát bộ nhớ cho ma trận 3000x1001, tôi làm như sau

#define ROUND_UP(x, s) (((x)+((s)-1)) & -(s))
const int n = 3000;
const int m = 1001;
int lda = ROUND_UP(m, 16);
int ldb = ROUND_UP(n, 16);

float *A = (float*)_mm_malloc(sizeof(float)*lda*ldb, 64);
float *B = (float*)_mm_malloc(sizeof(float)*lda*ldb, 64);

Đối với 3000x1001, nó trả về ldb = 3008 lda = 1008

Biên tập:

Tôi đã tìm thấy một giải pháp thậm chí còn nhanh hơn bằng cách sử dụng bản chất SSE:

inline void transpose4x4_SSE(float *A, float *B, const int lda, const int ldb) {
    __m128 row1 = _mm_load_ps(&A[0*lda]);
    __m128 row2 = _mm_load_ps(&A[1*lda]);
    __m128 row3 = _mm_load_ps(&A[2*lda]);
    __m128 row4 = _mm_load_ps(&A[3*lda]);
     _MM_TRANSPOSE4_PS(row1, row2, row3, row4);
     _mm_store_ps(&B[0*ldb], row1);
     _mm_store_ps(&B[1*ldb], row2);
     _mm_store_ps(&B[2*ldb], row3);
     _mm_store_ps(&B[3*ldb], row4);
}

inline void transpose_block_SSE4x4(float *A, float *B, const int n, const int m, const int lda, const int ldb ,const int block_size) {
    #pragma omp parallel for
    for(int i=0; i<n; i+=block_size) {
        for(int j=0; j<m; j+=block_size) {
            int max_i2 = i+block_size < n ? i + block_size : n;
            int max_j2 = j+block_size < m ? j + block_size : m;
            for(int i2=i; i2<max_i2; i2+=4) {
                for(int j2=j; j2<max_j2; j2+=4) {
                    transpose4x4_SSE(&A[i2*lda +j2], &B[j2*ldb + i2], lda, ldb);
                }
            }
        }
    }
}

1
Bắn đẹp, nhưng tôi không chắc chắn 'Phép nhân ma trận là O (n ^ 3)', tôi nghĩ nó là O (n ^ 2).
ulyssis 2

2
@ ulyssis2 Đó là O (n ^ 3), trừ khi bạn sử dụng Phép nhân ma trận của Strassen (O (n ^ 2,8074)). user2088790: Điều này được thực hiện rất tốt. Giữ cái này trong bộ sưu tập cá nhân của tôi. :)
saurabheights

10
Trong trường hợp, bất cứ ai muốn biết ai đã viết câu trả lời này, đó là tôi. Tôi đã bỏ SO một lần, vượt qua nó và quay lại.
Z boson

1
@ ulyssis2 Phép nhân ma trận Naive chắc chắn nhất là O (n ^ 3), và theo như tôi biết, các hạt nhân tính toán thực hiện thuật toán ngây thơ (tôi nghĩ điều này là do Strassen's kết thúc bằng cách thực hiện nhiều phép toán hơn (phép cộng), điều này thật tệ nếu bạn có thể làm sản phẩm nhanh, nhưng tôi có thể sai). Đây là một bài toán mở liệu phép nhân ma trận có thể là O (n ^ 2) hay không.
étale-cohomology

Việc dựa vào thư viện đại số tuyến tính để thực hiện công việc cho bạn thường là một lựa chọn tốt hơn. Các thư viện hiện đại như Intel MKL, OpenBLAS, v.v. cung cấp điều phối CPU động, chọn cách triển khai tốt nhất có sẵn cho phần cứng của bạn (ví dụ: có thể có các thanh ghi vectơ rộng hơn SSE: AVX AVX2, AVX512 ...), vì vậy bạn không 'không cần phải tạo một chương trình không di động để có được một chương trình nhanh.
Jorge Bellon

39

Điều này sẽ phụ thuộc vào ứng dụng của bạn nhưng nói chung cách nhanh nhất để chuyển ma trận là đảo ngược tọa độ của bạn khi bạn tra cứu, sau đó bạn thực sự không phải di chuyển bất kỳ dữ liệu nào.


32
Điều này thật tuyệt nếu đó là một ma trận nhỏ hoặc bạn chỉ đọc từ nó một lần. Tuy nhiên, nếu ma trận chuyển vị lớn và cần được sử dụng lại nhiều lần, bạn vẫn có thể lưu phiên bản chuyển vị nhanh để có được mẫu truy cập bộ nhớ tốt hơn. (+1, btw)
Agentlien

2
@Agentlien: Tại sao A [j] [i] lại chậm hơn A [i] [j]?
cốc

32
@beaker Nếu bạn có một ma trận lớn, các hàng / cột khác nhau có thể chiếm các dòng / trang trong bộ nhớ cache khác nhau. Trong trường hợp này, bạn muốn lặp lại các phần tử theo cách mà bạn truy cập các phần tử liền kề sau nhau. Nếu không, nó có thể dẫn đến việc mọi truy cập phần tử trở thành lỗi bộ nhớ cache, điều này hoàn toàn phá hủy hiệu suất.
Agentlien

10
@beaker: nó liên quan đến bộ nhớ đệm ở cấp CPU (giả sử rằng ma trận là một khối bộ nhớ lớn duy nhất), các dòng trong bộ nhớ cache sau đó là các dòng hiệu quả của ma trận và trình tìm nạp trước có thể tìm nạp một vài dòng tiếp theo. Nếu bạn chuyển đổi quyền truy cập, bộ nhớ cache / trình cài đặt sẵn CPU vẫn hoạt động từng dòng trong khi bạn truy cập từng cột, hiệu suất có thể giảm đáng kể.
Matthieu M.

2
@taocp Về cơ bản, bạn sẽ cần một số loại cờ để chỉ ra nó được hoán và sau đó yêu cầu cho tiếng nói (i,j)sẽ được ánh xạ tới(j,i)
Shafik Yaghmour

5

Một số chi tiết về chuyển đổi ma trận float vuông 4x4 (tôi sẽ thảo luận về số nguyên 32 bit sau) bằng phần cứng x86. Thật hữu ích khi bắt đầu ở đây để chuyển các ma trận vuông lớn hơn như 8x8 hoặc 16x16.

_MM_TRANSPOSE4_PS(r0, r1, r2, r3)được thực hiện khác nhau bởi các trình biên dịch khác nhau. GCC và ICC (tôi chưa kiểm tra Clang) sử dụng unpcklps, unpckhps, unpcklpd, unpckhpdtrong khi MSVC chỉ sử dụng shufps. Chúng ta thực sự có thể kết hợp hai cách tiếp cận này với nhau như thế này.

t0 = _mm_unpacklo_ps(r0, r1);
t1 = _mm_unpackhi_ps(r0, r1);
t2 = _mm_unpacklo_ps(r2, r3);
t3 = _mm_unpackhi_ps(r2, r3);

r0 = _mm_shuffle_ps(t0,t2, 0x44);
r1 = _mm_shuffle_ps(t0,t2, 0xEE);
r2 = _mm_shuffle_ps(t1,t3, 0x44);
r3 = _mm_shuffle_ps(t1,t3, 0xEE);

Một quan sát thú vị là hai lần trộn có thể được chuyển đổi thành một lần trộn và hai lần trộn (SSE4.1) như thế này.

t0 = _mm_unpacklo_ps(r0, r1);
t1 = _mm_unpackhi_ps(r0, r1);
t2 = _mm_unpacklo_ps(r2, r3);
t3 = _mm_unpackhi_ps(r2, r3);

v  = _mm_shuffle_ps(t0,t2, 0x4E);
r0 = _mm_blend_ps(t0,v, 0xC);
r1 = _mm_blend_ps(t2,v, 0x3);
v  = _mm_shuffle_ps(t1,t3, 0x4E);
r2 = _mm_blend_ps(t1,v, 0xC);
r3 = _mm_blend_ps(t3,v, 0x3);

Điều này đã chuyển đổi hiệu quả 4 lần trộn thành 2 lần trộn và 4 lần trộn. Điều này sử dụng nhiều hơn 2 hướng dẫn so với việc triển khai GCC, ICC và MSVC. Ưu điểm là nó làm giảm áp lực cổng có thể có lợi trong một số trường hợp. Hiện tại, tất cả các lần trộn và giải nén chỉ có thể đi đến một cổng cụ thể trong khi các bộ trộn có thể đi đến một trong hai cổng khác nhau.

Tôi đã thử sử dụng 8 trộn như MSVC và chuyển đổi đó thành 4 trộn + 8 trộn nhưng nó không hoạt động. Tôi vẫn phải sử dụng 4 lần giải nén.

Tôi đã sử dụng kỹ thuật tương tự cho phép chuyển vị phao 8x8 (xem ở cuối câu trả lời đó). https://stackoverflow.com/a/25627536/2542702 . Trong câu trả lời đó, tôi vẫn phải sử dụng 8 lần mở gói nhưng tôi đã chuyển đổi 8 lần trộn thành 4 lần trộn và 8 lần trộn.

Đối với số nguyên 32-bit thì không có gì giống như vậy shufps(ngoại trừ trộn 128-bit với AVX512) vì vậy nó chỉ có thể được thực hiện với các gói giải nén mà tôi không nghĩ có thể chuyển đổi thành hỗn hợp (hiệu quả). Với AVX512 vshufi32x4hoạt động hiệu quả như shufpsngoại trừ các làn 128-bit gồm 4 số nguyên thay vì số nổi 32-bit, vì vậy kỹ thuật tương tự này có thể áp dụng vshufi32x4trong một số trường hợp. Với Knights Landing xáo trộn chậm hơn bốn lần (thông lượng) so với hỗn hợp.


1
Bạn có thể sử dụng shufpstrên dữ liệu số nguyên. Nếu bạn đang thực hiện nhiều lần xáo trộn, bạn có thể thực hiện tất cả trong miền FP cho shufps+ blendps, đặc biệt nếu bạn không có vpblenddsẵn AVX2 hiệu quả như nhau . Ngoài ra, trên phần cứng Intel SnB-family, không có thêm độ trễ bỏ qua để sử dụng shufpsgiữa các lệnh số nguyên như paddd. (Tuy nhiên, có một độ trễ bỏ qua để trộn blendpsvới paddd, theo thử nghiệm SnB của Agner Fog.)
Peter Cordes

@PeterCordes, tôi cần xem xét lại các thay đổi của miền. Có một số bảng (có thể là một câu trả lời trên SO) tóm tắt hình phạt thay đổi miền cho Core2-Skylake không? Trong mọi trường hợp, tôi đã suy nghĩ nhiều hơn về điều này. Bây giờ tôi hiểu tại sao wim và bạn tiếp tục đề cập vinsertf64x4trong câu trả lời chuyển vị 16x16 của tôi thay vì vinserti64x4. Nếu tôi đang đọc sau đó viết ma trận thì chắc chắn không thành vấn đề nếu tôi sử dụng miền dấu phẩy động hay miền số nguyên vì phép chuyển vị chỉ là dữ liệu di chuyển.
Z boson

1
Các bảng của Agner liệt kê các miền trên mỗi lệnh cho Core2 và Nehalem (và tôi nghĩ là AMD), nhưng không phải họ SnB. Hướng dẫn về microarch của Agner chỉ có một đoạn nói rằng nó giảm xuống 1c và thường là 0 trên SnB, với một số ví dụ. Tôi nghĩ là sách hướng dẫn tối ưu hóa của Intel có một bảng, nhưng tôi chưa thử tìm hiểu nó nên tôi không nhớ nó có bao nhiêu chi tiết. Tôi nhớ lại nó không phải là hoàn toàn rõ ràng những gì loại một hướng dẫn nhất định sẽ được.
Peter Cordes

Ngay cả khi bạn không chỉ ghi lại vào bộ nhớ, nó cũng chỉ có thêm 1 đồng hồ cho toàn bộ quá trình chuyển đổi. Độ trễ thêm cho mỗi toán hạng có thể xảy ra song song (hoặc so le) khi người tiêu dùng chuyển vị bắt đầu đọc các thanh ghi được viết bởi xáo trộn hoặc kết hợp. Việc thực thi không theo thứ tự cho phép một vài FMA đầu tiên hoặc bất cứ thứ gì bắt đầu trong khi vài lần xáo trộn cuối cùng đang kết thúc, nhưng không có chuỗi chậm trễ dypass, chỉ là thêm nhiều nhất một.
Peter Cordes

1
Câu trả lời tuyệt vời! Tài liệu hướng dẫn sử dụng intel 64-ia-32-architecture-Optimization, bảng 2-3, liệt kê các độ trễ bỏ qua cho Skylake, có thể bạn quan tâm. Bảng 2-8 cho Haswell trông khá khác biệt.
wim

1

Hãy coi mỗi hàng là một cột và mỗi cột là một hàng .. sử dụng j, i thay vì i, j

demo: http://ideone.com/lvsxKZ

#include <iostream> 
using namespace std;

int main ()
{
    char A [3][3] =
    {
        { 'a', 'b', 'c' },
        { 'd', 'e', 'f' },
        { 'g', 'h', 'i' }
    };

    cout << "A = " << endl << endl;

    // print matrix A
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++) cout << A[i][j];
        cout << endl;
    }

    cout << endl << "A transpose = " << endl << endl;

    // print A transpose
    for (int i=0; i<3; i++)
    {
        for (int j=0; j<3; j++) cout << A[j][i];
        cout << endl;
    }

    return 0;
}

1

chuyển vị mà không có bất kỳ chi phí nào (lớp không hoàn thành):

class Matrix{
   double *data; //suppose this will point to data
   double _get1(int i, int j){return data[i*M+j];} //used to access normally
   double _get2(int i, int j){return data[j*N+i];} //used when transposed

   public:
   int M, N; //dimensions
   double (*get_p)(int, int); //functor to access elements  
   Matrix(int _M,int _N):M(_M), N(_N){
     //allocate data
     get_p=&Matrix::_get1; // initialised with normal access 
     }

   double get(int i, int j){
     //there should be a way to directly use get_p to call. but i think even this
     //doesnt incur overhead because it is inline and the compiler should be intelligent
     //enough to remove the extra call
     return (this->*get_p)(i,j);
    }
   void transpose(){ //twice transpose gives the original
     if(get_p==&Matrix::get1) get_p=&Matrix::_get2;
     else get_p==&Matrix::_get1; 
     swap(M,N);
     }
}

có thể được sử dụng như thế này:

Matrix M(100,200);
double x=M.get(17,45);
M.transpose();
x=M.get(17,45); // = original M(45,17)

tất nhiên tôi không bận tâm đến việc quản lý bộ nhớ ở đây, đây là một chủ đề quan trọng nhưng khác.


4
Bạn có một chi phí từ con trỏ hàm phải được theo sau cho mỗi quyền truy cập phần tử.
user877329

1

Nếu kích thước của các mảng đã biết trước thì chúng ta có thể sử dụng union để trợ giúp. Như thế này-

#include <bits/stdc++.h>
using namespace std;

union ua{
    int arr[2][3];
    int brr[3][2];
};

int main() {
    union ua uav;
    int karr[2][3] = {{1,2,3},{4,5,6}};
    memcpy(uav.arr,karr,sizeof(karr));
    for (int i=0;i<3;i++)
    {
        for (int j=0;j<2;j++)
            cout<<uav.brr[i][j]<<" ";
        cout<<'\n';
    }

    return 0;
}

Tôi là người mới trong C / C ++, nhưng điều này có vẻ thiên tài. Bởi vì union sử dụng vị trí bộ nhớ được chia sẻ cho các thành viên của nó, bạn có thể đọc bộ nhớ đó theo cách khác. Do đó, bạn nhận được một ma trận chuyển vị mà không cần thực hiện phân bổ mảng mới. Tôi nói đúng chứ?
Doğuş

1
template <class T>
void transpose( const std::vector< std::vector<T> > & a,
std::vector< std::vector<T> > & b,
int width, int height)
{
    for (int i = 0; i < width; i++)
    {
        for (int j = 0; j < height; j++)
        {
            b[j][i] = a[i][j];
        }
    }
} 

1
Tôi muốn nghĩ rằng nó sẽ nhanh hơn nếu bạn trao đổi hai vòng lặp, do hình phạt bỏ lỡ bộ nhớ cache nhỏ hơn khi ghi hơn là đọc.
phoeagon

5
Điều này chỉ hoạt động cho một ma trận vuông. Một ma trận hình chữ nhật là một vấn đề hoàn toàn khác!
NealB

2
Câu hỏi yêu cầu cách nhanh nhất. Đây chỉ là một cách. Điều gì khiến bạn nghĩ rằng nó là nhanh, chứ đừng nói là nhanh nhất? Đối với ma trận lớn, điều này sẽ phá hủy bộ nhớ cache và có hiệu suất khủng khiếp.
Eric Postpischil

1
@NealB: Bạn nghĩ thế nào về điều đó?
Eric Postpischil

@EricPostpischil OP đang hỏi về một ma trận tương đối lớn nên tôi cho rằng họ muốn làm điều đó "tại chỗ" để tránh phân bổ gấp đôi bộ nhớ. Khi điều này được thực hiện, địa chỉ cơ sở của ma trận nguồn và đích giống nhau. Chuyển đổi bằng cách lật các chỉ số Hàng và Cột sẽ chỉ hoạt động đối với ma trận vuông. Có những phương pháp để làm điều này đúng cho ma trận hình chữ nhật nhưng chúng hơi phức tạp hơn.
NealB

0

Các thư viện đại số tuyến tính hiện đại bao gồm các phiên bản được tối ưu hóa của các phép toán phổ biến nhất. Nhiều người trong số họ bao gồm điều phối CPU động, chọn cách triển khai tốt nhất cho phần cứng tại thời điểm thực thi chương trình (mà không ảnh hưởng đến tính di động).

Đây thường là một giải pháp thay thế tốt hơn để thực hiện tối ưu hóa thủ công các hàm của bạn thông qua các chức năng nội tại của phần mở rộng vectơ. Cái sau sẽ ràng buộc việc triển khai của bạn với một nhà cung cấp phần cứng và mô hình cụ thể: nếu bạn quyết định hoán đổi sang một nhà cung cấp khác (ví dụ: Power, ARM) hoặc sang một phần mở rộng vectơ mới hơn (ví dụ: AVX512), bạn sẽ cần triển khai lại nó để tận dụng tối đa chúng.

Ví dụ, chuyển vị MKL bao gồm chức năng mở rộng BLAS imatcopy. Bạn cũng có thể tìm thấy nó trong các triển khai khác như OpenBLAS:

#include <mkl.h>

void transpose( float* a, int n, int m ) {
    const char row_major = 'R';
    const char transpose = 'T';
    const float alpha = 1.0f;
    mkl_simatcopy (row_major, transpose, n, m, alpha, a, n, n);
}

Đối với một dự án C ++, bạn có thể sử dụng Armadillo C ++:

#include <armadillo>

void transpose( arma::mat &matrix ) {
    arma::inplace_trans(matrix);
}

0

intel mkl đề xuất các ma trận chuyển vị / sao chép tại chỗ và ngoài vị trí. đây là liên kết đến tài liệu . Tôi khuyên bạn nên thử triển khai tại chỗ nhanh hơn mười tại chỗ và trong tài liệu của phiên bản mkl mới nhất có một số lỗi.


-1

Tôi nghĩ rằng cách nhanh nhất không nên lấy cao hơn O (n ^ 2) cũng theo cách này, bạn có thể chỉ sử dụng khoảng trắng O (1):
cách để làm điều đó là hoán đổi theo từng cặp vì khi bạn hoán vị một ma trận thì bạn làm là: M [i] [j] = M [j] [i], do đó, lưu trữ M [i] [j] trong nhiệt độ, sau đó M [i] [j] = M [j] [i], và bước cuối cùng: M [j] [i] = temp. điều này có thể được thực hiện bởi một lần vượt qua, vì vậy nó sẽ mất O (n ^ 2)


2
M [i] [j] = M [j] [i] sẽ chỉ hoạt động nếu nó là ma trận vuông; nếu không nó sẽ ném một ngoại lệ chỉ mục.
Antony Thomas

-6

câu trả lời của tôi là chuyển thành ma trận 3x3

 #include<iostream.h>

#include<math.h>


main()
{
int a[3][3];
int b[3];
cout<<"You must give us an array 3x3 and then we will give you Transposed it "<<endl;
for(int i=0;i<3;i++)
{
    for(int j=0;j<3;j++)
{
cout<<"Enter a["<<i<<"]["<<j<<"]: ";

cin>>a[i][j];

}

}
cout<<"Matrix you entered is :"<<endl;

 for (int e = 0 ; e < 3 ; e++ )

{
    for ( int f = 0 ; f < 3 ; f++ )

        cout << a[e][f] << "\t";


    cout << endl;

    }

 cout<<"\nTransposed of matrix you entered is :"<<endl;
 for (int c = 0 ; c < 3 ; c++ )
{
    for ( int d = 0 ; d < 3 ; d++ )
        cout << a[d][c] << "\t";

    cout << endl;
    }

return 0;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.