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

SSE half loads (_mm_loadh_pi / _mm_loadl_pi) issue warnings

本文关键字:mm pi 警告 loadl loadh 负载 SSE      更新时间:2023-10-16

我从英特尔网站上借用了矩阵反演算法:http://download.intel.com/design/PentiumIII/sml/24504301.pdf

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

但是这种加载方法发出编译警告:"tmp1 在此函数中使用未初始化"

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

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

但是,详细了解代码的功能表明 tmp1 不需要初始化。初始化会稍微减慢代码的速度(这是可测量的)。

您是否知道如何删除警告,如果可能的话,以便携式方式,而无需初始化 tmp1?

这就是

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

除此之外,您还需要两个浮点数的movsd加载,这些浮点数零扩展并打破了对寄存器旧值的错误依赖,而不是合并的movlps (除非你正在构建一个粗糙的旧 32 位 CPU,它有 SSE1 但没有 SSE2,就像你的代码最初编写的 PIII 一样。

投射到double *并使用_mm_load_sd . 您不会自己取消引用它,只是通过 _mm_load_sd ,所以我认为这仍然是 100% 严格混叠安全的。 不过,它在实践中适用于当前的编译器! 如果事实证明不安全,_mm_loadl_epi64movq)需要一个__m128i const*参数(奇怪,因为它只加载低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 没有错误的依赖。 (Godbolt编译器资源管理器上的source+asm输出。

我尝试了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