为什么我的直接四元数乘法比SSE快?

Hay*_*ach 6 c++ optimization sse quaternions

我已经经历了一些不同的四元数乘法实现,但是我很惊讶地发现参考实现到目前为止是我最快的.这是有问题的实施:

inline static quat multiply(const quat& lhs, const quat& rhs)
{
    return quat((lhs.w * rhs.x) + (lhs.x * rhs.w) + (lhs.y * rhs.z) - (lhs.z * rhs.y),
                (lhs.w * rhs.y) + (lhs.y * rhs.w) + (lhs.z * rhs.x) - (lhs.x * rhs.z),
                (lhs.w * rhs.z) + (lhs.z * rhs.w) + (lhs.x * rhs.y) - (lhs.y * rhs.x),
                (lhs.w * rhs.w) - (lhs.x * rhs.x) - (lhs.y * rhs.y) - (lhs.z * rhs.z));
}
Run Code Online (Sandbox Code Playgroud)

我尝试了一些其他的实现,一些使用SSE,一些不使用.以下是一个这样的SSE实现的示例,基本上是从Bullet Physics使用的库中复制的:

inline static __m128 multiplynew(__m128 lhs, __m128 rhs)
{
    __m128 qv, tmp0, tmp1, tmp2, tmp3;
    __m128 product, l_wxyz, r_wxyz, xy, qw;
    vec4 sw;

    tmp0 = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(3, 0, 2, 1));
    tmp1 = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 1, 0, 2));
    tmp2 = _mm_shuffle_ps(lhs, lhs, _MM_SHUFFLE(3, 1, 0, 2));
    tmp3 = _mm_shuffle_ps(rhs, rhs, _MM_SHUFFLE(3, 0, 2, 1));
    qv = _mm_mul_ps(_mm_splat_ps(lhs, 3), rhs);
    qv = _mm_madd_ps(_mm_splat_ps(rhs, 3), lhs, qv);
    qv = _mm_madd_ps(tmp0, tmp1, qv);
    qv = _mm_nmsub_ps(tmp2, tmp3, qv);
    product = _mm_mul_ps(lhs, rhs);
    l_wxyz = _mm_sld_ps(lhs, lhs, 12);
    r_wxyz = _mm_sld_ps(rhs, rhs, 12);
    qw = _mm_nmsub_ps(l_wxyz, r_wxyz, product);
    xy = _mm_madd_ps(l_wxyz, r_wxyz, product);
    qw = _mm_sub_ps(qw, _mm_sld_ps(xy, xy, 8));

    sw.uiw = 0xffffffff;
    return _mm_sel_ps(qv, qw, sw);
}
Run Code Online (Sandbox Code Playgroud)

在启用了优化的发布模式下,我的简单参考实现比子弹的SSE实现快70%-90%.在没有优化的调试模式下,它的运行速度提高了3倍.

我的第一个问题是,为什么会发生这种情况?

我的第二个问题是,有什么方法可以优化我的四元数 - 四元数乘法程序吗?我不想处理汇编,但我在其他地方使用sse内在函数.

(顺便说一下,如果重要的话,我的四元数的数据存储被定义为union { __m128 data; struct { float x, y, z, w; }; float f[4]; };)

我看了一下反汇编.这是multiply(快速非sse)的反汇编:

00EC9940  movaps      xmm3,xmmword ptr [esp+0D0h]  
00EC9948  movaps      xmm2,xmmword ptr [esp+0C0h]  
00EC9950  movaps      xmm4,xmm3  
00EC9953  mulss       xmm4,xmm5  
00EC9957  movaps      xmm0,xmm2  
00EC995A  mulss       xmm0,xmm6  
00EC995E  mulss       xmm3,xmm1  
00EC9962  addss       xmm4,xmm0  
00EC9966  movss       xmm0,dword ptr [esp+40h]  
00EC996C  mulss       xmm0,xmm1  
00EC9970  addss       xmm4,xmm0  
00EC9974  movss       xmm0,dword ptr [esp+0F0h]  
00EC997D  mulss       xmm0,xmm7  
00EC9981  subss       xmm4,xmm0  
00EC9985  movss       xmm0,dword ptr [esp+0F0h]  
00EC998E  mulss       xmm0,xmm6  
00EC9992  addss       xmm3,xmm0  
00EC9996  movaps      xmm0,xmm2  
00EC9999  movaps      xmm2,xmmword ptr [esp+40h]  
00EC999E  mulss       xmm0,xmm7  
00EC99A2  addss       xmm3,xmm0  
00EC99A6  movaps      xmm0,xmm2  
00EC99A9  mulss       xmm0,xmm5  
00EC99AD  mulss       xmm2,xmm6  
00EC99B1  subss       xmm3,xmm0  
00EC99B5  movss       xmm0,dword ptr [esp+0D0h]  
00EC99BE  mulss       xmm0,xmm7  
00EC99C2  addss       xmm2,xmm0  
00EC99C6  movss       xmm0,dword ptr [esp+0F0h]  
00EC99CF  mulss       xmm0,xmm5  
00EC99D3  addss       xmm2,xmm0  
00EC99D7  movss       xmm0,dword ptr [esp+0C0h]  
00EC99E0  mulss       xmm0,xmm1  
00EC99E4  movss       xmm1,dword ptr [esp+0D0h]  
00EC99ED  mulss       xmm1,xmm6  
00EC99F1  subss       xmm2,xmm0  
00EC99F5  movss       xmm0,dword ptr [esp+0C0h]  
00EC99FE  mulss       xmm0,xmm5  
00EC9A02  movaps      xmm5,xmmword ptr [esp+50h]  
00EC9A07  unpcklps    xmm4,xmm2  
00EC9A0A  subss       xmm1,xmm0  
00EC9A0E  movss       xmm0,dword ptr [esp+0F0h]  
00EC9A17  mulss       xmm0,xmm5  
00EC9A1B  subss       xmm1,xmm0  
00EC9A1F  movss       xmm0,dword ptr [esp+40h]  
00EC9A25  mulss       xmm0,xmm7  
00EC9A29  subss       xmm1,xmm0  
00EC9A2D  unpcklps    xmm3,xmm1  
00EC9A30  unpcklps    xmm4,xmm3  
00EC9A33  movaps      xmm5,xmm4  
00EC9A36  movaps      xmmword ptr [esp+30h],xmm5  
00EC9A3B  dec         eax  
00EC9A3C  je          SDL_main+58Ah (0EC9A5Ah)  
Run Code Online (Sandbox Code Playgroud)

这是multiplynew(慢速sse)的反汇编:

00329BF3  movaps      xmm6,xmm5  
00329BF6  mulps       xmm6,xmm1  
00329BF9  movaps      xmm0,xmm5  
00329BFC  mov         dword ptr [esp+6Ch],0FFFFFFFFh  
00329C04  shufps      xmm0,xmm5,93h  
00329C08  movaps      xmm1,xmm5  
00329C0B  mulps       xmm4,xmm0  
00329C0E  movaps      xmm0,xmmword ptr [esp+110h]  
00329C16  movaps      xmm3,xmm6  
00329C19  shufps      xmm1,xmm5,0FFh  
00329C1D  mulps       xmm1,xmmword ptr [esp+40h]  
00329C22  movaps      xmm7,xmmword ptr [esp+60h]  
00329C27  addps       xmm3,xmm4  
00329C2A  mulps       xmm0,xmm5  
00329C2D  subps       xmm6,xmm4  
00329C30  shufps      xmm3,xmm3,4Eh  
00329C34  addps       xmm1,xmm0  
00329C37  movaps      xmm0,xmm5  
00329C3A  shufps      xmm0,xmm5,0C9h  
00329C3E  subps       xmm6,xmm3  
00329C41  mulps       xmm0,xmmword ptr [esp+120h]  
00329C49  shufps      xmm5,xmm5,0D2h  
00329C4D  mulps       xmm5,xmmword ptr [esp+0C0h]  
00329C55  andps       xmm6,xmmword ptr [esp+60h]  
00329C5A  addps       xmm1,xmm0  
00329C5D  subps       xmm1,xmm5  
00329C60  andnps      xmm7,xmm1  
Run Code Online (Sandbox Code Playgroud)

我测试速度的方式是使用:

timer.update();
for (uint i = 0; i < 1000000; ++i)
{
    temp1 = quat::multiply(temp1, q1);
}
timer.update();
printf("1M calls to multiplyOld took %fs.\n", timer.getDeltaTime());
Run Code Online (Sandbox Code Playgroud)

(timer.getDeltaTime()返回上次调用timer.update()和在此之前调用timer.update()的时间之间经过的时间(以秒为单位).)

为什么我的非sse版本运行得更快,尽管有更多的指令..?我在阅读拆卸错误还是什么?

编辑:我发现当我在x64中编译时,sse版本比非sse版本运行得更快.

Tim*_*lds 3

如果我不得不做出大胆的猜测,我会说这是因为multiply在指令级别高度可并行化,而multiplynew本质上是非常串行的,这是抑制性的。