我在使用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);
}
我做了什么愚蠢的事吗?这是什么解决方法?
答案 0 :(得分:12)
标准分配器通常只对齐alignof(maxalign_t)
,通常为16B,例如x86-64 System V ABI中的long double
。但是在一些32位ABI中它只有8B,所以它甚至不足以动态分配对齐的__m128
向量,你需要的不仅仅是调用new
或malloc
。 / 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_free
和free
是兼容的,但不保证它是可移植的。 (与其他两个不同,它将在运行时失败,而不是编译时。)在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
来最小化代码开销。