assembly 使用C内部函数从一个ZMM寄存器中提取四个XMM寄存器,反之亦然

vql8enpb  于 2023-04-21  发布在  其他
关注(0)|答案(2)|浏览(151)

有一个一般性的问题moving data between SSE and AVX512 registers。与那个问题相反,现在的问题不是关于汇编指令,而是关于C内部函数。
将两个xmm寄存器插入到一个ymm中有一个内在的方法:__m256_mm256_set_m128(__m128hi,__m128lo),其对应于vinsertf128 instruction,但不存在类似的内在将两个ymm寄存器插入到一个zmm寄存器中,例如假定的__m512_mm512_set_m256(__m256 hi,__m256 lo)。使用汇编指令,当我设置ymm寄存器时(即通过VinsertF128),该操作还明确地清除相应ZMM寄存器的高256位,vinsertf32x8指令的所有intrinsic在输入端已经需要一个512位zmm寄存器,而_mm256_set_m128只返回一个256位ymm寄存器。
从一个zmm寄存器中提取四个xmm寄存器的C内部函数是什么?我不能用定义的寄存器编写汇编函数,我需要将指令与任何可用的寄存器内联。

35g0bw71

35g0bw711#

提取,使用

/* floating point domain */
__m128 _mm512_extractf32x4_ps (__m512 a, int imm8);
__m256 _mm512_extractf32x8_ps (__m512 a, int imm8);

/* integer domain */
__m128i _mm512_extracti64x2_epi64 (__m512i a, int imm8);
__m256i _mm512_extracti64x4_epi64 (__m512i a, int imm8);

要进行汇编,要么遍历内存(例如,存储到一个4 __m128的数组中,然后从中加载),要么使用一系列插入指令。请注意,插入是跨通道操作,因此非常慢。遍历内存可能会更快,您应该测量它。

/* floating point domain */
__m512 _mm512_insertf32x4 (__m512 a, __m128 b, int imm8);
__m512 _mm512_insertf32x8 (__m512 a, __m256 b, int imm8);

/* integer domain */
__m256i _mm256_inserti64x2 (__m256i a, __m128i b, int imm8);
__m512i _mm512_inserti64x4 (__m512i a, __m256i b, int imm8);
kse8i1jr

kse8i1jr2#

我们可以使用下面的intrinsic进行强制转换,它们不产生任何指令:

_mm256_castps128_ps256    __m128  →  __m256
_mm256_castps256_ps128    __m256  →  __m128

_mm512_castps256_ps512    __m256  →  __m512
_mm512_castps512_ps256    __m512  →  __m256

不同类型有类似的intrinsic:

_mm256_castsi256_si128   __m256i  →  __m128i
_mm256_castsi128_si256   __m128i  →  __m256i

此外,如果我们需要在相同大小的各种类型之间进行转换,我们可以使用以下类型转换intrinsic:

_mm256_castps_si256    __m256   →  __m256i
_mm256_castsi256_ps    __m256i  →  __m256

所有造型说明均在www.example.com上列出https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=6371&cats=Cast
因此,将四个xmm寄存器组合成一个zmm寄存器的代码如下:

__m512 m512_combine_m128x4(__m128 x4, __m128 x3, __m128 x2, __m128 x1) 
{
    const __m256 h = _mm256_set_m128(x4, x3);
    const __m256 l = _mm256_set_m128(x2, x1);
    return _mm512_insertf32x8(_mm512_castps256_ps512(l), h, 1);
}

它被转换为两个vinsertf 128指令和一个vinsertf 32 x8指令。
因此,一旦我们使用了强制转换intrinsic,这就太容易了。分割的代码是类似的,使用上面提到的强制转换。然而,为了提取最低位,你可能只是从一个更宽的类型转换到一个更窄的类型,并丢失数据。
以下是使用__m128i和__m512i数据类型从一个zmm寄存器中提取四个xmm寄存器的示例:m512_split_m128x4,它转换为3个汇编指令,并直接从输入zmm寄存器产生所有结果,而没有任何中间指令,这些指令会创建依赖链,如Peter Cordes在评论中所建议的:

void m512_split_m128x4(__m512i r, __m128i &x4, __m128i &x3, __m128i &x2, __m128i &x1) 
{
        x1 = _mm256_castsi256_si128(_mm512_castsi512_si256(r));
        x2 = _mm256_extracti128_si256(_mm512_castsi512_si256(r), 1);
        x3 = _mm256_castsi256_si128(_mm512_extracti32x8_epi32(r, 1));
        x4 = _mm512_extracti32x4_epi32(r, 3);
}

下面是一个更复杂的例子,使用中间m256_split_m128x2函数;但是,它会创建一个依赖关系链:

void m256_split_m128x2(__m256i r, __m128i &hi, __m128i &lo) 
{
    hi = _mm256_extracti128_si256(r, 1);
    lo = _mm256_castsi256_si128(r);
}

void m512_split_m128x4(__m512i r, __m128i &x4, __m128i &x3, __m128i &x2, __m128i &x1) 
{
    const __m256i h = _mm512_extracti32x8_epi32(r, 1);
    const __m256i l = _mm512_castsi512_si256(r);
    m256_split_m128x2(h, x4, x3);
    m256_split_m128x2(l, x2, x1);
}

相关问题