Каков самый быстрый способ транспонировать матрицу в C++?
у меня есть матрица (относительно большая), которую мне нужно транспонировать. Например, предположим, что моя матрица
a b c d e f
g h i j k l
m n o p q r
Я хочу, чтобы результат был следующим:
a g m
b h n
c I o
d j p
e k q
f l r
какой самый быстрый способ сделать это?
8 ответов:
это хороший вопрос. Есть много причин, по которым вы хотели бы фактически перенести матрицу в память, а не просто поменять координаты, например, при умножении матрицы и размазывании Гаусса.
сначала позвольте мне перечислить одну из функций, которые я использую для транспонирования (EDIT: пожалуйста, смотрите конец моего ответа, где я нашел гораздо более быстрое решение)
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]; } }теперь давайте посмотрим, почему транспонирование полезно. Рассмотрим умножение матрицы C = A*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*l+j]; } C[K*i + j] = tmp; } }таким образом, однако, будет иметь много промахов кэша. Гораздо более быстрое решение состоит в том, чтобы сначала транспонировать B
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);умножение матрицы равно O (n^3), а транспонирование-O(n^2), поэтому взятие транспонирования должно иметь незначительное влияние на время вычисления (для больших
n). В цикле умножения матриц черепица Еще более эффективна, чем при транспонировании, но это гораздо сложнее.я хотел бы знать более быстрый способ сделать транспонирование (Edit: я нашел более быстрое решение, см. конец моего ответа). Когда Haswell / AVX2 выйдет через несколько недель, у него будет функция сбора. Я не знаю, будет ли это полезно в этом случае, но я могу представить себе сбор столбца и запись строки. Может быть, это сделает транспонирование ненужным.
для гауссовского размазывания то, что вы делаете, размазывается по горизонтали, а затем размазывается по вертикали. Но размазывание вертикально есть проблема с кешем, так что вы делаете
Smear image horizontally transpose output Smear output horizontally transpose outputвот документ от Intel, объясняющий, что http://software.intel.com/en-us/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions
наконец, то, что я на самом деле делаю в матричном умножении (и в гауссовом размазывании), не берет точно транспонирование, а берет транспонирование по ширине определенного размера вектора (например, 4 или 8 для SSE/AVX). Вот эта функция Я использую
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]; } }EDIT:
я попробовал несколько функций, чтобы найти самую быструю транспонирование для больших матриц. В конце концов, самый быстрый результат - использовать блокировку цикла с
block_size=16(Edit: я нашел более быстрое решение с помощью SSE и блокировки цикла-см. ниже). Этот код работает для любой матрицы NxM (т. е. матрица не должна быть квадратной).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); } } }значения
ldaиldb- это ширина матрицы. Эти должны быть кратны размеру блока. Чтобы найти значения и выделить память, например, для матрицы 3000x1001, я делаю что-то вроде этого#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);для 3000x1001 это возвращает
ldb = 3008иlda = 1008Edit:
я нашел еще более быстрое решение с помощью SSE intrinsics:
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); } } } } }
Это будет зависеть от вашего приложения, но в целом самый быстрый способ транспонировать матрицу-это инвертировать ваши координаты, когда вы смотрите вверх, тогда вам не нужно фактически перемещать какие-либо данные.
некоторые подробности о транспонировании 4x4 квадратных поплавковых (я буду обсуждать 32-разрядное целое число позже) матриц с оборудованием x86. Полезно начать здесь, чтобы транспонировать большие квадратные матрицы, такие как 8x8 или 16x16.
_MM_TRANSPOSE4_PS(r0, r1, r2, r3)реализуется по-разному в разных компиляторах. GCC и ICC (я не проверял Clang) используйтеunpcklps, unpckhps, unpcklpd, unpckhpdв то время как MSVC использует толькоshufps. Мы можем объединить эти два подхода вместе.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);один интересный наблюдение состоит в том, что две перетасовки могут быть преобразованы в одну перетасовку и две смеси (SSE4.1), как это.
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);это эффективно преобразуется 4 перетасовки в 2 перетасовки и 4 смеси. Это использует 2 больше инструкций, чем реализация GCC, ICC и MSVC. Преимущество заключается в том, что он снижает давление в портах, что может иметь преимущество в некоторых обстоятельствах. В настоящее время все перетасовки и распаковки могут идти только в один конкретный порт, тогда как смеси могут идти в любой из двух разных портов порты.
Я попытался использовать 8 перетасовок, таких как MSVC, и преобразовать их в 4 перетасовки + 8 смесей, но это не сработало. Я все еще должен был использовать 4 распаковывает.
я использовал эту же технику для транспонирования поплавка 8x8 (см. Ближе к концу этого ответа). https://stackoverflow.com/a/25627536/2542702. в этом ответе мне все еще пришлось использовать 8 распаковок, но я решил преобразовать 8 перетасовок в 4 перетасовки и 8 смесей.
для 32-разрядных целых чисел нет ничего как
shufps(за исключением 128-битных перетасовок с AVX512), поэтому он может быть реализован только с распаковками, которые, как я думаю, не могут быть преобразованы в смеси (эффективно). С AVX512vshufi32x4эффективно действует какshufpsза исключением 128-битных полос из 4 целых чисел вместо 32-битных поплавков, так что этот же метод может быть возможно сvshufi32x4в некоторых случаях. С рыцарями приземления перетасовки в четыре раза медленнее (пропускная способность), чем смеси.
template <class T> void transpose( 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]; } } }
рассматривайте каждую строку как столбец, а каждый столбец как строку .. используйте j, i вместо i, j
#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; }
транспонирование без каких-либо накладных расходов (класс не полный):
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); } }можно использовать так:
Matrix M(100,200); double x=M.get(17,45); M.transpose(); x=M.get(17,45); // = original M(45,17)конечно, я не беспокоился об управлении памятью здесь, что является важной, но другой темой.
Я думаю, что самый быстрый способ не должен принимать выше, чем O (n^2) также таким образом вы можете использовать только O(1) пространство :
способ сделать это-поменять местами в парах , потому что когда вы транспонируете матрицу, то что вы делаете: M[i][j]=M[j][i], поэтому сохраните M[i][j] в temp, затем M[i][j]=M[j][i], и последний шаг : M[j][i]=temp. это может быть сделано за один проход, поэтому он должен занять O (n^2)
мой ответ транспонируется из Матрицы 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; }
Comments