如何解决AVX加载/存储操作的32字节对齐问题?

问题描述 投票:11回答:3

我在使用ymm寄存器时遇到对齐问题,其中一些代码片段对我来说似乎很好。这是一个最小的工作示例:

#include <iostream> 
#include <immintrin.h>

inline void ones(float *a)
{
     __m256 out_aligned = _mm256_set1_ps(1.0f);
     _mm256_store_ps(a,out_aligned);
}

int main()
{
     size_t ss = 8;
     float *a = new float[ss];
     ones(a);

     delete [] a;

     std::cout << "All Good!" << std::endl;
     return 0;
}

当然,sizeof(float)在我的体系结构(4)上是Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz,并且我正在使用gcc标志使用-O3 -march=native进行编译。当然,由于未对齐的内存访问(即指定_mm256_storeu_ps),错误会消失。我在xmm寄存器上也没有此问题,即

inline void ones_sse(float *a)
{
     __m128 out_aligned = _mm_set1_ps(1.0f);
     _mm_store_ps(a,out_aligned);
}

我做任何愚蠢的事吗?解决方法是什么?

c++ c++11 sse memory-alignment avx
3个回答
13
投票

[标准分配器通常仅与alignof(max_align_t)对齐,通常为16B,例如x86-64 System V ABI中的long double。但是在某些32位ABI中,它只有8B,因此它甚至不足以动态分配对齐的__m128向量,并且您将需要的不仅仅是简单地调用newmalloc

静态和自动存储很容易:使用alignas(32) float arr[N];

C ++ 17提供对齐的new用于与delete兼容的对齐的动态分配:float * arr = new (std::align_val_t(32)) float[numSteps];请参阅new/new[]new的文档

动态分配的其他选项大多是new[] / std::align_val_t兼容,不是std::align_val_t/ malloc

  • [free:ISO C ++ 17。 主要缺点:大小必须是对齐的倍数。例如,这种死脑筋的要求使其不适用于分配未知数new的64B高速缓存行对齐的数组。尤其是2M对齐的阵列可以利用delete

    std::aligned_alloc的C版本已在ISO C11中添加。在某些(但不是全部)C ++编译器中可用。如cppreference页面上所述,当大小不是对齐倍数(它是未定义的行为)时,不需要C11版本失败,因此许多实现提供了明显的期望行为作为“扩展名”。 std::aligned_alloc,但目前我还不能真正推荐float作为分配任意大小数组的可移植方式。

    而且,评论者报告说它在MSVC ++中不可用。有关Windows的可行transparent hugepages,请参见aligned_alloc。但是AFAIK没有Windows对齐分配函数来产生与标准aligned_alloc兼容的指针。

  • [Discussion is underway to fix this:POSIX 2001的一部分,不是任何ISO C或C ++标准。与aligned_alloc相比,原型/界面笨拙。我已经看到gcc会生成指针的重载,因为不能确定存储到缓冲区中的指针是否会修改。 (由于传递了best cross-platform method to get aligned memory指针的地址。)因此,如果使用此指针,请将其复制到另一个尚未将其地址传递给函数的C ++变量中。

#ifdef
  • [free:在可以使用posix_memalign的任何平台上都可用,但是您不能将指针从其传递到posix_memalign。在许多C和C ++实现中,aligned_allocposix_memalign是兼容的,但不能保证可移植。 (并且不同于其他两个,它将在运行时失败,而不是在编译时失败。)在Windows的MSVC上,#include <stdlib.h> int posix_memalign(void **memptr, size_t alignment, size_t size); // POSIX 2001 void *aligned_alloc(size_t alignment, size_t size); // C11 (and ISO C++17) 使用_mm_malloc,它与_mm_whatever_ps不兼容。在实践中会崩溃。

在C ++ 11和更高版本中:使用free作为struct / class成员的第一个成员(或直接在普通数组上),因此该类型的静态和自动存储对象将具有32B对齐。 _mm_free有此技术的示例来解释free的功能。

这对于动态分配的存储(例如_mm_malloc)实际上不起作用,请参阅_aligned_malloc

在C ++ 17中,可能有一种方法可以将对齐的new用于_aligned_malloc。 TODO:了解操作方法。


最后,最后一个选项太糟糕了,它甚至不在列表的一部分:分配更大的缓冲区,并使用适当的强制转换添加do free。太多的缺点(难以释放,浪费内存)值得讨论,因为在支持Intel alignas(32) float avx_array[1234]内在函数的每个平台上都可以使用对齐分配功能。但是IIRC甚至还有库函数可以帮助您完成此操作。

为了使用此技术在普通的旧std::aligned_storage documentation之上实现std::aligned_storage的可能性,可能存在使用std::aligned_storage而不是std::vector<my_class_with_aligned_member_array>的要求。


6
投票

内存管理有两个内在函数。_mm_malloc的操作类似于标准malloc,但是它采用了一个附加参数来指定所需的对齐方式。在这种情况下,为32字节对齐。使用此分配方法时,必须通过相应的_mm_free调用释放内存。

Making std::vector allocate aligned memory

3
投票

您需要对齐的分配器。

但是没有理由不能将它们捆绑在一起:

std::vector

现在p+=31; p&=~31ULL是指向4字节对齐的_mm256 s数组的唯一指针。您可以通过_mm_free创建它,它创建20个4字节对齐的浮点数或free(仅在该语法中为编译时常数)。 _mm_malloc返回malloc而不是float *a = static_cast<float*>(_mm_malloc(sizeof(float) * ss , 32)); ... _mm_free(a);

template<class T, size_t align> struct aligned_free { void operator()(T* t)const{ ASSERT(!(uint_ptr(t) % align)); _mm_free(t); } aligned_free() = default; aligned_free(aligned_free const&) = default; aligned_free(aligned_free&&) = default; // allow assignment from things that are // more aligned than we are: template<size_t o, std::enable_if_t< !(o % align) >* = nullptr > aligned_free( aligned_free<T, o> ) {} }; template<class T> struct aligned_free<T[]>:aligned_free<T>{}; template<class T, size_t align=1> using mm_ptr = std::unique_ptr< T, aligned_free<T, align> >; template<class T, size_t align> struct aligned_make; template<class T, size_t align> struct aligned_make<T[],align> { mm_ptr<T, align> operator()(size_t N)const { return mm_ptr<T, align>(static_cast<T*>(_mm_malloc(sizeof(T)*N, align))); } }; template<class T, size_t align> struct aligned_make { mm_ptr<T, align> operator()()const { return aligned_make<T[],align>{}(1); } }; template<class T, size_t N, size_t align> struct aligned_make<T[N], align> { mm_ptr<T, align> operator()()const { return aligned_make<T[],align>{}(N); } }: // T[N] and T versions: template<class T, size_t align> auto make_aligned() -> std::result_of_t<aligned_make<T,align>()> { return aligned_make<T,align>{}(); } // T[] version: template<class T, size_t align> auto make_aligned(size_t N) -> std::result_of_t<aligned_make<T,align>(size_t)> { return aligned_make<T,align>{}(N); } 可以移动构造mm_ptr<float[], 4>,但反之则不能,这我认为很好。

[float可以采取任何对齐方式,但不能保证任何对齐方式。

开销,与make_aligned<float[], 4>(20)一样,每个指针基本上为零。积极的make_aligned<float[20], 4>()可以使代码开销最小化。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.