【问题标题】:What does memory 32bit Alignement constraint mean for AVX?内存 32 位对齐约束对 AVX 意味着什么?
【发布时间】:2021-08-26 14:17:31
【问题描述】:

_mm256_load_ps 的文档指出,内存必须是 32 位对齐的,才能将值加载到寄存器中。

所以我发现 post 解释了地址是如何 32 位对齐的。

#include <immintrin.h>
#include <vector>

int main() {
    std::vector<float> A(height * width, 0);
    std::cout << "&A = " << A.data() << std::endl; // 0x55e960270eb0
    __m256 a_row = _mm256_load_ps(A.data());
    return 0; // Exit Code 139 SIGSEGV 
}

所以尝试了该代码。 我预计它会起作用。 我检查了地址
0x55e960270eb0 % 4 = 0 并且浮点数为 4 个字节。
我完全被这个原因弄糊涂了。 如果我将原始数组与 malloc 一起使用,突然一切正常

#include <immintrin.h>
#include <vector>

int main() {
    std::vector<float> A(height * width, 0);
    std::cout << "&A = " << A.data() << std::endl; // &A = 0x55e960270eb0


    float* m = static_cast<float*>(_mm_malloc(A.size() * sizeof(float), 32));
    std::cout << "m* = " << m << std::endl; // m* = 0x562bbe989700

    __m256 a_row = _mm256_load_ps(m);

    delete m;

    return 0; // Returns 0
}

我遗漏了什么/误解了什么?

【问题讨论】:

  • 你看错了 this - 它说 32 BYTE 对齐,而不是 BIT。对于堆栈变量,您可以通过代码alignas(32) std::array&lt;float, 8&gt; a; 对齐它们
  • @Arty 非常感谢。没想到
  • 有时alignas 不够好。 (已经很久了,所以我的记忆很模糊。)如果您有一个必须对齐 256 字节的 MMU 块,或者必须对齐 4096 字节的 DMA 缓冲区,您可能需要编译器可以进行的特殊自定义对齐处理不提供。对于这种情况下的 AVX,我希望编译器能够处理它。

标签: c++ avx avx2


【解决方案1】:

你错过了this - 它说 32 BYTE 对齐,而不是 BIT。

所以你必须做 32 字节对齐而不是 4 字节对齐。

要对齐任何堆栈变量,您可以使用alignas(32) T var;,其中T 可以是任何类型,例如std::array&lt;float, 8&gt;

要对齐std::vector 的内存或任何其他基于堆的结构alignas(...) 是不够的,您必须编写特殊的对齐分配器(参见Test() 函数的用法示例):

Try it online!

#include <cstdlib>
#include <memory>

// Following includes for tests only
#include <vector>
#include <iostream>
#include <cmath>

template <typename T, std::size_t N>
class AlignmentAllocator {
  public:
    typedef T value_type;
    typedef std::size_t size_type;
    typedef std::ptrdiff_t difference_type;
    typedef T * pointer;
    typedef const T * const_pointer;
    typedef T & reference;
    typedef const T & const_reference;

  public:
    inline AlignmentAllocator() throw() {}
    template <typename T2> inline AlignmentAllocator(const AlignmentAllocator<T2, N> &) throw() {}
    inline ~AlignmentAllocator() throw() {}
    inline pointer adress(reference r) { return &r; }
    inline const_pointer adress(const_reference r) const { return &r; }
    inline pointer allocate(size_type n);
    inline void deallocate(pointer p, size_type);
    inline void construct(pointer p, const value_type & wert);
    inline void destroy(pointer p) { p->~value_type(); }
    inline size_type max_size() const throw() { return size_type(-1) / sizeof(value_type); }
    template <typename T2> struct rebind { typedef AlignmentAllocator<T2, N> other; };
    bool operator!=(const AlignmentAllocator<T, N> & other) const { return !(*this == other); }
    bool operator==(const AlignmentAllocator<T, N> & other) const { return true; }
};

template <typename T, std::size_t N>
inline typename AlignmentAllocator<T, N>::pointer AlignmentAllocator<T, N>::allocate(size_type n) {
    #if _MSC_VER
        return (pointer)_aligned_malloc(n * sizeof(value_type), N);
    #else
        void * p0 = nullptr;
        int r = posix_memalign(&p0, N, n * sizeof(value_type));
        if (r != 0) return 0;
        return (pointer)p0;
    #endif
}
template <typename T, std::size_t N>
inline void AlignmentAllocator<T, N>::deallocate(pointer p, size_type) {
    #if _MSC_VER
        _aligned_free(p);
    #else
        std::free(p);
    #endif
}
template <typename T, std::size_t N>
inline void AlignmentAllocator<T, N>::construct(pointer p, const value_type & wert) {
    new (p) value_type(wert);
}

template <typename T, size_t N = 64>
using AlignedVector = std::vector<T, AlignmentAllocator<T, N>>;

template <size_t Align>
void Test() {
    AlignedVector<float, Align> v(1);
    size_t uptr = size_t(v.data()), alignment = 0;
    while (!(uptr & 1)) {
        ++alignment;
        uptr >>= 1;
    }
    std::cout << "Requested: " << Align << ", Actual: " << (1 << alignment) << std::endl;
}

int main() {
    Test<8>();
    Test<16>();
    Test<32>();
    Test<64>();
    Test<128>();
    Test<256>();
}

输出:

Requested: 8, Actual: 16
Requested: 16, Actual: 16
Requested: 32, Actual: 32
Requested: 64, Actual: 128
Requested: 128, Actual: 8192
Requested: 256, Actual: 256

您可能会在上面的代码中看到,我对 CLang/GCC 使用了 posix_memalign(),对 MSVC 使用了 _aligned_malloc()。从 C++17 开始,还存在 std::aligned_alloc(),但似乎并非所有编译器都实现了它,至少 MSVC 没有。在 CLang/GCC 上,您可以使用 std::aligned_alloc() 而不是 posix_memalign() 作为 @Mgetz 的 commented

作为英特尔指南says here,您可以使用_mm_malloc()_mm_free() 代替posix_memalign()/_aligned_malloc()/_aligned_free()/std::aligned_alloc()/std::free()

【讨论】:

  • 这将是非常好的。我想避免使用原始指针。
  • @JohnZakariaAbdElMesiih 刚刚为您编写了分配器,请查看我更新后的代码答案!询问您是否对使用它有任何疑问。我在Test() 函数中的代码中显示了用法示例。此分配器可用于任何 std:: 结构,而不仅仅是 std::vector,例如它可用于 std::map、std::set、std::list 等。
  • Note post C++17 posix_memalign 在非 MSVC 平台上不是必需的,该标准现在具有 std::aligned_alloc,它具有几乎相同但理论上更便携的语义(除了 MS C标准库)。
  • @Mgetz 所以你是说除了MSVC我可以在任何地方使用std::aligned_alloc()
  • C++17 确实使 std::vector&lt;__m256&gt; 现在可以正常工作(即尊重具有超过 alignof(max_align_t) 对齐要求的类型的对齐)。但通常您希望在该数据上使用 std::vector&lt;float&gt;_mm256_load_ps(或 loadu)。 (Is it good or bad (performance-wise) to use std::vector 和半相关:Why doesn't gcc resolve _mm256_loadu_pd as single vmovupd? re: GCC 的加载效率)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-22
  • 1970-01-01
  • 2013-05-23
  • 1970-01-01
  • 2015-07-30
相关资源
最近更新 更多