Por que a velocidade do memcpy () diminui drasticamente a cada 4KB?

Eu testei a velocidade dememcpy() percebendo que a velocidade cai drasticamente em i * 4KB. O resultado é o seguinte: o eixo Y é a velocidade (MB / segundo) e o eixo X é o tamanho do buffer paramemcpy(), aumentando de 1 KB para 2 MB. As configurações 2 e 3 detalham a parte de 1 KB-150 KB e 1 KB-32 KB.

Meio Ambiente:

Processador: Intel (R) Xeon (R) CPU E5620 @ 2.40GHz

SO: 2.6.35-22-generic # 33-Ubuntu

Sinalizadores do compilador GCC: -O3 -msse4 -DINTEL_SSE4 -Wall -std = c99

Eu acho que deve estar relacionado a caches, mas não consigo encontrar um motivo para os seguintes casos hostis ao cache:

Por que meu programa fica lento ao fazer loop sobre exatamente 8192 elementos?

Por que a transposição de uma matriz de 512x512 é muito mais lenta que a transposição de uma matriz de 513x513?

Como a degradação do desempenho desses dois casos é causada por loops hostis que lêem bytes dispersos no cache, desperdiçando o resto do espaço de uma linha de cache.

Aqui está o meu código:

void memcpy_speed(unsigned long buf_size, unsigned long iters){
    struct timeval start,  end;
    unsigned char * pbuff_1;
    unsigned char * pbuff_2;

    pbuff_1 = malloc(buf_size);
    pbuff_2 = malloc(buf_size);

    gettimeofday(&start, NULL);
    for(int i = 0; i < iters; ++i){
        memcpy(pbuff_2, pbuff_1, buf_size);
    }   
    gettimeofday(&end, NULL);
    printf("%5.3f\n", ((buf_size*iters)/(1.024*1.024))/((end.tv_sec - \
    start.tv_sec)*1000*1000+(end.tv_usec - start.tv_usec)));
    free(pbuff_1);
    free(pbuff_2);
}
ATUALIZAR

Considerando as sugestões de @usr, @ChrisW e @Leeor, refiz o teste com mais precisão e o gráfico abaixo mostra os resultados. O tamanho do buffer é de 26 KB a 38 KB, e eu testei todos os outros 64B (26 KB, 26 KB + 64B, 26 KB + 128B, ......, 38 KB). Cada teste faz um loop de 100.000 vezes em cerca de 0,15 segundo. O interessante é que a queda não ocorre exatamente no limite de 4KB, mas também sai em 4 * i + 2 KB, com uma amplitude muito menor.

PS

O @Leeor ofereceu uma maneira de preencher a gota, adicionando um buffer fictício de 2 KB entrepbuff_1 epbuff_2. Funciona, mas não tenho certeza da explicação de Leeor.

questionAnswers(3)

yourAnswerToTheQuestion