我在使用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);
}
我做任何愚蠢的事吗?解决方法是什么?
[标准分配器通常仅与alignof(max_align_t)
对齐,通常为16B,例如x86-64 System V ABI中的long double
。但是在某些32位ABI中,它只有8B,因此它甚至不足以动态分配对齐的__m128
向量,并且您将需要的不仅仅是简单地调用new
或malloc
。
静态和自动存储很容易:使用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_alloc
和posix_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>
的要求。
内存管理有两个内在函数。_mm_malloc的操作类似于标准malloc,但是它采用了一个附加参数来指定所需的对齐方式。在这种情况下,为32字节对齐。使用此分配方法时,必须通过相应的_mm_free调用释放内存。
Making std::vector allocate aligned memory
您需要对齐的分配器。
但是没有理由不能将它们捆绑在一起:
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>()
可以使代码开销最小化。