Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/algorithm/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/cmake/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++中矩阵最快的转换方法是什么?_C++_Algorithm_Matrix_Transpose - Fatal编程技术网

C++中矩阵最快的转换方法是什么?

C++中矩阵最快的转换方法是什么?,c++,algorithm,matrix,transpose,C++,Algorithm,Matrix,Transpose,我有一个相对较大的矩阵,需要转置。例如,假设我的矩阵是 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 最快的方法是什么?这取决于您的应用程序,但通常转置矩阵的最快方法是在查找时反转坐标,然后您不必实际移动任何数据。这是一个好问题。有很多原因使您希望在内存中实际转置矩阵,而不仅仅是交换坐标,例如在矩阵乘法和高斯涂抹中 template <class T> void

我有一个相对较大的矩阵,需要转置。例如,假设我的矩阵是

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

最快的方法是什么?

这取决于您的应用程序,但通常转置矩阵的最快方法是在查找时反转坐标,然后您不必实际移动任何数据。

这是一个好问题。有很多原因使您希望在内存中实际转置矩阵,而不仅仅是交换坐标,例如在矩阵乘法和高斯涂抹中

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];
        }
    }
} 
首先,让我列出一个用于转置编辑的函数:请参阅我的答案的结尾,在那里我找到了一个更快的解决方案

这是英特尔公司的一篇论文,解释了这一点

最后,我在矩阵乘法和高斯涂抹中实际做的不是精确地取转置,而是在一定向量大小的宽度上取转置,例如对于SSE/AVX取4或8。这是我使用的函数

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];
    }
}
值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=1008

编辑:

我发现了一个使用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);
                }
            }
        }
    }
}

将每行视为一列,每列视为一行。。用j,i代替i,j

演示:


我认为最快的方式不应取高于^2的值,这样您可以只使用O1空间:
这样做的方法是成对交换,因为当你转置矩阵时,你要做的是:M[i][j]=M[j][i],所以将M[i][j]存储在temp中,然后M[i][j]=M[j][i],最后一步是:M[j][i]=temp。这可以通过一次传递完成,因此它应该是^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;
}

无任何开销等级的转置未完成:

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)

当然,我没有在这里讨论内存管理,这是一个至关重要但又不同的话题

关于转置4x4平方浮点的一些细节我将在后面讨论使用x86硬件的32位整数矩阵。从这里开始转置较大的方阵(如8x8或16x16)很有帮助

_MM_TRANSPOSE4_PSr0、r1、r2、r3由不同的编译器实现。GCC和ICC我没有检查Clang使用unpcklps、unpckhps、unpcklpd、unpckhpd,而MSVC只使用SHUFP。实际上,我们可以像这样将这两种方法结合在一起

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);
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);
一个有趣的观察结果是,两次洗牌可以转换为一次洗牌和两次混合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);

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);
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次混合。这比GCC、ICC和MSVC的实现多使用2条指令。其优点是降低了端口压力,这在某些情况下可能有好处。 目前,所有的洗牌和解包只能到一个特定的端口,而混合可以到两个不同的端口中的任何一个

我试着使用8个像MSVC一样的洗牌,并将其转换为4个洗牌+8个混合,但没有成功。我还得用4个拆包

我对8x8浮点转置使用了相同的技术,请参见答案的末尾。 . 在这个答案中,我仍然需要使用8个解包,但我设法将8个洗牌转换为4个洗牌和8个混合


对于32位整数,除了使用AVX512进行128位洗牌之外,没有什么比SHUFP更像的了,因此它只能通过解包实现,我认为解包不能有效地转换为混合。使用AVX512时,vshufi32x4的作用与SHUFP类似,不同之处在于4个整数的128位通道,而不是32位浮点,因此在某些情况下,vshufi32x4也可能采用相同的技术。骑士登陆时,洗牌的吞吐速度比混合牌慢四倍。

如果事先知道阵列的大小,我们可以使用联盟来帮助我们。像这样-

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

现代线性代数库包括最常见操作的优化版本。其中许多包括动态CPU调度,它在程序执行时为硬件选择最佳实现,而不影响可移植性

这通常是通过向量扩展内在函数对functinos执行手动优化的更好选择。后者将把您的实现与特定的硬件供应商和型号联系在一起:如果您决定与其他供应商(如Power、ARM)或更新的矢量扩展(如AVX512)交换,则需要重新实现,以充分利用它们

例如,MKL转置包括BLAS扩展函数imatcopy。您也可以在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);
}

对于C++项目,可以使用ARMADILO C++:

#include <armadillo>

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

英特尔mkl建议就地和异地转置/复制矩阵。这是你的电话号码。我推荐tryi

ng不到位的实现,因为最新版本的mkl文档中包含一些错误

这叫做转置。旋转90度是一个完全不同的概念。最快的方法不是旋转它,而是在访问数组时简单地交换索引顺序。无论它有多快,您都必须访问矩阵的所有元素。@HighPerformanceMark:我想这取决于,如果您希望按行顺序重复访问矩阵,那么使用转置标志将给您带来沉重打击。转置矩阵因其导致内存缓存问题而臭名昭著。如果您的数组足够大,以至于转置的性能非常重要,并且您无法通过简单地提供带有交换索引的接口来避免转置,那么您最好的选择是使用现有的库例程来转置大型矩阵。专家们已经完成了这项工作,你应该使用它。如果它是一个小矩阵,或者你只从中阅读一次,这是非常棒的。但是,如果转置矩阵很大,需要多次重复使用,您可能仍然需要保存快速转置版本以获得更好的内存访问模式+1.btw@Agentlien:为什么[j][i]会比[i][j]慢?@烧杯如果矩阵很大,不同的行/列可能会占用不同的缓存线/页面。在本例中,您希望以这样一种方式对元素进行迭代,即您可以依次访问相邻的元素。否则,它可能会导致每个元素访问都成为缓存未命中,从而完全破坏性能。@比克:这与CPU级别的缓存有关。假设矩阵是一个大内存块,缓存线就是矩阵的有效线,预取器可能会提取接下来的几行。如果您切换访问,CPU缓存/预取器仍然逐行工作,而您一列接一列地访问,性能下降可能会非常严重。@taocp基本上,您需要某种标志来指示它被转置,然后请求,例如i,j将映射到j,iI宁愿认为如果您交换两个循环,速度会更快,由于写入时的缓存未命中惩罚小于读取时的缓存未命中惩罚。这仅适用于方形矩阵。矩形矩阵是一个完全不同的问题!这个问题要求最快的方法。这只是一种方式。是什么让你认为它很快,更不用说最快了?对于较大的矩阵,这将破坏缓存并产生糟糕的性能。@NealB:您如何理解?@EricPostpischil OP询问的是一个相对较大的矩阵,因此我认为他们希望在适当的位置执行此操作,以避免分配双倍的内存。完成此操作后,源矩阵和目标矩阵的基址相同。通过翻转行和列索引进行的换位仅适用于平方矩阵。对于矩形矩阵,有一些方法可以做到这一点,但它们有些复杂。函数指针的开销对于每个元素的访问都必须遵循。M[i][j]=M[j][i]只有在它是方形矩阵时才起作用;否则它会引发索引异常。拍摄得不错,但我不确定“矩阵乘法在^3上”,我想它在^2上。@ulyssis2它在^3上,除非您在^2.8074上使用Strassen的矩阵乘法。用户2088790:这做得很好。把这个保存在我的个人收藏中以防万一,有人想知道是谁写的这个答案是我。我退出过一次,克服了它,回来了。@ulyssis2 Naive matrix乘法绝对是在^3上的,据我所知,计算内核实现了Naive算法,我想这是因为Strassen最终做了更多的加法运算,如果你能做快速产品,这是不好的,但我可能错了。矩阵乘法是否可以在^2上是一个公开问题。请注意,如果行数和列数不是4的倍数,则最后一个SSE代码段将无法正常工作。它将保持边框单元格不变。您可以对整数数据使用shufps。如果您正在进行大量的洗牌,那么在shufps+blendps的FP域中进行所有洗牌可能是值得的,特别是如果您没有同样高效的AVX2 vpblendd可用。此外,在Intel SnB系列硬件上,在整数指令(如paddd)之间使用SHUFP不会有额外的旁路延迟。根据Agner Fog的SnB测试,混合blendps和padd有一个旁路延迟,不过。@PeterCordes,我需要再次检查域更改。是否有一些表格可以提供答案,以便总结Core2 Skylake的域名更改惩罚?无论如何,我对这一点考虑得更多。现在我明白了为什么wim和您在我的16x16转置回答中一直提到vinsertf64x4,而不是vinserti64x4。如果我读然后写矩阵,那么我使用浮点域还是整数域肯定没有关系,因为转置只是移动数据。Agner的表列出了Core2、Nehalem和AMD的每个指令的域,我认为不是SnB系列。艾格妮
r的《微生物学指南》中有一段话说,在SnB上,它可以降到1c,通常为0,还有一些例子。英特尔的优化手册有一个表,我想,但我还没有尝试去摸索它,所以我不记得有多少细节。我记得给出的指令属于哪一类并不十分明显。即使你不只是写回内存,整个转置也只需要额外的1个时钟。当转置的使用者开始读取由混洗或混合写入的寄存器时,每个操作数的额外延迟可能以并行或交错方式发生。无序执行允许在最后几次洗牌结束时开始前几次FMA或其他任何操作,但没有dypass延迟链,最多一次。Nicw回答!英特尔64-ia-32-architectures-optimization-manual(英特尔64-ia-32-architectures-optimization-manual,表2-3)列出了Skylake的旁路延迟,您可能对此感兴趣。Haswell的表2-8看起来很不一样。我是C/C++新手,但这看起来很天才。因为union为其成员使用共享内存位置,所以您可以以不同的方式读取该内存。因此,无需进行新的数组分配即可获得转置矩阵。我说得对吗?
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);
#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;
}
#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);
}
#include <armadillo>

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