int64_t 指针转换为 AVX2 内在 _m256i

Posted

技术标签:

【中文标题】int64_t 指针转换为 AVX2 内在 _m256i【英文标题】:int64_t pointer cast to AVX2 intrinsic _m256i 【发布时间】:2016-07-13 22:09:02 【问题描述】:

您好,我对 AVX2 内部函数有一个奇怪的问题。我创建了一个指向带有 int64_t* 强制转换的 _m256i 向量的指针。然后我通过取消引用指针来分配一个值。奇怪的是,向量变量中没有观察到该值,除非我在它之后运行一些 cout 语句。指针和向量具有相同的内存地址,取消引用指针会产生正确的值,但向量不会。我错过了什么?

// Vector Variable 
__m256i R_A0to3 = _mm256_set1_epi32(0xFFFFFFFF);

int64_t *ptr = NULL;
for(int m=0; m<4; m++)
    // Cast pointer to vector type
    ptr = (int64_t*)&R_A0to3;

    cout<<"ptr_ADDRESS:      "<<ptr<<endl;
    cout<<"&R_A0to3_ADDRESS: "<<&R_A0to3<<endl;

    // access
    ptr[m] = (int64_t) m_array[m];

    // generic function that prints out register
    print_mm256_reg<int64_t>(R_A0to3, "R_A0to3");
    cout<<"m_array: "<< m_array[m]<<std::ends;

    // Additional print statements
    cout<<"ptr[m]: "<< ptr[m]<<std::endl;
    cout<<"ptr[0]: "<< ptr[0]<<std::endl;
    cout<<"ptr[1]: "<< ptr[1]<<std::endl;
    cout<<"ptr[2]: "<< ptr[2]<<std::endl;
    cout<<"ptr[3]: "<< ptr[3]<<std::endl;
    print_mm256_reg<int64_t>(R_A0to3, "R_A0to3");


Output:
 ptr_ADDRESS      0x7ffd9313e880
 &R_A0to3_ADDRESS 0x7ffd9313e880
 m_array: 8
 printing reg -    R_C0to3    -1|  -1|  -1|  -1|
 printing reg -    R_D0to3    -1|  -1|  -1|  -1|

Output with Additional print statements:
ptr_ADDRESS      0x7ffd36359e20
&R_A0to3_ADDRESS 0x7ffd36359e20
printing reg -    R_A0to3     -1|  -1|  -1|  -1|
m_array: 8

ptr[0]: 8
ptr[1]: -1
ptr[2]: -1
ptr[3]: -1
printing reg -    R_A0to3      8|  -1|  -1|  -1|

【问题讨论】:

这是什么编译器?我相信这是 GCC 中的极端案例之一,其中严格混叠违规实际上会导致问题,即使它们不应该这样做。 (SIMD 类型声明为__may_alias__。)您是否尝试过禁用严格混叠? -Wstrict-aliasing 会抱怨吗? @Mysticial:也许编译器放弃了保持变量的一致性,因为程序有未定义的行为? ptr[m] 用于 m=4..9 访问外部 __m256i R_A0to3。无论如何,这是使用向量的愚蠢方式。不要这样做。如果您真的想存储到缓冲区并修改向量,请编写执行此操作的代码并在之后重新加载向量。或者可能使用联合。使用指针转换的类型双关语不是一个好习惯。 @PeterCordes 啊哈,你是对的!我没有看到它超出范围。 @Mystical。在搜索了一些联合之后,我发现了这个内在函数 _mm256_insert_epi64 (__m256i a, __int64 i, const int index),它几乎可以避免这种指针转换。谢谢 -O1/-O2 的差异确实可能指向-fstrict-aliasing。但实际上,如果没有minimal reproducible example,这是没有用的。 【参考方案1】:

当您偶尔需要访问单个元素时,我建议使用 _mm256_extract_epi64_mm256_insert_epi64 内在函数。如果您需要访问向量中的所有元素,请考虑使用_mm256_store_si256_mm256_lddqu_si256 来存储和加载它。这些内在函数不太可能依赖于未定义的行为,并且它们对于正在生成的机器指令是透明的(因此对于性能而言)。

【讨论】:

如果您需要将所有元素作为单独的标量,则存储到本地数组不是一个坏选择。您可能会得到比提取更好的代码。或者使用联合进行类型双关而不是指针转换,因为 IIRC,GNU C 保证基于联合的类型双关有效。 (我认为它在其他不支持 GNU C 扩展的 x86 编译器上也是安全的。) "或者使用联合进行类型双关而不是指针转换,因为 IIRC,GNU C 保证基于联合的类型双关有效。(我认为它在其他 x86 编译器上是安全的) t 也支持 GNU C 扩展。)”在这种情况下,我认为您几乎不知道编译器将如何实现它……可能使用存储和加载,尽管我可能是错的。这可能是也可能不是你想要的。 gcc stores/reloads, clang uses extract instructions。这只是 gcc 错过的优化;但 IDK 多久会修好。显然,任何一种方式的性能都非常低(尤其是存储/修改/重新加载存储转发失败),因此应该主要用于调试打印之类的事情。我没有意识到_mm256_extract_epi64 存在/在上层元素上工作,因为vpextrq 没有,所以这很方便。此外,_mm256_lddqu_si256 毫无意义;只需使用loadu。 (或者 load 如果对齐,我猜)。 " 另外,_mm256_lddqu_si256 是没有意义的;只需使用 loadu。(或者如果对齐,我猜是 load)。我没有遇到过负载比 loadu 或 lddq 更快的情况。我也不知道 lddqu 和 loadu 之间的任何性能差异,但两者存在,英特尔表示 lddqu 可以更快(我从未观察到这一点,但我坚信这一点)。

以上是关于int64_t 指针转换为 AVX2 内在 _m256i的主要内容,如果未能解决你的问题,请参考以下文章

我可以使用内在函数加速类型转换吗?

防止 gcc 将我的 AVX2 内在函数复制到 REP MOVS

AVX2 1x mm256i 32bit 到 2x mm256i 64bit

将 int64_t 转换为 NSInteger

如何使用 avx 指令将 float 向量转换为 short int?

intptr_t 指针