我可以正确比较 avx 中的零寄存器吗?

Posted

技术标签:

【中文标题】我可以正确比较 avx 中的零寄存器吗?【英文标题】:Could I compare to zero register in avx correctly? 【发布时间】:2015-08-18 12:15:38 【问题描述】:

我遇到了 AVX 内部指令 _mm256_testc_pd() 的一个非常奇怪的行为。 这里可以看到这个函数的描述https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=AVX,AVX2&text=test&expand=5432

我在我的代码中使用了 2 次,预计会看到与零寄存器进行比较的类似结果。

#include <immintrin.h>
#include <stdio.h>

int main(void)

    unsigned char arr[32] __attribute__ ((aligned (32)));

    __m256d a, zero;
    int res1, res2;

    memset(arr, 0 , 32);
    arr[0] = 0xff;
    arr[4] = 0xff;
    arr[8] = 0xff;
    arr[12] = 0xff;
    arr[16] = 0xff;
    arr[20] = 0xfd;
    arr[24] = 0xff;
    arr[28] = 0xff;


    zero = _mm256_setzero_pd();
    a = _mm256_load_pd((double *)arr);
    res1 = _mm256_testc_pd(zero, a);
    printf("res1 = %d\n" , res1);

    memset(arr, 0xff, 32);
    a = _mm256_load_pd((double *)arr);
    res2 =  _mm256_testc_pd(zero, a);
    printf("res2 = %d\n" , res2);
    return 0;

结果我得到了

res1 = 1
res2 = 0

有人知道为什么会这样吗?我认为在这两种情况下a 都不等于零。

更新 在 cmets 讨论后,我的问题得到了解决,但我对函数 _mm256_testc_si256 和 _mm256_testz_si256 有一点误解

例如:

unsigned char arr[32] __attribute__ ((aligned (32)));

    __m256d a, zero;

    int res1, res2;
    memset(arr, 0 , 32);

    arr[0] = 0x80;

    zero = _mm256_setzero_pd();
    a = _mm256_load_pd((double *)arr);

    res1 = _mm256_testc_si256(_mm256_castpd_si256(zero),_mm256_castpd_si256(a));
    res2 = _mm256_testz_si256(_mm256_castpd_si256(zero),_mm256_castpd_si256(a));
    printf("res1 = %d\n" , res1);
    printf("res2 = %d\n" , res2);

输出是

res1 = 0
res2 = 1

而且我认为只有第一个是正确的。那么为什么这个函数会产生不同的输出呢?

【问题讨论】:

arr[20] = 0xfd; 是故意的吗? 没关系,可能是0xff,同样的行为。 这条指令真的能如你所愿吗?我宁愿说你想使用 _mm256_cmp_pd() 来比较值。 我认为你是对的,但你知道 _mm256_cmp_pd () 的第三个参数应该是什么吗? @AlekseyM:你可以通过_CMP_EQ_OQ (== 0)。 【参考方案1】:

_mm256_testc_pd 仅对每个双精度元素的 符号位 进行操作,因此观察到的行为是正确的。如果您想在每个元素中测试双精度 values,则首先使用合适的比较指令(例如 _mm256_cmp_pd 和适当的 _CMP_xxx 参数),然后使用 _mm256_testc_pd_mm256_testz_pd,具体取决于根据您的具体要求。

【讨论】:

@AlekseyM:您可以使用整数 PTEST 测试 FP 零,方法是测试除符号位之外的所有掩码。例如ones=_mm256_cmpeq_epi64(same, same); testmask=_mm256_srli_epi64(ones, 1); return _mm256_testz_si256(a, ones); 测试除符号位位置以外的任何地方的非零位。不过,我不建议将这种特殊情况优化用于实际使用! @AlekseyM:如果要测试整个寄存器中的任何设置位,请使用 _mm256_testz_si256(a, a),就像在整数寄存器上使用 test eax, eax / jz 一样。确保您确实希望在函数中将 -00 区别对待。如果您确定,那么这是检查 FP 值的二进制表示中是否存在任何非零位的最直接方法。 @AlekseyM:您正在针对zero 进行测试,而不是针对自身。 PTEST (testz_si256) 在其 args 之间进行 AND,如果所有位都为零,则设置 z 标志。它还执行src1 AND (NOT src2) 并设置c 标志,如果其中的所有位都为零。无论a 如何,您的testc总是为零。编辑:我认为您在我完成输入之前删除了您的评论。 :P @AlekseyM:您最多可以编辑 cmets 5 分钟,而不是删除和搞乱排序。 @AlekseyM:您不需要针对 0 进行显式测试,只需使用 Peter 之前给出的示例,即 _mm256_testz_si256(a, a) - 它更高效(不需要零向量)且更直观/可读。【参考方案2】:

感谢 Peter Cordes 和所有其他人,我的问题的正确(也是最漂亮)的解决方案是

res = _mm256_testz_si256(_mm256_castpd_si256(a), _mm256_castpd_si256(a))

【讨论】:

以上是关于我可以正确比较 avx 中的零寄存器吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 AVX512 寄存器 zmm26 中的 QuadWord 写入 rax 寄存器?

使用 x64 SSE / AVX 寄存器进行字符串反转

AVX2:AVX 寄存器中 8 位元素的 CountTrailingZeros

使用英特尔 AVX 进行掩码洗牌

将 2x4 64b 结构的第一行加载到 AVX2 的 256b 寄存器中的最快方法是啥?

从填充为 0 的数组加载到 256 位 AVX2 寄存器