我有一个需要转置的矩阵(相对较大)。例如,假设我的矩阵是
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
最快的方法是什么?
我有一个需要转置的矩阵(相对较大)。例如,假设我的矩阵是
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
最快的方法是什么?
Answers:
这是一个很好的问题。您有很多原因想要将矩阵实际转置到内存中,而不仅仅是交换坐标,例如在矩阵乘法和高斯拖尾中。
首先,让我列出我用于移调的功能之一(编辑:请在我的答案结尾处找到更快的解决方案)
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
)。在矩阵乘法中,循环平铺比进行转置更为有效,但这要复杂得多。
我希望我知道一种更快的转置方法(编辑:我找到了一个更快的解决方案,请参见答案的结尾)。当Haswell / AVX2在几周后问世时,它将具有收集功能。我不知道在这种情况下是否有帮助,但是我可以通过图像收集一列并写出一行。也许它将使移调变得不必要。
对于高斯涂抹,您要做的是水平涂抹然后垂直涂抹。但是垂直涂抹会产生缓存问题,因此您要做的是
Smear image horizontally
transpose output
Smear output horizontally
transpose output
这是Intel发表的一篇论文,解释了 http://software.intel.com/zh-CN/articles/iir-gaussian-blur-filter-implementation-using-intel-advanced-vector-extensions
最后,我实际上在矩阵乘法(以及高斯拖尾)中所做的并不是完全采用转置,而是采用一定矢量大小(例如,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];
}
}
编辑:
我尝试了几种函数来找到大型矩阵最快的转置。最后,最快的结果是将循环阻止与block_size=16
(编辑:我发现了使用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 = 1008
编辑:
我发现了使用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);
}
}
}
}
}
这将取决于您的应用程序,但是一般而言,转置矩阵的最快方法是在进行查找时反转坐标,因此不必实际移动任何数据。
(i,j)
将要说的请求映射到(j,i)
有关使用x86硬件转置4x4正方形浮点数(稍后将讨论32位整数)矩阵的一些详细信息。从此处开始以转置较大的正方形矩阵(例如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个混合。这比GCC,ICC和MSVC的实现多使用2条指令。优点是它可以降低端口压力,这在某些情况下可能会有所帮助。当前,所有混洗和拆包只能进入一个特定的港口,而混合酒可以进入两个不同的港口之一。
我尝试使用MSVC等8种混洗,并将其转换为4种混洗+ 8种混合,但没有用。我仍然必须使用4个解压缩包。
我对8x8浮点转置使用了相同的技术(请参见该答案的结尾)。 https://stackoverflow.com/a/25627536/2542702。在那个答案中,我仍然必须使用8个解包程序,但是我设法将8个混洗转换为4个混洗和8种混合处理。
对于32位整数,没有类似的东西shufps
(AVX512的128位随机播放除外),因此只能通过解压缩来实现,我认为这些解压缩不能(有效地)转换为混合。使用AVX512时,其vshufi32x4
行为类似于shufps
4个整数的128位通道而不是32位浮点数,因此vshufi32x4
在某些情况下可能也可以使用相同的技术。使用Knights Landing,混洗的速度(吞吐量)比混合慢4倍。
shufps
整数数据。如果您要进行大量改组,那么在FP域中为shufps
+进行全部操作可能是值得的blendps
,尤其是在您没有同样有效的AVX2的vpblendd
情况下。另外,在Intel SnB系列硬件上,shufps
在整数指令之间使用时没有多余的旁路延迟paddd
。(不过,根据Agner Fog的SnB测试,blendps
与混合时会有旁路延迟paddd
。)
vinsertf64x4
在我的16x16转置答案中一直提到而不是vinserti64x4
。如果我正在阅读然后编写矩阵,那么我使用浮点域还是整数域都没关系,因为转置只是在移动数据。
将每一行视为一列,并将每一列视为一行..使用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)
当然,我在这里没有理会内存管理,这很重要,但主题却不同。
如果事先知道数组的大小,则可以使用联合来提供帮助。像这样-
#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;
}
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];
}
}
}
现代线性代数库包括最常用运算的优化版本。其中许多功能包括动态CPU分配功能,该功能可以在程序执行时为硬件选择最佳的实现方式(而不影响可移植性)。
通常,这是通过向量扩展固有功能对函子进行手动优化的更好的选择。后者会将您的实现方式与特定的硬件供应商和模型联系在一起:如果您决定换用其他供应商(例如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 ++项目,可以使用Armadillo C ++:
#include <armadillo>
void transpose( arma::mat &matrix ) {
arma::inplace_trans(matrix);
}
我认为最快速的方法也不应占用高于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;
}