2015-07-19 104 views
0

作爲一種學習練習,我正在努力加快在各種體系結構上使用SIMD的矩陣乘法代碼。我對SSE2的3D矩陣乘法碼有一個奇怪的問題,它的性能在兩個極端之間跳躍,大約5ms(預期)或100萬次操作的〜100ms。這個SSE2代碼爲什麼執行不一致?

這段代碼所做的唯一不好的事情就是未對齊的存儲/加載和最後的黑客來將向量存儲到內存中而沒有第四個元素踐踏內存。這可以解釋一些性能差異,但性能差異如此之大的事實讓我懷疑我錯過了一些重要的東西。

我已經嘗試了幾件事情,但在睡眠後我會再試一次。

查看下面的代碼。 m_matrix變量在16字節邊界上對齊。

void Matrix3x3::MultiplySSE2(Matrix3x3 &other, Matrix3x3 &output) 
{ 
    __m128 a_row, r_row; 
    __m128 a1_row, r1_row; 
    __m128 a2_row, r2_row; 

    const __m128 b_row0 = _mm_load_ps(&other.m_matrix[0]); 
    const __m128 b_row1 = _mm_loadu_ps(&other.m_matrix[3]); 
    const __m128 b_row2 = _mm_loadu_ps(&other.m_matrix[6]); 

    // Perform dot products with first row 
    a_row = _mm_set1_ps(m_matrix[0]); 
    r_row = _mm_mul_ps(a_row, b_row0); 
    a_row = _mm_set1_ps(m_matrix[1]); 
    r_row = _mm_add_ps(_mm_mul_ps(a_row, b_row1), r_row); 
    a_row = _mm_set1_ps(m_matrix[2]); 
    r_row = _mm_add_ps(_mm_mul_ps(a_row, b_row2), r_row); 

    _mm_store_ps(&output.m_matrix[0], r_row); 

    // Perform dot products with second row 
    a1_row = _mm_set1_ps(m_matrix[3]); 
    r1_row = _mm_mul_ps(a1_row, b_row0); 
    a1_row = _mm_set1_ps(m_matrix[4]); 
    r1_row = _mm_add_ps(_mm_mul_ps(a1_row, b_row1), r1_row); 
    a1_row = _mm_set1_ps(m_matrix[5]); 
    r1_row = _mm_add_ps(_mm_mul_ps(a1_row, b_row2), r1_row); 

    _mm_storeu_ps(&output.m_matrix[3], r1_row); 

    // Perform dot products with third row 
    a2_row = _mm_set1_ps(m_matrix[6]); 
    r2_row = _mm_mul_ps(a2_row, b_row0); 
    a2_row = _mm_set1_ps(m_matrix[7]); 
    r2_row = _mm_add_ps(_mm_mul_ps(a2_row, b_row1), r2_row); 
    a2_row = _mm_set1_ps(m_matrix[8]); 
    r2_row = _mm_add_ps(_mm_mul_ps(a2_row, b_row2), r2_row); 

    // Store only the first 3 elements in a vector so we dont trample memory 
    _mm_store_ss(&output.m_matrix[6], _mm_shuffle_ps(r2_row, r2_row,  _MM_SHUFFLE(0, 0, 0, 0))); 
    _mm_store_ss(&output.m_matrix[7], _mm_shuffle_ps(r2_row, r2_row, _MM_SHUFFLE(1, 1, 1, 1))); 
    _mm_store_ss(&output.m_matrix[8], _mm_shuffle_ps(r2_row, r2_row, _MM_SHUFFLE(2, 2, 2, 2))); 
} 
+1

做100萬次測試幾次,並獲得平均時間。只做一次測試會給你帶來不可靠的結果。 – CoffeeandCode

+0

@CoffeeandCode我正在將平均數作爲測試平臺的一部分。與具有SSE2的2D和4D矩陣相比,平均時間仍然是一個異常。統計不能解釋步進行爲,代碼錯誤。 – BlamKiwi

+0

Idk,男人。如果有效,我不會說它有問題。你怎麼做一個未對齊的商店btw? – CoffeeandCode

回答

2

這樣的性能聽起來像你的數據可能是有時穿越頁面線,而不僅僅是緩存線。如果您正在測試許多不同矩陣的緩衝區,而不是重複使用同一個小矩陣,也許其他CPU核心上運行的其他程序正在將緩衝區從L3中推出?

在你的代碼的性能問題(不解釋因子的-20的變化,這些應始終緩慢。):

_mm_set1_ps(m_matrix[3])等將是一個問題。需要pshufdmovaps + shufps來廣播一個元素。不過,我認爲這對於matmuls來說是不可避免的。

存儲最後3個元素而沒有寫入結尾:嘗試PALIGNR將最後一行的前一行的最後一個元素存入到reg中。然後你可以做一個單獨的未對齊的商店,它與前面的商店重疊。這是很少洗牌,並且可能比movss/extractps/extractps更快。

如果您想嘗試用更少的未對齊16B店的東西,嘗試movss,洗牌或由4個字節(psrldq又名_mm_bsrli_si128),然後movqmovsd右移的最後8個字節存儲一氣呵成。 (字節方式轉變是相同的執行端口洗牌上,不像每個元素位移)

爲什麼你做 _mm_shuffle_psshufps)?對於最後一行的第一列,低位元素已經是您想要的元素。無論如何,我認爲extractps比shuffle + store更快,在non-AVX中,保留來源不被shufps破壞的源碼需要採取行動。 pshufd將工作。)

+0

另一件需要牢記的事情是,它確實取決於*哪些指令集可以使用。只支持SSE/SSE2是有限制的,但具有廣泛的兼容性 - 例如,x64 native需要它,所以支持x64的所有CPU必須支持它。但是,還有很多[有用的指令集](http://blogs.msdn.com/b/chuckw/archive/2012/09/11/directxmath-sse-sse2-and-arm-neon.aspx) SSE2。 –

+0

是的,'shufps'的AVX 3操作數版本可以更有效地播放單個元素。你可以做2 16B負載和左邊的8B負載,所以'm_matrix []'在3個寄存器中。儘管使用AVX,內存中的VBROADCASTSS只是一個單獨的uop,根本不需要shuffle端口。 '_mm_set1_ps'用gcc編譯成AVX。 FMA也會減少uops的數量。 –

+0

@PeterCordes我會記住這一點,但我會放棄這個實現並嘗試另一種方法。 – BlamKiwi