是的,你可以使用_mm256_loadu_ps
/storeu
进行未对齐的加载/存储(AVX:数据对齐:存储崩溃,storeu,load,loadu不支持)。如果编译器没有做糟糕的工作(咳咳 GCC默认调优),在数据对齐的情况下使用AVX _mm256_loadu
/storeu
与需要对齐的加载/存储一样快,因此在方便的时候对齐数据仍然可以让硬件处理它们不常见的情况,从而为通常在对齐数据上运行但让硬件处理它们不常见情况的函数提供最佳解决方案。(而不是总是运行额外的指令来检查这些内容)。
Alignment对于512位AVX-512向量特别重要,即使在大型数组上,你期望L3 / DRAM带宽成为瓶颈时,SKX的速度也会提高15到20%,而AVX2 CPU仅有几个百分点。如果你的数据在L2或尤其是L1d缓存中很热,并且可以接近每个时钟满载2个载入和/或1个存储,则甚至在现代CPU上使用AVX2仍然非常重要。缓存行分裂的吞吐量资源成本约为两倍,并且需要临时的线路分裂缓冲区。
标准分配器通常只对齐到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
用于对齐的动态分配。如果类型的alignof
大于标准对齐方式,则使用对齐的operator new
/operator delete
。因此,在C++17中,new __m256[N]
可以正常工作(如果编译器支持此C++17特性;请检查__cpp_aligned_new
特性宏)。实际上,GCC / clang / MSVC / ICX都支持它,ICC 2021不支持。
没有C++17的特性,即使是像
std::vector<__m256>
这样的东西也会出错,不仅仅是
std::vector<int>
,除非你幸运地将其对齐为32。
使用delete
兼容的float
/int
数组分配:
不幸的是,auto* arr = new alignas(32) float[numSteps]
并不适用于所有编译器,因为alignas
只适用于变量、成员或类声明,而不是类型修饰符。(GCC接受using vfloat = alignas(32) float;
,所以这确实给了你一个与普通delete
兼容的对齐新数组在GCC上)。
解决方法要么是将其包装在结构中(struct alignas(32) s { float v; }; new s[numSteps];
),要么将对齐作为放置参数传递(new (std::align_val_t(32)) float[numSteps];
),在后一种情况下,请确保调用匹配的对齐operator delete
。
请参阅
new
/new[]
和
std::align_val_t
的文档。
与new
/delete
不兼容的其他选项
动态分配的其他选项大多数与malloc
/free
兼容,但与new
/delete
不兼容:
std::aligned_alloc
: ISO C++17。 主要缺点:大小必须是对齐的倍数。 这个脑残的要求使其不适合分配未知数量的64B高速缓存行对齐的float
数组,例如。 或者特别是2M对齐的数组以利用透明巨大页面。
aligned_alloc
的C版本添加在ISO C11中。 它在一些但不是所有C ++编译器中可用。 正如cppreference页面上所述,当大小不是对齐的倍数时,不需要强制执行C11版本(这是未定义的行为),因此许多实现提供了明显的所需行为作为“扩展”。 正在进行讨论以修复此问题,但目前我无法真正推荐aligned_alloc
作为分配任意大小数组的便携式方式。 实际上,某些实现在UB /需要失败的情况下工作得很好,因此它可以是一个良好的非便携式选项。
此外,评论员报告MSVC ++中不可用。 有关Windows的可行#ifdef
,请参见最佳跨平台方法以获取对齐内存。 但是,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);
void *aligned_alloc(size_t alignment, size_t size);
_mm_malloc
: 可用于任何支持_mm_whatever_ps
的平台,但不能将其指针传递给free
。在许多C和C++实现中,_mm_free
和free
是兼容的,但无法保证可移植性。(与其他两者不同的是,它会在运行时而不是编译时失败。)在Windows上的MSVC中,_mm_malloc
使用_aligned_malloc
,这与free
不兼容;在实践中会崩溃。
直接使用系统调用,例如mmap
或VirtualAlloc
。适用于大型分配,并且您获得的内存定义为页面对齐(4k,甚至可能是2M大页)。与free
不兼容;当然,您必须使用munmap
或VirtualFree
,它们需要大小以及地址。(对于大型分配,您通常希望在完成后将内存返回给操作系统,而不是管理自由列表;glibc malloc直接使用mmap/munmap进行malloc/free超过某个大小阈值的块。)
主要优点:您不必处理C++和C的愚蠢拒绝为对齐分配器提供增长/收缩功能。如果您想在分配后再获得另外1MiB的空间,甚至可以使用Linux的mremap(MREMAP_MAYMOVE)
让其选择虚拟地址空间中的不同位置(如果需要)以获取相同物理页面,而无需复制任何内容。或者,如果不必移动,则当前正在使用部分的TLB条目保持有效。
由于您已经在使用操作系统系统调用(并且知道您正在使用整个页面),因此可以使用madvise(MADV_HUGEPAGE)
来暗示首选透明大页面,或者不是针对此范围的匿名页面。您还可以使用mmap
中的分配提示,例如使OS预先分配零页,或者如果在hugetlbfs上映射文件,则使用2M或1G页面。(如果该内核机制仍然起作用)。
通过madvise(MADV_FREE)
,您可以将其保留映射,但让内核在发生内存压力时回收页面,使其像延迟分配的零后备页面一样。因此,如果您很快重新使用它,则可能不会遭受新的页面错误。但是如果您不这样做,您不会独占它,并且当您阅读它时,它就像一个全新的mmapped区域。
alignas()
与数组/结构体
在C++11及以后版本中:可以将alignas(32) float avx_array[1234]
用作结构/类成员的第一个成员(或直接用于普通数组),这样该类型的静态和自动存储对象将具有32B对齐。std::aligned_storage
documentation提供了这种技术的示例来解释std::aligned_storage
的作用。
对于动态分配的存储(例如std::vector<my_class_with_aligned_member_array>
),直到C++17才实际起作用,请参见Making std::vector allocate aligned memory。
从C++17开始,编译器对于整个类型或其成员强制执行对齐的类型将选择对齐的new
,此外std::allocator
也将为这种类型选择对齐的new
,因此在创建这种类型的std::vector
时不必担心。
最后一种选择实在太糟糕了,甚至不列在列表中:分配一个更大的缓冲区,并使用适当的转换进行“p+=31; p&=~31ULL”。由于有太多缺点(难以释放、浪费内存),不值得讨论,因为所有支持Intel“_mm256_…”指令集的平台都提供对齐分配函数。但是如果您坚持,甚至有库函数可以帮助您完成这个过程(如我所知)。需要使用_mm_free而不是free的要求可能部分原因是可以使用这种技术在普通的malloc上实现_mm_malloc。或者对于使用备用空闲链表的对齐分配器。
new []
分配的内容时要使用delete []
。 - anorm