Memcpy toma el mismo tiempo que memset

Quiero medir el ancho de banda de la memoria usandomemcpy. Modifiqué el código de esta respuesta:por qué vectorizar el bucle no mejora el rendimiento que usómemset para medir el ancho de banda. El problema es esememcpy es solo un poco más lento quememset cuando espero que sea aproximadamente dos veces más lento ya que funciona con el doble de memoria.

Más específicamente, ejecuto más de 1 GB de matricesa yb (voluntad asignadacalloc) 100 veces con las siguientes operaciones.

operation             time(s)
-----------------------------
memset(a,0xff,LEN)    3.7
memcpy(a,b,LEN)       3.9
a[j] += b[j]          9.4
memcpy(a,b,LEN)       3.8

Darse cuenta dememcpy es solo un poco más lento quememset. Las operacionesa[j] += b[j] (dóndej va sobre[0,LEN)) debería tardar tres veces más quememcpy porque opera con el triple de datos. Sin embargo, solo es aproximadamente 2.5 tan lento comomemset.

Entonces inicialicéb a cero conmemset(b,0,LEN) y prueba de nuevo:

operation             time(s)
-----------------------------
memcpy(a,b,LEN)       8.2
a[j] += b[j]          11.5

Ahora vemos quememcpy es aproximadamente el doble de lento quememset ya[j] += b[j] es aproximadamente tres veces más lento quememset como espero

Por lo menos, hubiera esperado eso antesmemset(b,0,LEN) esememcpy seríamás lento debido a la asignación diferida (primer toque) en la primera de las 100 iteraciones.

¿Por qué solo obtengo el tiempo que espero despuésmemset(b,0,LEN)?

prueba.c

#include <time.h>
#include <string.h>
#include <stdio.h>

void tests(char *a, char *b, const int LEN){
    clock_t time0, time1;
    time0 = clock();
    for (int i = 0; i < 100; i++) memset(a,0xff,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    memset(b,0,LEN);
    time0 = clock();
    for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);

    time0 = clock();
    for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
    time1 = clock();
    printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
}

C Principal

#include <stdlib.h>

int tests(char *a, char *b, const int LEN);

int main(void) {
    const int LEN = 1 << 30;    //  1GB
    char *a = (char*)calloc(LEN,1);
    char *b = (char*)calloc(LEN,1);
    tests(a, b, LEN);
}

Compilar con (gcc 6.2)gcc -O3 test.c main.c. Clang 3.8 da esencialmente el mismo resultado.

Sistema de prueba: [email protected] (Skylake), 32 GB DDR4, Ubuntu 16.10. En mi sistema Haswell, los anchos de banda tienen sentido antesmemset(b,0,LEN) es decir, solo veo un problema en mi sistema Skylake.

Descubrí este problema por primera vez desdea[j] += b[k] operacionesen esta respuesta que estaba sobreestimando el ancho de banda.

Se me ocurrió una prueba más simple

#include <time.h>
#include <string.h>
#include <stdio.h>

void __attribute__ ((noinline))  foo(char *a, char *b, const int LEN) {
  for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
}

void tests(char *a, char *b, const int LEN) {
    foo(a, b, LEN);
    memset(b,0,LEN);
    foo(a, b, LEN);
}

Esto da salida.

9.472976
12.728426

Sin embargo, si lo hagomemset(b,1,LEN) en principal despuéscalloc (ver abajo) luego sale

12.5
12.5

Esto me lleva a pensar que este es un problema de asignación del sistema operativo y no un problema del compilador.

#include <stdlib.h>

int tests(char *a, char *b, const int LEN);

int main(void) {
    const int LEN = 1 << 30;    //  1GB
    char *a = (char*)calloc(LEN,1);
    char *b = (char*)calloc(LEN,1);
    //GCC optimizes memset(b,0,LEN) away after calloc but Clang does not.
    memset(b,1,LEN);
    tests(a, b, LEN);
}

Respuestas a la pregunta(2)

Su respuesta a la pregunta