gpt4 book ai didi

C++ operator[] 访问 SIMD(例如 AVX)变量的元素

转载 作者:行者123 更新时间:2023-12-03 06:51:58 26 4
gpt4 key购买 nike

我正在寻找一种重载 operator[](在更广泛的 SIMD 类中)的方法,以促进在 SIMD 字(例如 __m512i)中读取和写入单个元素。几个限制:

  • 符合 C++11(或更高版本)
  • 与其他基于内在函数的代码兼容
  • 不是 OpenCL/SYCL(我可以,但我不能*叹*)
  • 主要可移植到 g++、icpc、clang++
  • 最好适用于 Intel 以外的其他 SIMD(ARM、IBM 等...)
  • (编辑)性能并不是真正的问题(通常不用于性能很重要的地方)

  • (这排除了诸如通过指针转换进行类型双关和 GCC vector 类型之类的事情。)
    主要基于 Scott Meyers 的“更有效的 C++”(第 30 项)和其他代码,我提出了以下看起来“正确”的 MVC 代码,这似乎有效,但也似乎过于复杂。 (“代理”方法旨在处理左/右手运算符 [] 的用法,而“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();
    }

    最佳答案

    你的代码效率很低。通常这些 SIMD 类型不会出现在内存中的任何地方,它们是硬件寄存器,它们没有地址,您不能将它们传递给 memcpy()。编译器非常努力地假装它们是普通变量,这就是为什么您的代码可以编译并且可能工作的原因,但它很慢,您一直在从寄存器到内存并返回。
    下面是我将如何做到这一点,假设 AVX2 和整数 channel 。

    class SimdVector
    {
    __m256i val;

    alignas( 64 ) static const std::array<int, 8 + 7> s_blendMaskSource;

    public:

    int operator[]( size_t lane ) const
    {
    assert( lane < 8 );
    // Move lane index into lowest lane of vector register
    const __m128i shuff = _mm_cvtsi32_si128( (int)lane );
    // Permute the vector so the lane we need is moved to the lowest lane
    // _mm256_castsi128_si256 says "the upper 128 bits of the result are undefined",
    // and we don't care indeed.
    const __m256i tmp = _mm256_permutevar8x32_epi32( val, _mm256_castsi128_si256( shuff ) );
    // Return the lowest lane of the result
    return _mm_cvtsi128_si32( _mm256_castsi256_si128( tmp ) );
    }

    void setLane( size_t lane, int value )
    {
    assert( lane < 8 );
    // Load the blending mask
    const int* const maskLoadPointer = s_blendMaskSource.data() + 7 - lane;
    const __m256i mask = _mm256_loadu_si256( ( const __m256i* )maskLoadPointer );
    // Broadcast the source value into all lanes.
    // The compiler will do equivalent of _mm_cvtsi32_si128 + _mm256_broadcastd_epi32
    const __m256i broadcasted = _mm256_set1_epi32( value );
    // Use vector blending instruction to set the desired lane
    val = _mm256_blendv_epi8( val, broadcasted, mask );
    }

    template<size_t lane>
    int getLane() const
    {
    static_assert( lane < 8 );
    // That thing is not an instruction;
    // compilers emit different ones based on the index
    return _mm256_extract_epi32( val, (int)lane );
    }

    template<size_t lane>
    void setLane( int value )
    {
    static_assert( lane < 8 );
    val = _mm256_insert_epi32( val, value, (int)lane );
    }
    };

    // Align by 64 bytes to guarantee it's contained within a cache line
    alignas( 64 ) const std::array<int, 8 + 7> SimdVector::s_blendMaskSource
    {
    0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0
    };
    对于 ARM,情况不同。如果在编译时知道车道索引,请参阅 vgetq_lane_s32vsetq_lane_s32内在因素。
    要在 ARM 上设置 channel ,您可以使用相同的广播 + 混合技巧。广播是 vdupq_n_s32 . vector 混合的近似等价物是 vbslq_s32 ,它独立处理每一位,但对于这个用例,它同样适用,因为 -1设置了所有 32 位。
    为了提取或者写一个 switch ,或者将完整的 vector 存储到内存中,不确定这两个中哪个更有效。

    关于C++ operator[] 访问 SIMD(例如 AVX)变量的元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64282775/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com