Trabalhar em dados imutáveis com atribuições únicas tem o efeito óbvio de exigir mais memória, presume-se, porque você está constantemente criando novos valores (embora os compiladores ocultos façam truques de ponteiro para tornar isso menos um problema).
Mas ouvi algumas vezes agora que as perdas no desempenho são superadas pelos ganhos na maneira como a CPU (seu controlador de memória especificamente) pode tirar proveito do fato de que a memória não é mutada (tanto).
Eu esperava que alguém pudesse lançar alguma luz sobre como isso é verdade (ou se não é?).
Em um comentário em outro post , foi mencionado que os Abstract Data Types (ADTs) têm a ver com isso, o que me deixou ainda mais curioso, como os ADTs afetam especificamente a maneira como a CPU lida com a memória? No entanto, isso é um aparte, principalmente, estou interessado apenas em como a pureza da linguagem afeta necessariamente o desempenho da CPU e seus caches, etc.
fonte
let a = [1,2,3] in let b = 0:a in (a, b, (-1):c)
partilha reduz os requisitos de memória, mas depende da definição de(:)
e[]
e não o compilador. Eu acho que? Não tenho certeza sobre este.Respostas:
A vantagem é que esse fato evita que o compilador use as instruções do membar quando os dados são acessados.
Veja, quando os dados são acessados a partir de diferentes threads, na CPU com vários núcleos, é o seguinte: threads diferentes são executados em núcleos diferentes, cada um usando seu próprio cache (local ao núcleo) - uma cópia de algum cache global.
Se os dados são mutáveis e o programador precisa que ele seja consistente entre diferentes threads, é necessário tomar medidas para garantir a consistência. Para programador, isso significa usar construções de sincronização quando eles acessam (por exemplo, ler) dados em um encadeamento específico.
Para o compilador, a construção de sincronização no código significa que ele precisa inserir uma instrução membar para garantir que as alterações feitas na cópia dos dados em um dos núcleos sejam propagadas adequadamente ("publicadas"), para garantir que os caches em outros núcleos tenha a mesma cópia (atualizada).
Um pouco simplificador, veja a nota abaixo , eis o que acontece no processador multi-core para membar:
Veja bem, todos os núcleos não estão fazendo nada enquanto os dados estão sendo copiados entre os caches globais e locais . Isso é necessário para garantir que os dados mutáveis sejam sincronizados corretamente (thread-safe). Se houver 4 núcleos, todos os 4 param e esperam enquanto os caches estão sendo sincronizados. Se houver 8, todos os 8 param. Se houver 16 ... bem, você tem 15 núcleos que não fazem exatamente nada enquanto esperam pelo que é necessário fazer em um deles.
Agora, vamos ver o que acontece quando os dados são imutáveis? Não importa qual thread acessa, é garantido o mesmo. Para programador, isso significa que não há necessidade de inserir construções de sincronização quando eles acessam dados (lidos) em um encadeamento específico.
Para o compilador, isso significa que não há necessidade de inserir uma instrução membar .
Como resultado, o acesso aos dados não precisa parar núcleos e aguardar enquanto os dados estão sendo gravados entre caches globais e locais. Essa é uma vantagem do fato de a memória não estar mutada .
Observe que a explicação um pouco simplificadora acima elimina alguns efeitos negativos mais complicados de os dados serem mutáveis, por exemplo, no pipelining . Para garantir a solicitação necessária, a CPU precisa invalidar as linhas de controle afetadas pelas alterações de dados - isso é mais uma penalidade no desempenho. Se isso for implementado através da invalidação direta (e, portanto, confiável) de todos os pipelines, o efeito negativo será amplificado.
fonte