Enhanced REP MOVSB for memcpy

Me gustaría utilizar REP MOVSB (ERMSB) mejorado para obtener un ancho de banda alto para una costumbrememcpy.

ERMSB se introdujo con la microarquitectura Ivy Bridge. Consulte la sección "Operación REP MOVSB y STOSB mejorada (ERMSB)" en elManual de optimización de Intel si no sabes qué es ERMSB.

La única forma en que sé hacer esto directamente es con el ensamblaje en línea. Obtuve la siguiente función dehttps://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;
}

Sin embargo, cuando uso esto, el ancho de banda es mucho menor que conmemcpy. __movsb obtiene 15 GB / symemcpy Obtenga 26 GB / s con mi sistema i7-6700HQ (Skylake), Ubuntu 16.10, DDR4 @ 2400 MHz de doble canal de 32 GB, GCC 6.2.

¿Por qué el ancho de banda es mucho menor conREP MOVSB? ¿Qué puedo hacer para mejorarlo?

Aquí está el código que usé para probar esto.

//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);  
}

La razón por la que estoy interesadorep movsb se basa en estos comentarios

Tenga en cuenta que en Ivybridge y Haswell, con buffers demasiado grandes para caber en MLC, puede vencer a movntdqa usando rep movsb; movntdqa incurre en una RFO en LLC, rep movsb no ... rep movsb es significativamente más rápido que movntdqa cuando se transmite a la memoria en Ivybridge y Haswell (¡pero ten en cuenta que antes de Ivybridge es lento!)

¿Qué falta / subóptimo en esta implementación de memcpy?

Aquí están mis resultados en el mismo sistema detinymembnech.

 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%)

Tenga en cuenta que en mi sistemaSSE2 copy prefetched también es más rápido queMOVSB copy.

En mis pruebas originales no desactivé el turbo. Inhabilité turbo y probé nuevamente y no parece hacer mucha diferencia. Sin embargo, cambiar la administración de energía hace una gran diferencia.

Cuando lo hago

sudo cpufreq-set -r -g performance

A veces veo más de 20 GB / s conrep movsb.

con

sudo cpufreq-set -r -g powersave

lo mejor que veo es de unos 17 GB / s. Peromemcpy no parece ser sensible a la administración de energía.

Verifiqué la frecuencia (usandoturbostat) con y sin SpeedStep habilitado, conperformance y conpowersave para inactivo, una carga de 1 núcleo y una carga de 4 núcleos. Ejecuté la multiplicación de matriz densa MKL de Intel para crear una carga y establecer el número de hilos usandoOMP_SET_NUM_THREADS. Aquí hay una tabla de resultados (números en 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

Esto muestra que conpowersave incluso con SpeedStep deshabilitado, la CPU sigue bajando a la frecuencia inactiva de0.8 GHz. Es solo conperformance sin SpeedStep que la CPU funciona a una frecuencia constante.

Usé por ejemplosudo cpufreq-set -r performance (porquecpufreq-set estaba dando resultados extraños) para cambiar la configuración de energía. Esto vuelve a encender el turbo, así que tuve que desactivarlo después.

Respuestas a la pregunta(6)

Su respuesta a la pregunta