使用SSE指令点产品性能

Phi*_*tor 2 optimization performance x86 assembly simd

是它更快地通过的装置计算两个向量的点积dpps指令形式SSE 4.1指令集或通过使用一系列的addps,shufpsmulps从SSE 1?

Chu*_*urn 5

答案可能非常具有上下文性,并且完全取决于在更大的代码流中使用的位置和方式,以及您正在使用的硬件.

从历史上看,当英特尔推出新指令时,他们并没有专注于硬件领域.如果它被充分利用和使用,他们会在后代中使用更多的硬件.因此_mm_dp_ps,与原始ALU性能方面的SSE2方式相比,Penryn并不是特别令人印象深刻.另一方面,它确实需要I-cache中较少的指令,因此当更紧凑的编码表现更好时,它可能会有所帮助.

真正的问题_mm_dp_ps是作为SSE ​​4.1的一部分,你不能指望它在每一台现代PC都得到支持(Valve的Steam硬件调查将游戏玩家约为85%).因此,您最终必须编写受保护的代码路径而不是直线代码,这通常比使用该指令所带来的好处更高.

它是有用的,如果你为一个保证支持它的CPU制作二进制文件.例如,如果您正在构建/arch:AVX(或甚至/arch:AVX2),因为您的目标是像Xbox One这样的固定平台,或者正在构建EXE/DLL的多个版本,您也可以假设也支持SSE 4.1.

这实际上是DirectXMath的作用:

inline XMVECTOR XMVector4Dot( FXMVECTOR V1, FXMVECTOR V2 )
{
#if defined(_XM_NO_INTRINSICS_)

    XMVECTOR Result;
    Result.vector4_f32[0] =
    Result.vector4_f32[1] =
    Result.vector4_f32[2] =
    Result.vector4_f32[3] = V1.vector4_f32[0] * V2.vector4_f32[0] + V1.vector4_f32[1] * V2.vector4_f32[1] + V1.vector4_f32[2] * V2.vector4_f32[2] + V1.vector4_f32[3] * V2.vector4_f32[3];
    return Result;

#elif defined(_M_ARM) || defined(_M_ARM64)

    float32x4_t vTemp = vmulq_f32( V1, V2 );
    float32x2_t v1 = vget_low_f32( vTemp );
    float32x2_t v2 = vget_high_f32( vTemp );
    v1 = vpadd_f32( v1, v1 );
    v2 = vpadd_f32( v2, v2 );
    v1 = vadd_f32( v1, v2 );
    return vcombine_f32( v1, v1 );

#elif defined(__AVX__) || defined(__AVX2__)

    return _mm_dp_ps( V1, V2, 0xff );

#elif defined(_M_IX86) || defined(_M_X64)

    XMVECTOR vTemp2 = V2;
    XMVECTOR vTemp = _mm_mul_ps(V1,vTemp2);
    vTemp2 = _mm_shuffle_ps(vTemp2,vTemp,_MM_SHUFFLE(1,0,0,0));
    vTemp2 = _mm_add_ps(vTemp2,vTemp);
    vTemp = _mm_shuffle_ps(vTemp,vTemp2,_MM_SHUFFLE(0,3,0,0));
    vTemp = _mm_add_ps(vTemp,vTemp2);
    return _mm_shuffle_ps(vTemp,vTemp,_MM_SHUFFLE(2,2,2,2));

#else
    #error Unsupported platform
#endif
}
Run Code Online (Sandbox Code Playgroud)

这当然假设您将在其他向量操作中使用点积的"标量"结果.按照惯例,DirectXMath会在返回向量中返回这些标量"splatted".

请参阅DirectXMath:SSE4.1和SSE4.2

更新:虽然不是很普及,因为SSE/SSE2支持,你可能需要SSE3支持,你是不是与建设情况/arch:AVX还是/arch:AVX2和尝试:

inline XMVECTOR XMVector4Dot(FXMVECTOR V1, FXMVECTOR V2)
{
    XMVECTOR vTemp = _mm_mul_ps(V1,V2);
    vTemp = _mm_hadd_ps( vTemp, vTemp );
    return _mm_hadd_ps( vTemp, vTemp );
}
Run Code Online (Sandbox Code Playgroud)

也就是说,目前尚不清楚hadd在大多数情况下,对于SSE/SSE2添加和改组解决方案而言至少是dot-product的胜利.

  • 在 Intel SnB 系列 CPU 上,“dpps”仍然在 uop 缓存中占用 4 个 uop,这通常是比 L1 I 缓存更宝贵的资源。然而,在这种情况下,它看起来确实比后备方案取得了胜利。与水平求和的“haddps”不同,它只用 4 个 uops 就完成了整个过程。 (2认同)
  • 对于除代码大小之外的所有内容,看起 `haddps`是3 uops,5c延迟(在Haswell上).因此,在完成后,2xhaddps是4次shuffles,2次加法.SSE2版本是3次shuffle和2次添加,9c延迟而不是10次.(不包括mul). (2认同)
  • 顺便说一句,我以前没有看过它,但这是将 `_mm_shuffle_ps` 与 2 个不同的输入变量一起使用的好方法,以避免需要 `movaps` 来保存下一次添加的旧值。然而,其中一个 shuffle 应该是“movhlps”:在 Pentium-M / Merom / K8 上速度更快(其中 shuffle 很慢)。有关一些优化的水平总和,请参阅http://stackoverflow.com/a/35270026/224132。嗯,我认为你可以通过洗牌来避免最后的广播洗牌,以 1 或 2 个额外的 movap 为代价,使最后添加的所有元素产生完整的结果。 (2认同)