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 sassimN=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:
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?