我想使用增强 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给出了奇怪的结果)来更改电源设置。这个能让涡轮恢复工作,所以我不得不在之后关闭涡轮。