c++ - 与 __m256i 和 std::vector 相互转换
问题描述
我想在__m256i
实例和std::vector<uint32_t>
实例之间进行转换(正好包含 8 个元素)。
到目前为止,我想出了这个:
using vu32 = std::vector<uint32_t>;
__m256i v2v(const vu32& in) {
assert(in.size() == 8);
return _mm256_loadu_si256(reinterpret_cast<const __m256i*>(in.data()));
}
vu32 v2v(__m256i in) {
vu32 out(8);
_mm256_storeu_si256(reinterpret_cast<__m256i*>(out.data()), in);
return out;
}
安全吗?
有没有更惯用的方法来做到这一点?
解决方案
那么首先,SIMD 向量和std::vector
彼此基本上没有任何关系。我知道你已经知道这一点,但未来的读者应该仔细考虑这是否真的是他们想要做的事情。
它是安全的; .data()
必须返回一个可以在任何有效索引处读取或写入的指针。std::vector
考虑到真实库的实现细节,这在实践中肯定是安全的。就纸上标准而言,我很确定摘要中的内容。
从评论来看,您似乎担心严格混叠 UB。
may_alias
通过指针类型(包括char*
or )读/写其他对象__m256i*
是可以的。 memcpy(&a, &b, sizeof(a))
是修改a
via的对象表示的常见示例char*
。memcpy 本身并没有什么特别之处。由于char*
别名特殊情况,这是明确定义的。
may_alias
是一个 GNU C 扩展,它允许您定义除char
允许别名之外的类型char*
。GNU C 对__m128
/的定义__m256i
是根据 GNU C 本机向量,如typedef long long __m256i __attribute((vector_size(32), may_alias));
其他 C++ 实现(如 MSVC)定义__m256i
不同,但英特尔内在 API 保证在char*
/memcpy
将是任何情况下,将向量指针别名到其他类型是合法的。
另请参阅硬件矢量指针和相应类型之间的“重新解释转换”是未定义的行为吗?
另外:SSE:_mm_load/store 与使用直接指针访问之间的区别 - loadu
/storeu
就像在取消引用之前强制转换向量类型的一个aligned(1)
版本。因此,所有关于指针和别名的推理都适用于将指针传递给_mm_storeu
,而不仅仅是直接取消引用。
惯用语;可以肯定,这看起来像非常惯用的 C++。我可能仍然使用带有内在函数的 C 风格强制转换,只是因为reinterpret
阅读时间太长,而且整数向量设计不佳的内在函数 API 到处都需要它。也许 si256 load/loadu 和 store/storeu 的模板化包装函数是合适的,它可以转换为任何指针类型__m256i*
或const __m256i*
从任何指针类型转换。
不过,我可能更喜欢将__m256i
元素传递给 的构造函数的东西out
,以阻止愚蠢的编译器可能将内存归零然后存储向量。但希望这不会发生。
在实践中 gcc 和 clang 在存储向量之前确实将死存储优化到零 8 个元素。任何使用迭代器构造函数的尝试都会使事情变得更糟,在存储/重新加载到堆栈(周围)vector(begin, end)
的顶部用于异常处理的额外代码,然后将其存储到新分配的内存中。in
new
请参阅Godbolt 编译器资源管理器上的一些尝试,注意它们会保存/恢复@Beer13
版本没有的地方,以及通过函数在正常路径之外生成的额外代码。这消失了-fno-exceptions
,但它们与@Bee 的版本相同,而不是更好。所以使用问题中的代码;它至少编译了我的任何不同的尝试。
std::vector<uint32_t>
如果可以在不更改模板类型的情况下使用 32 字节对齐的内存,我可能还更喜欢做一些事情来获得新分配的内存。我不确定这是否可能。
即使我们可以让这个初始分配在实践中保持一致,而无需更改类型以使其成为未来使用的编译时保证,这可能会有所帮助。将未对齐处理留给硬件的 AVX 代码将受益于没有缓存行拆分。
但是我认为如果不破解自定义构造函数,这是不可能的,std::vector
因为它会使用对齐的初始分配new
,假设它与常规兼容delete
。
如果您可以std::vector<uint32_t, some_aligned_allocator>
在代码中的任何地方使用 a ,那可能是值得的。但是,如果您必须将其传递给使用 normal 的代码,则可能不值得麻烦vector<uint32_t>
。
您可能会对编译器撒谎,因为该类型与常规二进制兼容(但不兼容源代码)std::vector<uint32_t>
,在对齐的新/删除与普通新/删除兼容的系统上。但我不建议这样做。
推荐阅读
- python - SQLITE(系统找不到指定的路径)
- maven - 如何使用不同的属性文件或环境配置文件来执行一个 Maven 项目?
- sql - 只返回每组oracle中的最大值
- python - OSError:处理大文件时Jupyter docker容器中的[Errno 107]
- docker - 我可以将 Google Container Optimized OS 用作安全的容器沙箱吗?
- python - 如何在pygame中防止垃圾邮件子弹
- rest - Rest API 设计 - 图像标记应用程序
- java - http 出站网关的 URL 中的路径参数
- r - 如何正确重写 R Shiny 的函数 icon() 以包含 Font-Awesome Pro 图标?
- tcl - 使用 tcl/tk 的临时目录