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

时间:2015-09-16 14:57:58

标签: c++ c++11 sse memory-alignment avx

我在使用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)在我的架构上是4Intel(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);
}

我做了什么愚蠢的事吗?这是什么解决方法?

3 个答案:

答案 0 :(得分:12)

标准分配器通常只对齐alignof(maxalign_t),通常为16B,例如x86-64 System V ABI中的long double。但是在一些32位ABI中它只有8B,所以它甚至不足以动态分配对齐的__m128向量,你需要的不仅仅是调用newmalloc。 / p>

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

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

的文档

动态分配的其他选项大多数malloc / free兼容,不是 new / delete

  • std::aligned_alloc :ISO C ++ 17。 主要缺点:大小必须是对齐的倍数。例如,这种脑卒认要求不适合分配未知数量float的64B缓存行对齐数组。或者尤其是2M对齐的数组,以利用transparent hugepages

    ISO C11中添加了aligned_alloc的C版本。它可以在一些但不是所有的C ++编译器中使用。正如cppreference页面上所指出的,当大小不是对齐的倍数(它是未定义的行为)时,C11版本不需要失败,因此许多实现提供了明显的期望行为作为“扩展”。 Discussion is underway to fix this,但目前我不能真正推荐aligned_alloc作为分配任意大小数组的可移植方式。

    此外,评论者报告它在MSVC ++中不可用。有关Windows的可行#ifdef,请参阅best cross-platform method to get aligned memory。但是AFAIK没有Windows对齐分配函数可以生成与标准free兼容的指针。

  • posix_memalign :POSIX 2001的一部分,而不是任何ISO C或C ++标准。与aligned_alloc相比,笨重的原型/界面。我已经看到gcc生成指针的重载,因为它不确定缓冲区中的存储没有修改指针。 (因为posix_memalign传递了指针的地址。)因此,如果你使用它,将指针复制到另一个C ++变量中,该变量的地址没有传递到函数之外。

#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可用的任何平台上使用,但您无法将指针从free传递到<{strong} >。在许多C和C ++实现中,_mm_freefree是兼容的,但不保证它是可移植的。 (与其他两个不同,它将在运行时失败,而不是编译时。)在Windows上的MSVC上,_mm_malloc使用_aligned_malloc,这与free不兼容;它在实践中崩溃了。

在C ++ 11及更高版本中:使用alignas(32) float avx_array[1234]作为结构/类成员的第一个成员(或直接在普通数组上),因此该类型的静态和自动存储对象将具有32B对齐。 std::aligned_storage documentation有一个这种技术的例子来解释std::aligned_storage的作用。

这实际上不适用于动态分配的存储(例如std::vector<my_class_with_aligned_member_array>),请参阅Making std::vector allocate aligned memory

在C ++ 17中,可能有一种方法可以为std::vector使用对齐的new。 TODO:了解具体方法。

最后,最后一个选项太糟糕了,它甚至不是列表的一部分:分配一个更大的缓冲区并添加do p+=31; p&=~31ULL并进行适当的转换。太多的缺点(难以释放,浪费内存)值得讨论,因为在支持英特尔_mm256内在函数的每个平台上都可以使用对齐分配功能。但是甚至还有一些图书馆功能可以帮助你做到这一点,IIRC。

使用_mm_free代替free的要求可能存在于使用此技术在普通旧_mm_malloc之上实现malloc的可能性。

答案 1 :(得分:6)

内存管理有两种内在函数。  _mm_malloc的运行方式与标准的malloc类似,但它需要一个额外的参数来指定所需的对齐方式。在这种情况下,32字节对齐。使用此分配方法时,必须通过相应的_mm_free调用释放内存。

float *a = static_cast<float*>(_mm_malloc(sizeof(float) * ss , 32));
...
_mm_free(a);

答案 2 :(得分:3)

您需要对齐的分配器。

但是,没有理由将它们捆绑起来:

window.open("www.google.com", '_system')

现在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); } 是指向4个字节对齐的mm_ptr<float[], 4>数组的唯一指针。您可以通过float创建它,它创建20个4字节对齐的浮点数,或make_aligned<float[], 4>(20)(仅在该语法中编译时常量)。 make_aligned<float[20], 4>()返回make_aligned<float[20],4>而不是mm_ptr<float[],4>

mm_ptr<float[20],4>可以移动 - 构建mm_ptr<float[], 8>但不反之亦然,我认为这很好。

mm_ptr<float[],4>可以进行任何对齐,但不保证任何对齐。

开销,与mm_ptr<float[]>一样,每个指针基本上为零。可以通过积极的std::unique_ptr来最小化代码开销。