Por que o uso da mesma linha de cache de vários threads não causa lentidão grave?

Veja este trecho:

#include <atomic>
#include <thread>

typedef volatile unsigned char Type;
// typedef std::atomic_uchar Type;

void fn(Type *p) {
    for (int i=0; i<500000000; i++) {
        (*p)++;
    }
}

int main() {
    const int N = 4;

    std::thread thr[N];
    alignas(64) Type buffer[N*64];

    for (int i=0; i<N; i++) {
        thr[i] = std::thread(&fn, &buffer[i*1]);
    }

    for (int i=0; i<N; i++) {
        thr[i].join();
    }

}

Este pequeno programa incrementa quatro bytes adjacentes muitas vezes a partir de quatro threads diferentes. Antes, eu usava a regra: não use a mesma linha de cache de threads diferentes, pois o compartilhamento de linhas de cache é ruim. Então, eu esperava que uma versão de quatro threads (N=4) é muito mais lento que uma versão de um thread (N=1)

No entanto, estas são minhas medidas (em uma CPU Haswell):

N = 1: 1 segN = 4: 1,2 s

assimN=4 não é muito mais lento. Se eu usar linhas de cache diferentes (substitua*1 com*64), entãoN=4 torna-se um pouco mais rápido: 1,1 seg.

As mesmas medidas para acesso atômico (troque os comentários emtypedef), mesma linha de cache:

N = 1: 3,1 segN = 4: 48 seg

Então oN=4 caso é muito mais lento (como eu esperava). Se diferentes linhas de cache forem usadas,N=4 tem desempenho semelhante aoN=1: 3,3 seg.

Não entendo a razão por trás desses resultados. Por que não tenho uma desaceleração séria do não-atômico,N=4 caso? Quatro núcleos têm a mesma memória em seus caches; portanto, eles devem ser sincronizados de alguma forma, não é? Como eles podem funcionar quase perfeitamente paralelos? Por que apenas o caso atômico sofre uma desaceleração séria?

Eu acho que preciso entender como a memória é atualizada nesse caso. No começo, nenhum núcleo tembuffer em seus caches. Depois de umfor iteração (emfn), todos os 4 núcleos têmbuffer em suas linhas de cache, mas cada núcleo grava um byte diferente. Como essas linhas de cache são sincronizadas (no caso não atômico)? Como o cache sabe, quaisbyte está sujo? Ou existe algum outro mecanismo para lidar com este caso? Por que esse mecanismo é muito mais barato (na verdade, é quase gratuito) do que o atômico?

questionAnswers(2)

yourAnswerToTheQuestion