【问题标题】:Initializing an __m128 type from a 64-bit unsigned int从 64 位无符号整数初始化 __m128 类型
【发布时间】:2014-06-22 05:02:47
【问题描述】:

_mm_set_epi64 和类似的 *_epi64 指令似乎使用并依赖于 __m64 类型。我想初始化一个__m128类型的变量,使其高64位为0,低64位设置为x,其中x的类型为uint64_t(或类似的无符号64 位类型)。这样做的“正确”方式是什么?

最好以独立于编译器的方式完成。

【问题讨论】:

  • 这不是微软特有的数据类型吗?
  • @JohnDibling 它是特定于 x86 的。但绝对不是特定于 MS 的。所有主要编译器(VS、GCC、ICC、Clang)都支持它。

标签: c++ sse intrinsics


【解决方案1】:

回答您有关如何将 64 位值加载到 XMM 寄存器的低 64 位同时将高 64 位归零_mm_loadl_epi64(&x)will do exactly what you want 的问题。

关于to _mm_set_epi64 我曾经说过,查看Agner Fog 的Vector Class Library 的源代码可以回答关于SO 上SSE/AVX 的95% 的问题。 Agner 为多个编译器以及 64 位和 32 位实现了这个(来自文件 vectori128.h)。请注意,MSVC 32 位 Agner 的解决方案说“这是低效的,但其他解决方案更糟”。我想这就是神秘主义者所说的“没有好的方法”。

Vec2q(int64_t i0, int64_t i1) {
#if defined (_MSC_VER) && ! defined(__INTEL_COMPILER)
        // MS compiler has no _mm_set_epi64x in 32 bit mode
#if defined(__x86_64__)                                    // 64 bit mode
#if _MSC_VER < 1700
        __m128i x0 = _mm_cvtsi64_si128(i0);                // 64 bit load
        __m128i x1 = _mm_cvtsi64_si128(i1);                // 64 bit load
        xmm = _mm_unpacklo_epi64(x0,x1);                   // combine
#else
        xmm = _mm_set_epi64x(i1, i0);
#endif
#else   // MS compiler in 32-bit mode
        union {
            int64_t q[2];
            int32_t r[4];
        } u;
        u.q[0] = i0;  u.q[1] = i1;
        // this is inefficient, but other solutions are worse
        xmm = _mm_setr_epi32(u.r[0], u.r[1], u.r[2], u.r[3]);
#endif  // __x86_64__
#else   // Other compilers
        xmm = _mm_set_epi64x(i1, i0);
#endif
};

【讨论】:

    【解决方案2】:

    最常见的“标准”内在函数是_mm_set_epi64x

    对于缺少_mm_set_epi64x 的平台,您可以像这样定义替换宏:

    #define _mm_set_epi64x(m0, m1) _mm_set_epi64(_m_from_int64(m0), _m_from_int64(m1))
    

    【讨论】:

    • 对于任何关心 32 位的人来说,这个内在函数只存在于 x64 上。要面向 32 位,需要采用不同的方法。
    • @Mysticial 如果您不介意添加有关如何执行此操作的答案,这可能对追随我的人有所帮助。
    • @Gideon 这不是一个好方法。最好首先避免使用这些设置的内在函数。我发现它们唯一可以接受的地方是编译时常量——在这种情况下,您只需手动将 64 位整数分成两半并使用_mm_set_epi32()
    • @Gideon,我为您的问题发布了答案。
    【解决方案3】:

    我想初始化一个 __m128 类型的变量...其中 x 是 uint64_t 类型的变量

    采用uint64_t 的内在函数是_mm_set_epi64x(而不是采用__m64_mm_set_epi64)。

    我最近在 Solaris 上遇到了这个问题。 Sun Studio 12.3 及更低版本缺少_mm_set_epi64x。它还缺少解决方法,例如 _mm_cvtsi64_si128_m_from_int64

    如果有兴趣,这是我使用的 hack。另一种选择是禁用 SSE2,它不太吸引人(在基准测试中它慢了 3 倍):

    // Sun Studio 12.3 and earlier lack SSE2's _mm_set_epi64 and _mm_set_epi64x.
    #if defined(__SUNPRO_CC) && (__SUNPRO_CC < 0x5130)
    inline __m128i _mm_set_epi64x(const uint64_t a, const uint64_t b)
    {
        union INT_128_64 {
            __m128i   v128;
            uint64_t  v64[2];
        };
    
        INT_128_64 v;
        v.v64[0] = b; v.v64[1] = a; 
        return v.v128;
    }
    #endif
    

    我相信 C++11 可以做一些额外的事情来帮助编译器和性能,比如初始化一个常量数组:

    const INT_128_64 v = {a,b};
    return v.v128;
    

    有一个很大的警告......我相信有undefined behavior,因为使用联合的v64 成员进行写入,然后使用联合的v128 成员进行读取。在 SunCC 下的测试表明编译器正在执行预期(但技术上不正确)的事情。

    我相信您可以使用 memcpy 回避未定义的行为,但这可能会破坏性能。另请参阅 How to swap two __m128i variables in C++03 given its an opaque type and an array? 上的 Peter Cordes 的回答和讨论。

    以下也可能是避免使用非活动联合成员的未定义行为的好选择。但我不确定双关语。

    INT_128_64 v;
    v.v64[0] = b; v.v64[1] = a;
    return *(reinterpret_cast<__m128i*>(v.v64));
    

    编辑(三个月后):Solaris 和 SunCC 不喜欢双关语。它为我们生成了错误的代码,我们不得不将memcpy 的值转换为__m128i。 Unix、Linux、Windows、GCC、Clang、ICC、MSC 都可以。只有 SunCC 给我们带来了麻烦。

    【讨论】:

    • 使用联合的类型双关语比指针转换更可取。根据标准,它们都是未定义的行为,但至少对于 gcc,联合是安全的。指针转换技术对于真正的编译器是不安全的。 (可能使用 SIMD __m128 类型除外,这些类型使用 may_alias 属性或类似属性定义。希望 SunCC 对其进行类似定义。)
    • 如果您不确定基于联合的类型双关语是否安全,值得尝试 memcpy。一些编译器擅长优化它,但正如你所说,我表明并非所有编译器都能生成可接受的代码。 memcpy 是 AFAIK 唯一一种保证 ISO C 和 C++ 可移植的类型双关技术,但据我了解,基于联合的类型双关在现实生活中被广泛使用。
    • @PeterCordes - 看看 SSE2 _mm_loadl_pi,它可能是一个合适的替代品。它允许加载未对齐的 64 位值。使用_mm_loadl_pi 两次中间移位可以避免一些理论问题。
    • 如果您真的希望编译器发出加载指令,而不是 movd xmm0, eaxmovq xmm0, rax 或其他东西,那么也许。但是你绝对不应该使用它两次!看起来最接近您想要的 asm 的内在函数是 __m128i _mm_loadu_si64 (void const* mem_addr)(用 movq xmm0, m64 加载低 64 位,将 xmm 的高 64 位归零)。或者在不支持它的编译器上,_mm_loadl_epi64 也表示它编译为 movq 负载。然后_mm_loadh_pimovhps 加载上半部分。使用 shift 内在函数会很愚蠢。
    • 无论如何,movq / movl/h 可能适用于一对不相邻的 64 位值。顺便说一句,没有指令对 64 位或更小的操作数有对齐要求;你不需要一个特殊的内在。我也可以避免在一对相邻的最近写入的 64 位值上出现存储转发失败停顿,而不是使用 _mm_loadu_si128。 (但是_mm_loadu 的指针转换应该是别名安全的。可能您的转换版本实际上是安全的,但我想我已经看到了一个 SO 问题,其中类似的东西没有达到 OP 的要求。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-05
    • 2011-11-10
    • 2012-02-11
    • 2021-11-25
    • 2012-10-20
    • 1970-01-01
    相关资源
    最近更新 更多