Я хотел бы использовать расширенный REP MOVSB (ERMSB), чтобы получить высокую пропускную способность для пользовательского memcpy
.
ERMSB была представлена с микроархитектурой Ivy Bridge. В разделе Руководство по оптимизации Intel, если вы не знаете, что такое 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
.
__movsb
получает 15 ГБ/с и memcpy
получает 26 ГБ/с с моей системой i7-6700HQ (Skylake), Ubuntu 16.10, DDR4 @2400 МГц с двумя каналами 32 ГБ, 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
, основывается на этих комментариях
Обратите внимание, что на Ivybridge и Haswell, с буферами до больших, чтобы соответствовать MLC, вы можете бить movntdqa с помощью rep movsb; movntdqa несет RFO в LLC, rep movsb не... rep movsb значительно быстрее, чем movntdqa при потоковой передаче в память на Айвибридже и Хасуэлле (но имейте в виду, что pre-Ivybridge медленно!)
Что отсутствует/не оптимально в этой реализации memcpy?
Вот мои результаты в той же системе из 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
Я иногда вижу более 20 ГБ/с с REP MOVSB
.
с
sudo cpufreq-set -r -g powersave
лучшее, что я вижу, - около 17 ГБ/с. Но memcpy
, похоже, не чувствителен к управлению питанием.
Я проверил частоту (используя turbostat
) с включенным и включенным SpeedStep, с performance
и с powersave
для режима ожидания, 1-я нагрузка ядра и 4-х ядерная нагрузка. Я использовал умноженное умножение матрицы Intel MKL для создания нагрузки и задал количество потоков, используя OMP_SET_NUM_THREADS
. Вот таблица результатов (числа в ГГц).
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 процессор работает с постоянной частотой.
Я использовал, например, sudo cpufreq-set -r performance
(потому что cpufreq-set
давал странные результаты), чтобы изменить настройки мощности. Это снова включилось, поэтому мне пришлось отключить турбо.