SSE,主要行vs主要列的性能问题

SSE, row major vs column major performance issue

本文关键字:性能 问题 SSE vs      更新时间:2023-10-16

对于个人和有趣的事情,我正在使用SSE(4.1)编写一个geom库。

我花了最后12小时试图理解处理行主要vs列主要存储矩阵时的性能问题。

我知道直接/OpenGL矩阵存储行主要,所以它会更好,我保持我的矩阵存储在行主要顺序,所以我将没有转换时存储/加载矩阵到/从GPU/着色器。

但是,我做了一些分析,并且我得到了列major更快的结果。

用一个变换矩阵变换一个点,在行主要中,它是p ' = p * M,在列主要中,它是p ' = M * p。所以在列major中,它只是4个点积,所以只有4个SSE4.1指令(_mm_dp_ps),当在行major中,我必须在转置矩阵上做这4个点积。

在10M矢量上的性能结果

(30/05/2014@08:48:10) Log: [5] (vec . mull . matrix) = 76.216653 ms (row major transform)

(30/05/2014@08:48:10) Log: [6] (matrix . mull . vec) = 61.554892 ms (column major transform)

我尝试了几种方法来做Vec *矩阵操作,使用_mm_转置与否,我发现最快的方法是:

mssFloat    Vec4::operator|(const Vec4& v) const //-- Dot Product
{
    return _mm_dp_ps(m_val, v.m_val, 0xFF ).m128_f32[0];
}
inline Vec4 operator*(const Vec4& vec,const Mat4& m)
{
    return Vec4(    Vec4( m[0][0],m[1][0],m[2][0],m[3][0]) | vec
        ,   Vec4( m[0][1],m[1][1],m[2][1],m[3][1]) | vec
        ,   Vec4( m[0][2],m[1][2],m[2][2],m[3][2]) | vec
        ,   Vec4( m[0][3],m[1][3],m[2][3],m[3][3]) | vec
                );
}

我的类Vec4只是一个__m128 m_val,在优化的c++中,向量构造都是在SSE寄存器上有效地完成的。

我的第一个猜测是,这个乘法不是最优的。我是SSE的新手,所以我有点困惑如何优化它,我的直觉告诉我使用shuffle指令,但我想知道为什么它会更快。它是否会比赋值更快地加载4 shuffle __m128 (__m128 m_val = _mm_set_ps(w, z, y, x);)

从https://software.intel.com/sites/landingpage/IntrinsicsGuide/

我找不到mm_set_ps的性能信息

编辑:我仔细检查了分析方法,每个测试都以相同的方式完成,所以没有内存缓存差异。为了避免本地缓存,我对随机化bug向量数组进行操作,每个测试的种子都是相同的。每次执行只进行一次测试,以避免内存缓存带来的性能提升。

不要使用_mm_dp_ps进行矩阵乘法!我已经在SSE的高效4x4矩阵向量乘法中详细解释了这一点:水平加法和点积-重点是什么?(顺便说一下,这是我在SO上的第一篇文章)。

你不需要任何比SSE更有效的东西(甚至不需要SSE2)。使用这个代码做4x4矩阵乘法有效。如果矩阵是按行为主顺序存储的,那么gemm4x4_SSE(A,B,C)也是如此。如果矩阵按列主顺序存储,则按gemm4x4_SSE(B,A,C)存储

void gemm4x4_SSE(float *A, float *B, float *C) {
    __m128 row[4], sum[4];
    for(int i=0; i<4; i++)  row[i] = _mm_load_ps(&B[i*4]);
    for(int i=0; i<4; i++) {
        sum[i] = _mm_setzero_ps();      
        for(int j=0; j<4; j++) {
            sum[i] = _mm_add_ps(_mm_mul_ps(_mm_set1_ps(A[i*4+j]), row[j]), sum[i]);
        }           
    }
    for(int i=0; i<4; i++) _mm_store_ps(&C[i*4], sum[i]); 
}

我们实际上分析了3x4矩阵伪乘法(就好像它是4x4仿射),并发现在SSE3和AVX中,只要两种布局都优化到极限,列主布局和行主布局的差异很小(<10%)。

基准https://github.com/buildaworldnet/IrrlichtBAW/blob/master/examples_tests/19.SIMDmatrixMultiplication/main.cpp