【问题标题】:Store four 16bit integers with SSE intrinsics使用 SSE 内在函数存储四个 16 位整数
【发布时间】:2014-03-29 07:36:43
【问题描述】:

我将四个 32 位浮点数相乘并舍入,然后使用 SSE 内在函数将其转换为四个 16 位整数。我想将四个整数结果存储到一个数组中。使用花车很容易:_mm_store_ps(float_ptr, m128value)。但是我还没有找到任何使用 16 位 (__m64) 整数的指令。

void process(float *fptr, int16_t *sptr, __m128 factor)
{
  __m128 a = _mm_load_ps(fptr);
  __m128 b = _mm_mul_ps(a, factor);
  __m128 c = _mm_round_ps(b, _MM_FROUND_TO_NEAREST_INT);
  __m64 s =_mm_cvtps_pi16(c);
  // now store the values to sptr
}

任何帮助将不胜感激。

【问题讨论】:

    标签: c++ sse intrinsics sse2


    【解决方案1】:

    我个人会避免使用 MMX。此外,我会使用显式存储而不是隐式存储,后者通常仅适用于某些编译器。以下代码适用于 MSVC2012 和 SSE 4.1。

    请注意,fptr 需要 16 字节对齐。如果你在 64 位模式下编译这不是问题,但在 32 位模式下你应该确保它是对齐的。

    #include <stdio.h>
    #include <stdint.h>
    #include <smmintrin.h>
    
    void process(float *fptr, int16_t *sptr, __m128 factor)
    {
      __m128 a = _mm_load_ps(fptr);
      __m128 b = _mm_mul_ps(a, factor);
      __m128i c = _mm_cvttps_epi32(b);
      __m128i d = _mm_packs_epi32(c,c);
      _mm_storel_epi64((__m128i*)sptr, d);
    }
    
    int main() {
        float x[] = {1.0, 2.0, 3.0, 4.0};
        int16_t y[4];
        __m128 factor = _mm_set1_ps(3.14159f);
        process(x, y, factor);
        printf("%d %d %d %d\n", y[0], y[1], y[2], y[3]);
    }
    

    请注意,_mm_cvtps_pi16 不是一个简单的内在函数,英特尔内在指南说“这个内在函数会创建两个或更多指令的序列,并且可能比本机指令执行得更差。考虑这个内在函数的性能影响。”

    这是使用 MMX 版本的汇编输出

    mulps   (%rdi), %xmm0
    roundps $0, %xmm0, %xmm0
    movaps  %xmm0, %xmm1
    cvtps2pi    %xmm0, %mm0
    movhlps %xmm0, %xmm1
    cvtps2pi    %xmm1, %mm1
    packssdw    %mm1, %mm0
    movq    %mm0, (%rsi)
    ret
    

    这是使用 SSE 唯一版本的程序集输出

    mulps   (%rdi), %xmm0
    cvttps2dq   %xmm0, %xmm0
    packssdw    %xmm0, %xmm0
    movq    %xmm0, (%rsi)
    ret
    

    【讨论】:

    • 这正是我想要的!但是应该使用 _mm_packs_epi32 而不是 _mm_packus_epi32 来保留有符号值,还是我错了?
    • 除了 SSE 更快的事实之外,使用 MMX 可能会导致省略 EMMS 的错误(如本例所示),这是一个严重错误(并且当某些错误明显难以诊断时)数百万个周期后不相关的 FP 计算开始出现异常)。对 MMX 说不。
    【解决方案2】:

    使用__m64 类型,您可以适当地转换目标指针:

    void process(float *fptr, int16_t *sptr, __m128 factor)
    {
      __m128 a = _mm_load_ps(fptr);
      __m128 b = _mm_mul_ps(a, factor);
      __m128 c = _mm_round_ps(b, _MM_FROUND_TO_NEAREST_INT);
      __m64 s =_mm_cvtps_pi16(c);
      *((__m64 *) sptr) = s;
    }
    

    与 SSE/AVX 一样,使用 MMX 指令的对齐和未对齐存储之间没有区别;因此,您不需要内在函数来执行存储。

    【讨论】:

    • MSDN 说 x64 处理器不支持 __m64 类型。它到底是什么意思?根据msdn.microsoft.com/en-us/library/08x3t697.aspx
    • @plasmacel:我相信这只是 Visual Studio 的 64 位编译器的限制(不确定它是否是任何类型的 Windows 限制)。我现在使用了在 x86-64 架构机器上使用 MMX 指令的生产代码(在 Linux 上,使用 gcc 或 Intel C++ 构建)。
    • 不使用__m64,您可以简单地坚持使用__m128 并使用_mm_storel_epi64 (MOVQ) 来存储低64 位。今天没有任何充分的理由使用 MMX。
    • @StephenCanon:这将存储两个 32 位值(从低 64 位开始),而不是所有四个 16 位精度的值。
    • 您永远不会通过 MMX 传递数据(通过使用 _mm_cvtps_epi32 + _mm_packs_epi32 而不是 _mm_cvtps_pi16,如 Z Boson 的回答所示;尽管需要两个内在函数而不是一个,但这是实际上更有效)。
    【解决方案3】:

    我认为您可以安全地将其移至通用 64 位寄存器(long long 适用于 Linux LLP64 和 Windows LP64)并自行复制。

    根据我在xmmintrin.h 中读到的内容,gcc 将完美地处理从__m64long long 的演员表。 可以肯定的是,您可以使用_mm_cvtsi64_si64x

    short* f;
    long long b = _mm_cvtsi64_si64x(s);
    f[0] = b >> 48;
    f[1] = b >> 32 & 0x0000FFFFLL;
    f[2] = b >> 16 & 0x000000000FFFFLL;
    f[3] = b & 0x000000000000FFFFLL;
    

    你可以用 union 输入 pune 让它看起来更好,但我想这会属于未定义的行为。

    【讨论】:

    • 我没有找到任何关于 _mm_cvtsi64_si64x 的参考资料。也不是software.intel.com/sites/landingpage/IntrinsicsGuide
    • 正如我在自定义头文件中看到的,它只是简单地实现为强制转换:_mm_cvtsi64_si64x(__m64 __i) { return (long long)__i; }
    猜你喜欢
    • 2023-03-09
    • 1970-01-01
    • 2011-04-27
    • 2013-03-12
    • 2020-04-07
    • 2017-07-12
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    相关资源
    最近更新 更多