我想使用增强的REP MOVSB(ERMSB)为自定义获取高带宽memcpy
。
ERMSB是与Ivy Bridge微体系结构一起引入的。如果您不知道什么是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 @ 2400 MHz双通道32 GB,GCC 6.2可达到__movsb
15 GB / s和memcpy
26 GB / s。
为什么带宽这么低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
基于这些评论
注意,在Ivybridge和Haswell上,如果缓冲区较大以适合MLC,则可以使用rep movsb击败movntdqa。movntdqa导致LLC产生RFO,rep movsb不会...当在Ivybridge和Haswell上流传输到内存时,rep movsb的速度明显高于movntdqa(但请注意,在Ivybridge之前它很慢!)
这是我在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
。
在我的原始测试中,我没有禁用turbo。我禁用了turbo并再次进行了测试,但似乎没有太大的区别。但是,更改电源管理确实有很大的不同。
当我做
sudo cpufreq-set -r -g performance
有时我会看到超过20 GB / s的速度rep movsb
。
与
sudo cpufreq-set -r -g powersave
我所看到的最好的速度约为17 GB / 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
。只有在performance
没有SpeedStep的情况下,CPU才能以恒定频率运行。
我使用了例如sudo cpufreq-set -r performance
(因为cpufreq-set
给出了奇怪的结果)来更改电源设置。这会重新打开turbo,因此之后我必须禁用turbo。