我想与__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;
}
安全吗?
还有更惯用的方法吗?
答案 0 :(得分:2)
首先,SIMD向量和std::vector
基本上没有任何关系。我知道您已经知道这一点,但是未来的读者应该仔细考虑这是否真的是他们想做的事情。
很安全; .data()
必须返回一个可以在任何有效索引处读写的指针。鉴于实际std::vector
库的实现细节,在实践中肯定是安全的。而且我很确定抽象的是纸上标准。
从评论中看,您似乎担心严格混用UB。
通过may_alias
指针类型(包括char*
或__m256i*
)读/写其他对象是可以的。 memcpy(&a, &b, sizeof(a))
是通过a
修改char*
的对象表示的常见示例。 memcpy本身并没有什么特别之处。由于char*
别名的特殊情况,因此定义明确。
may_alias
是GNU C扩展,可让您定义char
以外的其他类型,它们可以用char*
来别名。 GNU C对__m128
/ __m256i
的定义是根据诸如typedef long long __m256i __attribute((vector_size(32), may_alias));
之类的GNU C本机向量进行的。其他C ++实现(例如MSVC)对__m256i
的定义不同,但是内部函数API保证在char*
/ memcpy
可以使用的任何情况下,将向量指针别名化为其他类型都是合法的。
也:SSE: Difference between _mm_load/store vs. using direct pointer access-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
)之上有额外的代码用于异常处理,然后存储将其存储到新分配的内存中。
请参见on the Godbolt compiler explorer的一些尝试,请注意它们会保存/恢复r13
,其中@Bee的版本没有,还有通过函数的正常路径之外生成的额外代码。 -fno-exceptions
可以解决这个问题,但是它们只是等同于@Bee的版本,而不是更好。因此,使用问题中的代码;它至少可以像我尝试的一样进行编译。
如果不更改模板类型,我可能更喜欢做一些事情来分配分配给32位字节对齐内存的新std::vector<uint32_t>
。我不确定 是否可能。
即使我们只是可以使初始分配在实践中保持一致,而无需更改类型以为将来使用提供编译时保证,这也可能会有所帮助。没有缓存行拆分的AVX代码将受益于没有缓存行拆分。
但是我认为,如果不破解std::vector
的自定义构造函数,并假设它与常规new
兼容,那么该delete
会使用对齐的std::vector<uint32_t, some_aligned_allocator>
进行初始分配。 >
如果您可以在代码中的任何地方使用vector<uint32_t>
,那么可能是值得的。但是,如果您必须将其传递给使用常规std::vector<uint32_t>
的代码,则可能不值得麻烦。
您可能对您的编译器撒谎,因为该类型在常规new / delete与纯new兼容的系统上与常规{{1}}是二进制兼容的(但不是源兼容的) /删除。但我不建议这样做。