性能报告显示此函数“__memset_avx2_unaligned_erms”有开销。这是否意味着内存未对齐?
perf report shows this function "__memset_avx2_unaligned_erms" has overhead. does this mean memory is unaligned?
我正在尝试使用 perf 工具分析我的 C++ 代码。实现包含带有 SSE/AVX/AVX2 指令的代码。除此之外,代码还使用 -O3 -mavx2 -march=native
标志进行编译。我相信 __memset_avx2_unaligned_erms
函数是 memset
的 libc 实现。 perf 表明这个函数有相当大的开销。函数名称表示内存未对齐,但是在代码中我使用 GCC 内置宏 __attribute__((aligned (x)))
显式对齐内存是否明确对齐?
我已将示例报告作为图片附上。
不,不是。 这意味着 glibc 在该硬件上选择的 memset 策略不会尝试完全避免对齐访问,在小尺寸情况下。 (glibc 在动态链接器符号解析时选择一个 memset 实现,因此它在第一次调用后得到运行时调度而没有额外的开销。)
如果您的缓冲区实际上是对齐的并且大小是矢量宽度的倍数,则所有访问都将对齐并且基本上没有开销。 (使用 vmovdqu
和一个恰好在运行时对齐的指针完全等同于 vmovdqa
在所有支持 AVX 的 CPU 上。)
对于大缓冲区,它仍然会在主循环之前对齐指针,以防它未对齐, 代价是一些额外的指令与仅实现适用于 32 字节对齐的指针。 (但看起来它使用了 rep stosb
而没有对齐指针,如果它要指向 rep stosb
的话。)
gcc+glibc 没有只用对齐指针调用的特殊版本的 memset。 (或用于不同对齐保证的多个特殊版本)。 GLIBC 的 AVX2 未对齐实现适用于对齐和未对齐输入。
在glibc/sysdeps/x86_64/multiarch/memset-avx2-unaligned-erms.S
, which defines a couple macros (like defining the vector size as 32) and then #includes "memset-vec-unaligned-erms.S"
中定义。
源码中的注释说:
/* memset is implemented as:
1. Use overlapping store to avoid branch.
2. If size is less than VEC, use integer register stores.
3. If size is from VEC_SIZE to 2 * VEC_SIZE, use 2 VEC stores.
4. If size is from 2 * VEC_SIZE to 4 * VEC_SIZE, use 4 VEC stores.
5. If size is more to 4 * VEC_SIZE, align to 4 * VEC_SIZE with
4 VEC stores and store 4 * VEC at a time until done. */
主循环之前的实际对齐是在一些 vmovdqu
向量存储之后完成的(如果用于实际对齐的数据则没有惩罚:https://agner.org/optimize/):
L(loop_start):
leaq (VEC_SIZE * 4)(%rdi), %rcx # rcx = input pointer + 4*VEC_SIZE
VMOVU %VEC(0), (%rdi) # store the first vector
andq $-(VEC_SIZE * 4), %rcx # align the pointer
... some more vector stores
... and stuff, including storing the last few vectors I think
addq %rdi, %rdx # size += start, giving an end-pointer
andq $-(VEC_SIZE * 4), %rdx # align the end-pointer
L(loop): # THE MAIN LOOP
VMOVA %VEC(0), (%rcx) # vmovdqa = alignment required
VMOVA %VEC(0), VEC_SIZE(%rcx)
VMOVA %VEC(0), (VEC_SIZE * 2)(%rcx)
VMOVA %VEC(0), (VEC_SIZE * 3)(%rcx)
addq $(VEC_SIZE * 4), %rcx
cmpq %rcx, %rdx
jne L(loop)
因此 VEC_SIZE = 32,它将指针对齐 128。这太过分了;缓存行是 64 字节,实际上只要与矢量宽度对齐就可以了。
它还有一个使用 rep stos
的阈值,如果启用并且缓冲区大小 > 2kiB,在具有 ERMSB 的 CPU 上。 ().
我正在尝试使用 perf 工具分析我的 C++ 代码。实现包含带有 SSE/AVX/AVX2 指令的代码。除此之外,代码还使用 -O3 -mavx2 -march=native
标志进行编译。我相信 __memset_avx2_unaligned_erms
函数是 memset
的 libc 实现。 perf 表明这个函数有相当大的开销。函数名称表示内存未对齐,但是在代码中我使用 GCC 内置宏 __attribute__((aligned (x)))
显式对齐内存是否明确对齐?
我已将示例报告作为图片附上。
不,不是。 这意味着 glibc 在该硬件上选择的 memset 策略不会尝试完全避免对齐访问,在小尺寸情况下。 (glibc 在动态链接器符号解析时选择一个 memset 实现,因此它在第一次调用后得到运行时调度而没有额外的开销。)
如果您的缓冲区实际上是对齐的并且大小是矢量宽度的倍数,则所有访问都将对齐并且基本上没有开销。 (使用 vmovdqu
和一个恰好在运行时对齐的指针完全等同于 vmovdqa
在所有支持 AVX 的 CPU 上。)
对于大缓冲区,它仍然会在主循环之前对齐指针,以防它未对齐, 代价是一些额外的指令与仅实现适用于 32 字节对齐的指针。 (但看起来它使用了 rep stosb
而没有对齐指针,如果它要指向 rep stosb
的话。)
gcc+glibc 没有只用对齐指针调用的特殊版本的 memset。 (或用于不同对齐保证的多个特殊版本)。 GLIBC 的 AVX2 未对齐实现适用于对齐和未对齐输入。
在glibc/sysdeps/x86_64/multiarch/memset-avx2-unaligned-erms.S
, which defines a couple macros (like defining the vector size as 32) and then #includes "memset-vec-unaligned-erms.S"
中定义。
源码中的注释说:
/* memset is implemented as:
1. Use overlapping store to avoid branch.
2. If size is less than VEC, use integer register stores.
3. If size is from VEC_SIZE to 2 * VEC_SIZE, use 2 VEC stores.
4. If size is from 2 * VEC_SIZE to 4 * VEC_SIZE, use 4 VEC stores.
5. If size is more to 4 * VEC_SIZE, align to 4 * VEC_SIZE with
4 VEC stores and store 4 * VEC at a time until done. */
主循环之前的实际对齐是在一些 vmovdqu
向量存储之后完成的(如果用于实际对齐的数据则没有惩罚:https://agner.org/optimize/):
L(loop_start):
leaq (VEC_SIZE * 4)(%rdi), %rcx # rcx = input pointer + 4*VEC_SIZE
VMOVU %VEC(0), (%rdi) # store the first vector
andq $-(VEC_SIZE * 4), %rcx # align the pointer
... some more vector stores
... and stuff, including storing the last few vectors I think
addq %rdi, %rdx # size += start, giving an end-pointer
andq $-(VEC_SIZE * 4), %rdx # align the end-pointer
L(loop): # THE MAIN LOOP
VMOVA %VEC(0), (%rcx) # vmovdqa = alignment required
VMOVA %VEC(0), VEC_SIZE(%rcx)
VMOVA %VEC(0), (VEC_SIZE * 2)(%rcx)
VMOVA %VEC(0), (VEC_SIZE * 3)(%rcx)
addq $(VEC_SIZE * 4), %rcx
cmpq %rcx, %rdx
jne L(loop)
因此 VEC_SIZE = 32,它将指针对齐 128。这太过分了;缓存行是 64 字节,实际上只要与矢量宽度对齐就可以了。
它还有一个使用 rep stos
的阈值,如果启用并且缓冲区大小 > 2kiB,在具有 ERMSB 的 CPU 上。 (