__m256i和std :: vector <uint32_t>之间的相互转换

时间:2019-06-23 22:02:31

标签: c++ intel simd intrinsics avx2

我想与__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;
}

安全吗?

还有更惯用的方法吗?

1 个答案:

答案 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可以使用的任何情况下,将向量指针别名化为其他类型都是合法的。

另请参阅Is `reinterpret_cast`ing between hardware vector pointer and the corresponding type an undefined behavior?

也: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}}是二进制兼容的(但不是源兼容的) /删除。但我不建议这样做。