std :: vector
中的数据,则需要16字节对齐。我如何实现这一点?我需要编写自己的分配器吗?还是默认的分配器已经对齐到16字节边界?std :: vector
中的数据,则需要16字节对齐。我如何实现这一点?我需要编写自己的分配器吗?还是默认的分配器已经对齐到16字节边界?C++标准要求分配函数(malloc()
和operator new()
)为任何标准类型分配内存,使其对齐合适。由于这些函数不接受对齐需求作为参数,实际上意味着所有分配的对齐方式相同,并且是具有最大对齐需求的标准类型的对齐方式,通常是long double
和/或long long
(请参见boost max_align union)。
向量指令(例如SSE和AVX)具有比标准C++分配函数提供的更强的对齐要求(128位访问需要16字节对齐,256位访问需要32字节对齐)。可以使用posix_memalign()
或memalign()
来满足此类对齐要求更强的分配。
在C++17中,分配函数接受一种名为std::align_val_t
的附加参数。
可以像这样使用它:
#include <immintrin.h>
#include <memory>
#include <new>
int main() {
std::unique_ptr<__m256i[]> arr{new(std::align_val_t{alignof(__m256i)}) __m256i[32]};
}
此外,在C++17中,标准分配器已更新为遵守类型的对齐方式,因此您可以简单地执行以下操作:
#include <immintrin.h>
#include <vector>
int main() {
std::vector<__m256i> arr2(32);
}
或者(不涉及堆分配,且在C++11中受支持):
#include <immintrin.h>
#include <array>
int main() {
std::array<__m256i, 32> arr3;
}
new(std::align_val_t{alignof(__m256i)})
中的 (std::align_val_t{alignof(__m256i)})
部分是多余的。如果数组类型具有比不对齐的 operator new[]
保证更严格的对齐要求,新表达式将调用带有正确 align_val_t
参数的 operator new[]
重载。显式指定它有点误导人,因为不保证比给定数组类型更严格的对齐方式能够正常工作。 - user17732522在使用std::
容器(如vector
)时,您应该使用自定义分配器。以下是一个比较好的实现,虽然作者已经无从考证,但我在使用一段时间后发现它似乎很有效(根据编译器/平台可能需要将_aligned_malloc
更改为_mm_malloc
):
#ifndef ALIGNMENT_ALLOCATOR_H
#define ALIGNMENT_ALLOCATOR_H
#include <stdlib.h>
#include <malloc.h>
template <typename T, std::size_t N = 16>
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) {
return (pointer)_aligned_malloc(n*sizeof(value_type), N);
}
inline void deallocate (pointer p, size_type) {
_aligned_free (p);
}
inline void construct (pointer p, const value_type & wert) {
new (p) 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);
}
// Returns true if and only if storage allocated from *this
// can be deallocated from other, and vice versa.
// Always returns true for stateless allocators.
bool operator==(const AlignmentAllocator<T,N>& other) const {
return true;
}
};
#endif
使用方法如下(如果需要,将16更改为其他对齐方式):
std::vector<T, AlignmentAllocator<T, 16> > bla;
然而,这只确保了std::vector
使用的内存块是16字节对齐的。如果sizeof(T)
不是16的倍数,则某些元素将不会对齐。根据您的数据类型,这可能不是一个问题。如果T
是int
(4个字节),则仅加载索引为4的倍数的元素。如果是double
(8个字节),则仅加载2的倍数等。
真正的问题是如果您使用类作为T
,在这种情况下,您将不得不在类本身中指定对齐要求(再次取决于编译器,这可能是不同的;以下示例适用于GCC):
class __attribute__ ((aligned (16))) Foo {
__attribute__ ((aligned (16))) double u[2];
};
我们快要完成了!如果您使用的是Visual C++(至少2010版本),则由于std::vector::resize,您将无法使用已指定对齐方式的类的std::vector。
编译时,如果出现以下错误:
C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector(870):
error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned
您需要修改stl::vector头文件
:
vector
头文件[C:\ Program Files \ Microsoft Visual Studio 10.0 \ VC \ include \ vector]void resize(_Ty _Val)
方法[VC2010的第870行]void resize(const _Ty& _Val)
。vector<T> v(1); v.resize(v[0]);
是合法的。但是在更改为引用后,它可能会出现问题。 - Ben Voigt不必像之前建议的那样编写自己的分配器,您可以像这样为std::vector
使用boost::alignment::aligned_allocator
:
#include <vector>
#include <boost/align/aligned_allocator.hpp>
template <typename T>
using aligned_vector = std::vector<T, boost::alignment::aligned_allocator<T, 16>>;
编写自己的内存分配器。 allocate
和 deallocate
是重要的函数。以下是一个例子:
pointer allocate( size_type size, const void * pBuff = 0 )
{
char * p;
int difference;
if( size > ( INT_MAX - 16 ) )
return NULL;
p = (char*)malloc( size + 16 );
if( !p )
return NULL;
difference = ( (-(int)p - 1 ) & 15 ) + 1;
p += difference;
p[ -1 ] = (char)difference;
return (T*)p;
}
void deallocate( pointer p, size_type num )
{
char * pBuffer = (char*)p;
free( (void*)(((char*)p) - pBuffer[ -1 ] ) );
}
intptr_t
(它保证具有指针大小)代替 int
,并移除 INT_MAX
(尺寸很可能是无符号的)。 - Christian Rau如果 sizeof(T)*vector.size() > 16
,那么是的。
假设您的向量使用普通分配器
注意:只要 alignof(std::max_align_t) >= 16
,因为这是最大对齐。
更新于2017年8月25日新标准 n4659
如果它对于任何大于16的东西都是对齐的,那么它也正确地对齐了16。
对齐方式表示为 std::size_t 类型的值。有效对齐方式仅包括基本类型的 alignof 表达式返回的那些值以及一个额外的实现定义的值集,该集合可以为空。每个对齐值都必须是正的二的幂。
对齐方式具有从较弱到较强或更严格的对齐方式的顺序。更严格的对齐方式具有更大的对齐值。满足对齐要求的地址也满足任何较弱的有效对齐要求。
new和new[]返回对齐的值,以便对象根据其大小正确对齐:
[注意:当分配函数返回除null之外的值时,它必须是指向已保留对象空间的存储块的指针。假定存储块已适当对齐并具有所请求的大小。如果对象是数组,则创建的对象的地址不一定与块的地址相同。—注]
请注意,大多数系统都有最大对齐方式。动态分配的内存不需要对齐到大于此值的值。
基本对齐由小于或等于实现在所有上下文中支持的最大对齐方式表示,该对齐方式等于alignof(std :: max_align_t)(21.2)。当类型用作完整对象的类型和用作子对象的类型时,所需的对齐方式可能不同。
因此,只要您的向量内存分配大于16个字节,它就会在16个字节边界上正确对齐。
vmovaps
加载/存储需要32字节对齐的内存而导致std::vector<__m256>
出现段错误。 SIMD向量不被视为基本类型,因此,在现有的x86 C++实现中,new
不会返回足够对齐以容纳它们的内存。 在一些实现中(特别是32位),new
只返回8字节对齐的内存,即使是std::vector<__m128>
也会出错。 - Peter Cordessizeof(T)*vector.size()
并不相关。首先,T
可以是结构体类型;其次,vector.size()
与内存对齐方式无关。除了实现细节(例如,通常会分配新的整个页面),这两者之间没有任何联系。原帖中想要实现的是(例如)使用16字节对齐的std::vector<float>
,但大多数实现都不保证在没有自定义分配器的情况下实现这一点。(std::vector<__m128>
也不支持这种用例,但这不是通常的用法。) - Peter Cordesaligned_storage
来使标准容器与其他值对齐。参见:http://en.cppreference.com/w/cpp/types/aligned_storage - Martin Yorkstd::aligned_storage
只是一个缓冲区。那里的示例在其上实现了一个容器(使用存储数组作为成员数组,而不是动态分配)。没有明显的方法可以让标准容器用它来做任何事情。 “示例实现”说它可以基于alignas
构建,但这对于动态存储并没有帮助。” - Peter Cordesstd::vector<__m256>
在正确的实现中确实可以使用 -std=gnu++17
或 -std=c++17
。只有在 C++14 及更早版本中才会出现问题,而我 2017 年的早期评论可能仅基于尝试了没有 -std=gnu++17
的 g++
,如果当时支持的话。但是,您将不得不使用 vector<__m256>
而不是其他代码可以轻松访问的 vector<float>
,并且它的大小不是 8 个浮点数的倍数。 - Peter Cordes这是对一个过时但重要的问题的现代回答。
像其他人所说,编写自己的Allocator
类[模板]是首选。自C++11以来直到C++17,实现大多受标准限制,只能使用alignas
和放置new
。C++17取消了C11的aligned_alloc
,这很方便。此外,C++17的std::pmr
命名空间(头文件<memory_resource>
)引入了polymorphic_allocator
类模板和memory_resource
抽象接口,受Boost启发,用于多态分配。除了允许真正通用的动态代码之外,在某些情况下已经显示出速度提升;在这种情况下,您的SIMD代码将表现得更好。
在Intel的向量化教程中,使用declspec(align(x,y))
进行说明,http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf
std::vector
上不起作用:它只会对齐控制块,而不是指向的.data()
。你链接的文档没有提到std::vector
或vector<
,所以我认为任何提到单词vector
的地方都是在谈论SIMD向量,而不是C++的std::vector<T>
。 - Peter Cordesnew
和new[]
返回对于任何数据类型都对齐的数据,这应该包括SSE。无论MSVC是否遵循该规则是另一个问题。char*
或MYTHING*
指针参数,并检查该指针是否为4096对齐,并在不对齐时中止、抛出或执行未定义的操作。这并不意味着char
或MYTHING
在标准的意义上具有4096对齐要求。我认为标准确实打算通过malloc
和new
来满足实现所施加的任何对齐要求,但实现者认为这样会浪费空间,因此不切实际。 - Steve Jessopalignof(long double)
= 16,这是Intel针对80位x87优化手册的建议。在64位Linux(使用glibc)上,malloc
/new
确实会为您提供16字节对齐的内存。但这对于AVX或AVX512并没有帮助。(正如您所说,64位Windows和32位任何操作系统只提供8字节对齐的内存。) - Peter Cordes
aligned_storage
。也许还有一个aligned_allocator
?让我检查一下。 - Xeo