SSE 半负载 (_mm_loadh_pi / _mm_loadl_pi) 发出警告

Posted

技术标签:

【中文标题】SSE 半负载 (_mm_loadh_pi / _mm_loadl_pi) 发出警告【英文标题】:SSE half loads (_mm_loadh_pi / _mm_loadl_pi) issue warnings 【发布时间】:2013-09-11 14:10:01 【问题描述】:

我从 Intel 网站借用了一个矩阵求逆算法: http://download.intel.com/design/PentiumIII/sml/24504301.pdf

它使用 _mm_loadh_pi 和 _mm_loadl_pi 加载 4x4 矩阵系数并同时进行部分洗牌。我的应用程序的性能提升非常显着,如果我使用 _mm_load_ps 对矩阵进行经典加载/随机播放,它会稍微慢一些。

但是这种加载方法会发出编译警告:“tmp1 is used uninitialized in this function”

__m128 tmp1;
tmp1 = _mm_loadh_pi(_mm_loadl_pi(tmp1, (__m64*)(src)), (__m64*)(src+ 4));

这在某种程度上是有道理的,因为 tmp1 是 _mm_loadl_pi 的输入参数,并且会影响结果。

但是,仔细查看代码所做的事情表明 tmp1 不需要初始化。并且初始化会稍微减慢代码的速度(这是可测量的)。

您是否知道如何以可移植的方式删除警告,而无需初始化 tmp1?

【问题讨论】:

【参考方案1】:

这就是_mm_undefined_ps 的用途(但它实际上只有助于英特尔编译器的代码生成。其他编译器通常以与_mm_setzero_ps 类似的方式对待它)。

除此之外,您需要一个 movsd 加载两个零扩展并打破对寄存器旧值的错误依赖的浮点数,而不是合并的 movlps (除非您正在为具有 SSE1 但没有 SSE2 的硬壳旧 32 位 CPU 构建,例如您的代码最初编写的 PIII。)

转换为 double * 并使用 _mm_load_sd。您自己并没有取消引用它,只能通过_mm_load_sd,所以我认为这仍然是 100% 严格混叠安全的。不过,它实际上适用于当前的编译器! 如果结果证明它不安全,_mm_loadl_epi64 (movq) 采用 __m128i const* arg(很奇怪,因为它只加载低 64 位,但它是一个 may_alias 类型,您绝对可以安全地使用它来读取任何其他类型,例如 char*。)

static inline
__m128 stride_gather(float *src) 
    __m128 tmp1 = _mm_castpd_ps(_mm_load_sd((const double*)src));  // movsd
    tmp1 = _mm_loadh_pi(tmp1, (const __m64*)(src+4));              // movhps
    return tmp1;

gcc7 及更高版本使用movq 而不是movsd,这很奇怪,但我认为这很好。在最坏的情况下,在某些旧 CPU 上将额外的旁路延迟延迟周期作为 movhps 的输入,但不会降低吞吐量。

其他 3 个主要编译器 (clang/ICC/MSVC) 都将其编译为预期的 movsd / movhps,而不会错误地依赖 xmm0 的旧值。 (source+asm output on the Godbolt compiler explorer.)

【讨论】:

【参考方案2】:

我尝试了 3 个编译器:MS Visual Studio 2012、gcc481 和 Intel icl 13.1。正如您指出的那样,他们都发出警告。我发现 gcc 和 MS 都自动为 tmp1 生成初始化代码,即使它们警告缺少初始化也是如此。 MS 编译器生成不希望的内存访问:movaps xmm0,xmmword ptr [rsp]。 Gcc 生成更高效的xorps xmm0,xmm0。因此,在 gcc 的情况下,添加 tmp1=_mm_setzero_ps() 会消除警告并生成与不添加完全相同的代码。在 MS 的情况下,添加 tmp1=_mm_setzero_ps() 会使代码更短并且可能更快。只有英特尔编译器足够聪明,可以避免不必要的初始化。以下是 MS 和 gcc 编译器可能的解决方法:

    __m128 tmp1 = _mm_loadh_pi(_mm_load_ps (src), (__m64*)(src + 4));

代码生成是:

movaps      xmm0,xmmword ptr [rcx]
movhps      xmm0,qword ptr [rcx+10h]

它看起来更短,但应该进行基准测试以确保它更快。

09/12/2013:不同警告抑制思路的测试代码:

#include <xmmintrin.h>
#include <stdint.h>
#include <stdio.h>

//---------------------------------------------------------------------------
// original code from http://download.intel.com/design/PentiumIII/sml/24504301.pdf
__m128 func1 (float *src)
    
    __m128 tmp1;
    tmp1 = _mm_loadh_pi(_mm_loadl_pi(tmp1, (__m64*)(src)), (__m64*)(src+ 4));
    return tmp1;
    

//---------------------------------------------------------------------------
// original code plus tmp1 initialization
__m128 func2 (float *src)
    
    __m128 tmp1 = _mm_loadh_pi(_mm_loadl_pi (_mm_setzero_ps (), (__m64*)(src)), (__m64*)(src + 4));
    return tmp1;
    

//---------------------------------------------------------------------------
// use redundant load to eliminate warning 
__m128 func3 (float *src)
    
    __m128 tmp1 = _mm_loadh_pi(_mm_load_ps (src), (__m64*)(src + 4));
    return tmp1;
    

//---------------------------------------------------------------------------

static void dump (void *data)
    
    float *f16 = data;
    int index;

    for (index = 0; index < 4; index++)
        printf ("%g ", f16 [index]);
    printf ("\n");
    

//---------------------------------------------------------------------------

int main (void)
    
    float f [8] = 1, 2, 3, 4, 5, 6, 7, 8;
    __m128 tmp;

    tmp = func1 (f);
    dump (&tmp);
    tmp = func2 (f);
    dump (&tmp);
    tmp = func3 (f);
    dump (&tmp);
    return 0;
    

构建命令:

gcc  -O3 -Wall -Wfatal-errors sample.c -osample.exe
objdump -Mintel --disassemble sample.exe > disasm.txt

cl -Ox -Zi -W4 sample.c
dumpbin -disasm -symbols sample.exe > disasm.txt

icl -Ox -Zi sample.c                                           
dumpbin -disasm -symbols sample.exe > disasm.txt                  

【讨论】:

该死的,我没有用 gcc 检查生成的代码,只是尝试添加一个初始化,这让代码变慢了一点。也许这只是一些测量噪音。我会重试。 gcc 4.8.0 mingw64(rubenvb 构建) 我将尝试对 load_ps 版本进行基准测试。它还需要 src 的 16 字节对齐,而 loadl_pi 不需要。使用 loadu_ps 肯定会更慢。

以上是关于SSE 半负载 (_mm_loadh_pi / _mm_loadl_pi) 发出警告的主要内容,如果未能解决你的问题,请参考以下文章

__memcpy_sse2_unaligned - 这是啥意思?

SSE:从 const __m128 * 转换为 const float *

在 Visual Studio 中检测 SSE/SSE2 指令集的可用性

仅使用 SSE3 替换 _mm_cvtepi16_epi32

SSE:将 __m128 转换为浮点数

__m128, SSE4 中最大绝对值的符号