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.
.globl ASMDELAY
ASMDELAY:
subs r0,r0,#1
bne ASMDELAY
bx lr
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):
min max difference
00016DDE 003E025D 003C947F
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.