【发布时间】:2021-01-24 16:42:40
【问题描述】:
我正在寻找一种方法来重载 operator[](在更广泛的 SIMD 类中),以方便在 SIMD 字中读取和写入单个元素(例如 __m512i)。几个限制:
- 符合 C++11(或更高版本)
- 与其他基于内部函数的代码兼容
- 不是 OpenCL/SYCL(我可以,但我不能*叹气*)
- 主要可跨 g++、icpc、clang++ 移植
- 最好适用于英特尔以外的其他 SIMD(ARM、IBM 等...)
- (编辑)性能并不是真正的问题(通常不用于性能很重要的地方)
(这排除了诸如通过指针转换的类型双关语和 GCC 向量类型之类的事情。)
主要基于 Scott Meyers 的“更有效的 C++”(第 30 项)和其他代码,我提出了以下 MVC 代码,这些代码似乎“正确”,似乎有效,但也似乎过于复杂。 (“代理”方法旨在处理左/右手 operator[] 的使用,“memcpy”旨在处理类型双关/C++ 标准问题。)
我想知道是否有人有更好的解决方案(并且可以解释它以便我学到一些东西;^))
#include <iostream>
#include <cstring>
#include "immintrin.h"
using T = __m256i; // SIMD type
using Te = unsigned int; // SIMD element type
class SIMD {
class SIMDProxy;
public :
const SIMDProxy operator[](int index) const {
std::cout << "SIMD::operator[] const" << std::endl;
return SIMDProxy(const_cast<SIMD&>(*this), index);
}
SIMDProxy operator[](int index){
std::cout << "SIMD::operator[]" << std::endl;
return SIMDProxy(*this, index);
}
Te get(int index) {
std::cout << "SIMD::get" << std::endl;
alignas(T) Te tmp[8];
std::memcpy(tmp, &value, sizeof(T)); // _mm256_store_si256(reinterpret_cast<__m256i *>(tmp), c.value);
return tmp[index];
}
void set(int index, Te x) {
std::cout << "SIMD::set" << std::endl;
alignas(T) Te tmp[8];
std::memcpy(tmp, &value, sizeof(T)); // _mm256_store_si256(reinterpret_cast<__m256i *>(tmp), c.value);
tmp[index] = x;
std::memcpy(&value, tmp, sizeof(T)); // c.value = _mm256_load_si256(reinterpret_cast<__m256i const *>(tmp));
}
void splat(Te x) {
alignas(T) Te tmp[8];
std::memcpy(tmp, &value, sizeof(T));
for (int i=0; i<8; i++) tmp[i] = x;
std::memcpy(&value, tmp, sizeof(T));
}
void print() {
alignas(T) Te tmp[8];
std::memcpy(tmp, &value, sizeof(T));
for (int i=0; i<8; i++) std::cout << tmp[i] << " ";
std::cout << std::endl;
}
protected :
private :
T value;
class SIMDProxy {
public :
SIMDProxy(SIMD & c_, int index_) : c(c_), index(index_) {};
// lvalue access
SIMDProxy& operator=(const SIMDProxy& rhs) {
std::cout << "SIMDProxy::=SIMDProxy" << std::endl;
c.set(rhs.index, rhs.c.get(rhs.index));
return *this;
}
SIMDProxy& operator=(Te x) {
std::cout << "SIMDProxy::=T" << std::endl;
c.set(index,x);
return *this;
}
// rvalue access
operator Te() const {
std::cout << "SIMDProxy::()" << std::endl;
return c.get(index);
}
private:
SIMD& c; // SIMD this proxy refers to
int index; // index of element we want
};
friend class SIMDProxy; // give SIMDProxy access into SIMD
};
/** a little main to exercise things **/
int
main(int argc, char *argv[])
{
SIMD x, y;
Te a = 3;
x.splat(1);
x.print();
y.splat(2);
y.print();
x[0] = a;
x.print();
y[1] = a;
y.print();
x[1] = y[1];
x.print();
}
【问题讨论】:
-
我考虑过一个(未命名的?)与 std::array 的联合。
-
我的理解是指针别名和联合方法,虽然它们可能经常工作,并且 are 对 C 有效,但实际上在 C++ 标准下无效。 (因此我的问题)。如果有人想证明我错了,我很高兴。
-
@AndreySemashev “例如,如果没有 reinterpret_cast,就无法加载或存储整数向量,这是另一种类型双关语。” — 不,您可以
memcpy,就像 OP 一样。这总是定义明确的,编译器会对其进行优化。此外,UB 和实现定义之间存在(巨大)差异。虽然 SIMD 无法避免后者,但您大多可以避免前者。一个例外是当别名为数组时,这是目前计划在下一个版本中修复的标准缺陷。 -
@KonradRudolph 好吧,那句话的措辞不准确,您确实可以使用
memcpy。但是,这样做的预期方法是使用内部函数,例如_mm_loadu_si128/_mm_storeu_si128,而这些通常需要reinterpret_cast。允许这样做的原因是编译器允许对向量类型和标量类型进行类型别名。这显然不是纯 C/C++,这就是为什么我说无论如何你都必须依赖一些编译器扩展,我支持这一点。 -
您可能会发现this GitHub project 是一个有用的参考。
标签: c++ simd intrinsics avx