我想使用增强 REP MOVSB (ERMSB)来获得一个自定义 memcpy
的高带宽。
ERMSB 是由常春藤大桥微架构引入的。如果您不知道 ERMSB 是什么,请参阅 英特尔优化手册中的“增强 REP MOVSB 和 STOSB 操作(ERMSB)”一节。
我所知道的直接执行此操作的唯一方法是使用内联汇编。我从 https://groups.google.com/forum/#!topic/gnu.gcc.help/-Bmlm_EG_fE得到了以下函数
static inline void *__movsb(void *d, const void *s, size_t n) {
asm volatile ("rep movsb"
: "=D" (d),
"=S" (s),
"=c" (n)
: "0" (d),
"1" (s),
"2" (n)
: "memory");
return d;
}
然而,当我使用它时,带宽比使用 memcpy
时要小得多。
我的 i7-6700HQ (Skylake)系统,Ubuntu 16.10,DDR4@2400MHz 双通道32GB,GCC 6.2。
为什么 REP MOVSB
的带宽这么低? 我能做些什么来改善它?
这是我用来测试这个的代码。
//gcc -O3 -march=native -fopenmp foo.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>
#include <omp.h>
#include <x86intrin.h>
static inline void *__movsb(void *d, const void *s, size_t n) {
asm volatile ("rep movsb"
: "=D" (d),
"=S" (s),
"=c" (n)
: "0" (d),
"1" (s),
"2" (n)
: "memory");
return d;
}
int main(void) {
int n = 1<<30;
//char *a = malloc(n), *b = malloc(n);
char *a = _mm_malloc(n,4096), *b = _mm_malloc(n,4096);
memset(a,2,n), memset(b,1,n);
__movsb(b,a,n);
printf("%d\n", memcmp(b,a,n));
double dtime;
dtime = -omp_get_wtime();
for(int i=0; i<10; i++) __movsb(b,a,n);
dtime += omp_get_wtime();
printf("dtime %f, %.2f GB/s\n", dtime, 2.0*10*1E-9*n/dtime);
dtime = -omp_get_wtime();
for(int i=0; i<10; i++) memcpy(b,a,n);
dtime += omp_get_wtime();
printf("dtime %f, %.2f GB/s\n", dtime, 2.0*10*1E-9*n/dtime);
}
我对 rep movsb
感兴趣的原因是基于这些评论
请注意,在艾维布里奇和 Haswell 上,缓冲区大到足以适应 mLC,你可以通过移动代表(rep emosb)击败 move/tdqa,而 move/tdqa 将招致一个 RFO 进入有限责任公司,rep move/sb 不会... ..。 当在艾维布里奇和 Haswell 上进行流式记忆时,movsb 代表的速度明显快于 movntdqa (但要注意,在常春藤桥之前,这个速度很慢!)
这是我在 Tinymembnech的同一个系统上得到的结果。
C copy backwards : 7910.6 MB/s (1.4%)
C copy backwards (32 byte blocks) : 7696.6 MB/s (0.9%)
C copy backwards (64 byte blocks) : 7679.5 MB/s (0.7%)
C copy : 8811.0 MB/s (1.2%)
C copy prefetched (32 bytes step) : 9328.4 MB/s (0.5%)
C copy prefetched (64 bytes step) : 9355.1 MB/s (0.6%)
C 2-pass copy : 6474.3 MB/s (1.3%)
C 2-pass copy prefetched (32 bytes step) : 7072.9 MB/s (1.2%)
C 2-pass copy prefetched (64 bytes step) : 7065.2 MB/s (0.8%)
C fill : 14426.0 MB/s (1.5%)
C fill (shuffle within 16 byte blocks) : 14198.0 MB/s (1.1%)
C fill (shuffle within 32 byte blocks) : 14422.0 MB/s (1.7%)
C fill (shuffle within 64 byte blocks) : 14178.3 MB/s (1.0%)
---
standard memcpy : 12784.4 MB/s (1.9%)
standard memset : 30630.3 MB/s (1.1%)
---
MOVSB copy : 8712.0 MB/s (2.0%)
MOVSD copy : 8712.7 MB/s (1.9%)
SSE2 copy : 8952.2 MB/s (0.7%)
SSE2 nontemporal copy : 12538.2 MB/s (0.8%)
SSE2 copy prefetched (32 bytes step) : 9553.6 MB/s (0.8%)
SSE2 copy prefetched (64 bytes step) : 9458.5 MB/s (0.5%)
SSE2 nontemporal copy prefetched (32 bytes step) : 13103.2 MB/s (0.7%)
SSE2 nontemporal copy prefetched (64 bytes step) : 13179.1 MB/s (0.9%)
SSE2 2-pass copy : 7250.6 MB/s (0.7%)
SSE2 2-pass copy prefetched (32 bytes step) : 7437.8 MB/s (0.6%)
SSE2 2-pass copy prefetched (64 bytes step) : 7498.2 MB/s (0.9%)
SSE2 2-pass nontemporal copy : 3776.6 MB/s (1.4%)
SSE2 fill : 14701.3 MB/s (1.6%)
SSE2 nontemporal fill : 34188.3 MB/s (0.8%)
注意,在我的系统 SSE2 copy prefetched
也比 MOVSB copy
快。
在我最初的测试中,我没有禁用涡轮增压。我关闭了涡轮增压,并再次测试,它似乎没有太大的区别。然而,改变电源管理的确会带来很大的不同。
等我找到的时候
sudo cpufreq-set -r -g performance
我有时看到超过20GB/s 与 rep movsb
。
和
sudo cpufreq-set -r -g powersave
我看到的最大容量是17GB/s。但 memcpy
似乎对电源管理不敏感。
我检查的频率(使用 turbostat
) 启用或不启用 SpeedStep,与 performance
和与 powersave
空闲,1核心负载和4核心负载。我运行了 Intel 的 MKL 稠密矩阵乘法,创建了一个负载,并使用 OMP_SET_NUM_THREADS
设置了线程数。下面是结果表(以 GHz 为单位的数字)。
SpeedStep idle 1 core 4 core
powersave OFF 0.8 2.6 2.6
performance OFF 2.6 2.6 2.6
powersave ON 0.8 3.5 3.1
performance ON 3.5 3.5 3.1
这表明在 powersave
中,即使使用 SpeedStep 也禁用了 CPU
仍然将空闲频率设置为 0.8 GHz
。只有在没有 SpeedStep 的 performance
中,CPU 才能以恒定的频率运行。
我使用例如 sudo cpufreq-set -r performance
(因为 cpufreq-set
给出了奇怪的结果)来更改电源设置。这个能让涡轮恢复工作,所以我不得不在之后关闭涡轮。