Há algum tempo, pesquisei e li muito sobre o alinhamento de memória, como funciona e como usá-lo. O artigo mais relevante que encontrei no momento é este .
Mas mesmo com isso ainda tenho algumas perguntas sobre isso:
- Fora do sistema incorporado, geralmente temos uma grande quantidade de memória em nosso computador que torna o gerenciamento de memória muito menos crítico, sou totalmente otimista, mas agora é realmente algo que pode fazer a diferença se compararmos o mesmo programa com ou sem a sua memória reorganizada e alinhada?
- O alinhamento da memória tem outras vantagens? Li em algum lugar que a CPU funciona melhor / mais rápido com memória alinhada, porque isso exige menos instruções para processar (se um de vocês tiver um link para um artigo / benchmark sobre isso?), Nesse caso, a diferença é realmente significativa? Há mais vantagens do que esses dois?
- No link do artigo, no capítulo 5, o autor diz:
Cuidado: em C ++, classes que parecem estruturas podem quebrar essa regra! (O que eles fazem ou não depende de como as classes base e as funções de membro virtual são implementadas e varia de acordo com o compilador.)
O artigo fala principalmente sobre estruturas, mas a declaração de variáveis locais também é afetada por essa necessidade?
Você tem alguma idéia de como o alinhamento de memória funciona exatamente em C ++, pois parece ter algumas diferenças?
Esta pergunta anterior contém a palavra "alinhamento", mas não fornece respostas para as perguntas acima.
fonte
Respostas:
Sim, o alinhamento e a organização dos seus dados podem fazer uma grande diferença no desempenho, não apenas alguns por cento, mas poucas ou muitas centenas de por cento.
Faça esse loop, duas instruções são importantes se você executar loops suficientes.
Com e sem cache e com alinhamento com e sem lançamento de cache na previsão de ramificação, é possível variar o desempenho dessas duas instruções em uma quantidade significativa (marcações do timer):
Um teste de desempenho que você pode fazer com muita facilidade. adicione ou remova nops ao redor do código em teste e faça um trabalho preciso de tempo, mova as instruções em teste ao longo de uma ampla variedade de endereços para tocar nas bordas das linhas de cache, etc.
O mesmo tipo de coisa com acessos de dados. Algumas arquiteturas reclamam de acessos desalinhados (executando uma leitura de 32 bits no endereço 0x1001, por exemplo), causando uma falha nos dados. Alguns deles você pode desativar a falha e sofrer o impacto no desempenho. Outros que permitem acessos desalinhados, você apenas obtém o desempenho.
Às vezes, são "instruções", mas na maioria das vezes são ciclos de relógio / ônibus.
Veja as implementações do memcpy no gcc para vários destinos. Digamos que você esteja copiando uma estrutura de 0x43 bytes, você pode encontrar uma implementação que copia um byte, deixando 0x42, depois copia 0x40 bytes em grandes blocos eficientes e, no último 0x2, pode ser feito como dois bytes individuais ou como uma transferência de 16 bits. O alinhamento e o destino entram em ação se os endereços de origem e destino estiverem no mesmo alinhamento, por exemplo, 0x1003 e 0x2003, então você pode fazer o byte, 0x40 em grandes blocos e depois 0x2, mas se um for 0x1002 e o outro 0x1003, obtém muito feio e muito lento.
Na maioria das vezes são ciclos de ônibus. Ou pior, o número de transferências. Pegue um processador com um barramento de dados de 64 bits de largura, como ARM, e faça uma transferência de quatro palavras (leitura ou gravação, LDM ou STM) no endereço 0x1004, que é um endereço alinhado por palavras e perfeitamente legal, mas se o barramento for 64 bits de largura, é provável que a instrução única se transforme em três transferências, neste caso, 32 bits em 0x1004, 64 bits em 0x1008 e 32 bits em 0x100A. Mas se você tivesse a mesma instrução, mas no endereço 0x1008, ele poderia fazer uma única transferência de quatro palavras no endereço 0x1008. Cada transferência tem um tempo de configuração associado. Portanto, a diferença de endereço de 0x1004 a 0x1008 por si só pode ser várias vezes mais rápida, mesmo / esp ao usar um cache e todos são hits do cache.
Falando nisso, mesmo se você fizer uma leitura de duas palavras no endereço 0x1000 vs 0x0FFC, o 0x0FFC com falhas de cache causará duas leituras de linha de cache em que 0x1000 é uma linha de cache, você terá a penalidade de ler uma linha de cache de maneira aleatória acesso (lendo mais dados do que usando), mas isso dobra. Como suas estruturas estão alinhadas ou seus dados em geral e sua frequência de acesso a esses dados, etc., podem causar problemas no cache.
Você pode acabar distribuindo seus dados de forma que, ao processar os dados, possa criar despejos, você pode ficar realmente azarado e acabar usando apenas uma fração do cache e, ao passar por ele, o próximo blob de dados colide com um blob anterior . Ao misturar seus dados ou reorganizar as funções no código-fonte, etc, você pode criar ou remover colisões, pois nem todos os caches são criados iguais, o compilador não irá ajudá-lo aqui. Até a detecção do impacto ou melhoria do desempenho é sua.
Todas as coisas que adicionamos para melhorar o desempenho, barramentos de dados mais amplos, pipelines, caches, previsão de ramificação, várias unidades / caminhos de execução, etc. Geralmente ajudarão, mas todos eles têm pontos fracos, que podem ser explorados intencionalmente ou acidentalmente. Há muito pouco que o compilador ou as bibliotecas podem fazer sobre isso, se você estiver interessado em desempenho, precisará ajustar e um dos maiores fatores de ajuste é o alinhamento do código e dos dados, não apenas os 32, 64, 128, 256 limites de bits, mas também onde as coisas são relativas umas às outras, você deseja que loops muito usados ou dados reutilizados não cheguem à mesma maneira de cache, pois cada um deles quer o seu. Os compiladores podem ajudar, por exemplo, na ordenação de instruções para uma arquitetura super escalar, reorganizando as instruções que são importantes uma para a outra,
A maior supervisão é a suposição de que o processador é o gargalo. Não é verdade há uma década ou mais, alimentar o processador é o problema e é aí que problemas como o desempenho do alinhamento atingem, a troca de cache, etc. entram em jogo. Com um pouco de trabalho, mesmo no nível do código-fonte, reorganizar os dados em uma estrutura, ordenar as declarações de variável / estrutura, ordenar as funções no código-fonte e um pouco de código extra para alinhar os dados, pode melhorar o desempenho várias vezes acima ou abaixo. Mais.
fonte
Sim, o alinhamento da memória ainda é importante.
Alguns processadores, na verdade, não podem executar leituras em endereços não alinhados. Se você estiver usando esse hardware e armazenar seus números inteiros não alinhados, provavelmente precisará lê-los com duas instruções seguidas de mais algumas instruções para colocar os vários bytes nos lugares certos, para que você possa usá-los . Dados alinhados são críticos para o desempenho.
A boa notícia é que você não precisa se preocupar. Quase qualquer compilador para quase qualquer idioma produzirá código de máquina que respeite os requisitos de alinhamento do sistema de destino. Você só precisa começar a pensar sobre isso se estiver assumindo o controle direto da representação na memória de seus dados, o que não é necessário nem em um lugar tão próximo quanto antes. É uma coisa interessante a saber e absolutamente crítico para saber se você deseja entender o uso da memória de várias estruturas que está criando e como reorganizar as coisas para que sejam mais eficientes (evitando o preenchimento). Mas, a menos que você precise desse tipo de controle (e para a maioria dos sistemas não precisa), você pode passar por uma carreira inteira sem saber ou se importar com isso.
fonte
Sim, isso ainda importa e, em alguns algoritmos críticos de desempenho, você não pode confiar no compilador.
Vou listar apenas alguns exemplos:
Se você não estiver trabalhando em algoritmos críticos de desempenho, esqueça os alinhamentos de memória. Não é realmente necessário para programação normal.
fonte
Nós tendemos a evitar situações onde isso importa. Se importa, importa. Dados não alinhados costumavam ocorrer, por exemplo, ao processar dados binários, o que parece ser evitado atualmente (as pessoas usam muito XML ou JSON).
Se você, de alguma maneira, conseguir criar uma matriz desalinhada de números inteiros, em um processador intel típico, o processamento de código dessa matriz será um pouco mais lento do que nos dados alinhados. Em um processador ARM, ele será executado um pouco mais devagar se você informar ao compilador que os dados estão desalinhados. Ele pode rodar muito, muito mais devagar ou gerar resultados incorretos, dependendo do modelo do processador e do sistema operacional, se você usar dados desalinhados sem informar o compilador.
Explicando a referência ao C ++: No C, todos os campos em uma estrutura devem ser armazenados em ordem crescente de memória. Portanto, se você possui os campos char / double / char e deseja alinhar tudo, terá um byte char, sete bytes não utilizados, oito bytes duplos, um byte char, sete bytes não utilizados. Nas estruturas C ++, é o mesmo para compatibilidade. Mas para estruturas, o compilador pode reordenar campos, portanto, você pode ter um byte char, outro byte char, seis bytes não utilizados e 8 bytes duplos. Usando 16 em vez de 24 bytes. Nas estruturas C, os desenvolvedores geralmente evitam essa situação e têm os campos em uma ordem diferente em primeiro lugar.
fonte
Muitos pontos positivos já são mencionados nas respostas acima. Apenas para adicionar, mesmo em sistemas não incorporados que lidam com a pesquisa / mineração de dados, o desempenho de questões de memória e tempos de acesso são tão importantes que, além do código de montagem de alinhamento, é escrito o mesmo.
Também recomendo uma leitura útil: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf
fonte
Sim. Não. Depende.
Seu aplicativo terá uma área ocupada por memória menor e funcionará mais rápido se estiver alinhado corretamente. No aplicativo de desktop típico, isso não importa fora de casos raros / atípicos (como seu aplicativo sempre terminando com o mesmo gargalo de desempenho e exigindo otimizações). Ou seja, o aplicativo será menor e mais rápido se alinhado corretamente, mas, na maioria dos casos práticos, não deve afetar o usuário de uma maneira ou de outra.
Pode ser. É algo que (possivelmente) você deve ter em mente ao escrever o código, mas na maioria dos casos isso simplesmente não deve importar (ou seja, eu ainda organizo minhas variáveis de membro por área de memória e frequência de acesso - o que deve facilitar o armazenamento em cache - mas faço isso para facilidade de uso / leitura e refatoração do código, não para fins de armazenamento em cache).
Eu li sobre isso quando o material de alinhamento saiu (C ++ 11?). Não me incomodei com isso desde então (estou fazendo principalmente aplicativos de desktop e desenvolvimento de servidores de back-end atualmente).
fonte