与SSE有效的4x4matrix向量乘法:水平添加和点积 – 有什么意义?

我试图find使用SSE与vector(u)进行4×4matrix(M)乘法运算的最有效的实现。 我的意思是Mu = v。

据我所知,有两个主要方法可以解决这个问题:

method 1) v1 = dot(row1, u), v2 = dot(row2, u), v3 = dot(row3, u), v4 = dot(row4, u) method 2) v = u1 col1 + u2 col2 + u3 col3 + u4 col4. 

方法2在SSE2中很容易实现。 方法一可以用SSE3中的水平加法指令或SSE4中的点积指令来实现。 但是,在我所有的testing中,方法2总是优于方法1。

一个地方,我虽然方法1将有一个优势是在一个3×4的matrix,例如仿射变换。 在这种情况下,最后一个点产品是不必要的。 但即使在这种情况下,4x4matrix上的方法2比3x4matrix上的方法1更快。 我发现在4x4matrix上唯一比方法2快的方法是在4x3matrix上的方法2。

那么水平添加和点积指令有什么意义呢? 实际上,点生产指令在这种情况下performance最差。 也许这与数据格式有关? 如果不能定义matrix是如何sorting的,那么转置是必要的,在这种情况下,方法1可能会更好?

见下面的一些代码。

 __m128 m4x4v_colSSE(const __m128 cols[4], const __m128 v) { __m128 u1 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(0,0,0,0)); __m128 u2 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(1,1,1,1)); __m128 u3 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(2,2,2,2)); __m128 u4 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(3,3,3,3)); __m128 prod1 = _mm_mul_ps(u1, cols[0]); __m128 prod2 = _mm_mul_ps(u2, cols[1]); __m128 prod3 = _mm_mul_ps(u3, cols[2]); __m128 prod4 = _mm_mul_ps(u4, cols[3]); return _mm_add_ps(_mm_add_ps(prod1, prod2), _mm_add_ps(prod3, prod4)); } __m128 m4x4v_rowSSE3(const __m128 rows[4], const __m128 v) { __m128 prod1 = _mm_mul_ps(rows[0], v); __m128 prod2 = _mm_mul_ps(rows[1], v); __m128 prod3 = _mm_mul_ps(rows[2], v); __m128 prod4 = _mm_mul_ps(rows[3], v); return _mm_hadd_ps(_mm_hadd_ps(prod1, prod2), _mm_hadd_ps(prod3, prod4)); } __m128 m4x4v_rowSSE4(const __m128 rows[4], const __m128 v) { __m128 prod1 = _mm_dp_ps (rows[0], v, 0xFF); __m128 prod2 = _mm_dp_ps (rows[1], v, 0xFF); __m128 prod3 = _mm_dp_ps (rows[2], v, 0xFF); __m128 prod4 = _mm_dp_ps (rows[3], v, 0xFF); return _mm_shuffle_ps(_mm_movelh_ps(prod1, prod2), _mm_movelh_ps(prod3, prod4), _MM_SHUFFLE(2, 0, 2, 0)); } 

水平添加和点积指令是复杂的:它们被分解成多个简单的微操作,由处理器执行,就像简单的指令一样。 水平添加和点积指令到微操作的确切分解是针对处理器的,但是对于最近的英特尔处理器,水平添加被分解成2个SHUFFLE + 1个ADD微操作,并且dot乘积被分解成1个MUL + 1个SHUFFLE + 2个ADD微操作。 除了大量的微操作之外,本指令还强调了处理器stream水线中的指令译码器:英特尔处理器每个周期只能解码一个这样复杂的指令(与4个简单指令相比)。 在AMD推土机上,这些复杂指令的相对成本更高。