Otimização Elementar
Quando se trata de otimização, muitas pessoas desdenham: "As velocidades dos computadores são tão rápidas agora, qual é o sentido de ser alguns por cento mais rápido?" Isso faz algum sentido. Os resultados compilados pelos compiladores atuais foram totalmente otimizados. Exceto para o desenvolvimento de software específico, como gráficos, imagens e multimídia, a otimização deliberada não é necessária na maioria dos casos, mas se os desenvolvedores estiverem escrevendo código. ao mesmo tempo, você já tinha consciência da otimização. Ao concluir a otimização, você pode garantir ou até melhorar a eficiência do desenvolvimento.
Obviamente, o design do algoritmo é o núcleo da otimização. Na maioria dos casos, a eficiência de execução do programa é determinada principalmente pela compreensão geral do programa pelo desenvolvedor, pelo design do algoritmo, etc.! Mas às vezes a otimização dos detalhes também faz sentido!
Além disso, em muitos casos, esse tipo de otimização não requer a escrita de código diretamente por meio de assembly, mas neste caso também pode refletir a superioridade de dominar o conhecimento de assembly!
Como as duas funções a seguir:
função GetBit (i: Cardeal; n: Cardeal): Boolean;
começar
Resultado:= Boolean((i shr n) e 1);
fim;
função GetBit (i: Cardeal; n: Cardeal): Boolean;
começar
Resultado: = Boolean ((1 shl n) e i);
fim;
Código de montagem correspondente:
MOV ECX, EDX
SHR EAX, CL
E EAX, $ 01
MOV ECX, EDX
MOVEDX, $01
SHL EDX, CL
E EAX, EDX
Eles têm a mesma função, todos assumem o valor de um determinado bit de i, retornam True se for 1 e False se for 0!
Superficialmente, você pode pensar que a eficiência de execução das duas funções é a mesma, mas na verdade há uma diferença. A operação de deslocamento do primeiro programa é executada em i. De acordo com a convenção de chamada padrão no Delphi, registre-se. neste momento, i O valor é armazenado no registrador EAX, e a operação de deslocamento pode ser concluída diretamente, mas o segundo programa é diferente. Para concluir a operação de deslocamento no valor imediato 1, ele deve ser transferido primeiro para o registrador; então deve haver mais uma instrução! É claro que nem em todos os casos, menos instruções serão necessariamente mais rápidas do que mais instruções. Durante a execução específica, também devemos considerar questões como o ciclo de clock de execução da instrução e o emparelhamento de instruções (mais sobre isso mais tarde). o problema de forma independente. Somente quando as comparações só podem ser feitas em ambientes de código específicos.
Em circunstâncias normais, essa diferença na eficiência é insignificante, mas nunca é ruim manter a consciência da otimização durante a programação! Se tal código estiver localizado na camada mais interna de um loop e N ciclos de clock se acumularem através de um grande número de loops, a diferença na eficiência de execução pode se tornar muito grande!
O texto acima é apenas um pequeno exemplo. Pode-se ver que se você puder pensar em algumas questões do ponto de vista da montagem durante o desenvolvimento, poderá escrever código detalhado mais eficiente em linguagens de alto nível, garantindo a eficiência do desenvolvimento! Mas ainda há muitos momentos em que a otimização detalhada precisa ser concluída usando código assembly incorporado e, às vezes, devido à aplicação de código assembly incorporado, a escrita de código também pode se tornar mais eficiente.
Se você precisar reverter a ordem dos bytes de um número de 32 dígitos, como fazer isso completamente em linguagem de alto nível no Delphi? Você pode usar shifting, você também pode chamar a função interna Swap várias vezes, mas se você pensar em uma instrução BSWAP, tudo se tornará muito simples.
função SwapLong (Valor: Cardinal): Cardinal;
asm
BSWAP EAX
fim;
Nota: Assim como acima, o valor de Value é armazenado no registrador EAX, e o valor de 32 dígitos também é retornado por meio de EAX, portanto, apenas uma frase é necessária.
É claro que a maioria das otimizações de montagem incorporada não são tão simples, mas é difícil obter uma otimização mais profunda com o pouco conhecimento de montagem aprendido na faculdade. A experiência só pode ser adquirida por meio do acúmulo e comparação contínua de códigos de montagem compilados! Felizmente, na maioria dos casos, a otimização detalhada não é a parte principal do design do programa.
Porém, se o programa desenvolvido envolver gráficos, imagens, multimídia, etc., ainda é necessário realizar uma otimização mais aprofundada! Felizmente, o Delphi6 pode fornecer um bom suporte, seja na otimização de instruções de ponto flutuante ou na aplicação de MMX, SSE, 3DNow, etc. Mesmo se você quiser que versões anteriores do Delphi suportem esses conjuntos de instruções estendidas de CPU ou queira oferecer suporte a novos conjuntos de instruções de CPU no futuro, você pode usar as quatro instruções de montagem de DB, DW, DD e DQ suportadas pelo Delphi em montagem incorporada ( no Delphi6 oficial da Borland O manual do idioma diz apenas que ele suporta DB, DW e DD) e a representação numérica das instruções relevantes também pode ser implementada de forma flexível.
como:
DW$A20F //CPUID
DW $ 770F //EMMS
BD $0F, $6F, $C1 //MOVQ MM0, MM1
Compreender as instruções é apenas a base. Depois de projetar o algoritmo em torno de FPU, MMX e SSE, se quiser otimizá-lo ainda mais, você também deve compreender algumas das características técnicas da própria CPU.
Vamos dar uma olhada nos dois trechos de código a seguir:
asm
ADICIONAR [a], ECX
ADICIONAR [b], EDX
fim
asm
MOV EAX, [a]
MOV EBX, [b]
ADICIONAR EAX, ECX
ADICIONAR EBX, EDX
MOV[a], EAX
MOV [b], EBX
fim
O segundo é mais eficiente? Errado, como mencionado acima, menos instruções não significa alta eficiência de execução. De acordo com as informações relevantes, o ciclo de clock para a execução das duas instruções na primeira seção do código é 3 (cada instrução precisa completar três etapas de leitura, etapa de modificação e escrita), os ciclos de clock executados pelas seis instruções na segunda seção do código são todos 1. Então os dois trechos de código são igualmente eficientes? Errado novamente, o segundo trecho de código, na verdade, é executado com mais eficiência do que o primeiro trecho de código! Por que? Como as CPUs posteriores à classe Pentium têm dois pipelines para executar instruções, quando duas instruções adjacentes podem ser emparelhadas, elas podem ser executadas ao mesmo tempo! Específicos para os dois trechos de código acima, quais são os motivos específicos?
Embora as duas instruções do primeiro código possam ser emparelhadas, o ciclo de clock total de execução necessário é 5 em vez de 3, enquanto as seis instruções do segundo código podem ser executadas em paralelo, o que leva a este resultado.
Falando nisso, todos esses são exemplos muito simples, que por si só não podem ajudar muito. Se você realmente deseja otimizar um programa específico, deve procurar alguns artigos especiais sobre otimização de FPU e MMX, ou encontrar manuais técnicos para estudar e estudar tecnologias como "execução fora de ordem" e "previsão de ramificação". Só espero que todos os meus amigos que estão na faculdade não se concentrem apenas nas ferramentas de desenvolvimento "ganhadoras de dinheiro" e nas novas tecnologias da moda, mas possam dedicar mais tempo ao estabelecimento de bases. Com uma base sólida, você pode dominar rapidamente novos conhecimentos. , Só assim poderemos dominar novas ferramentas e habilidades de desenvolvimento em um tempo mais rápido... (omitir mil palavras).
Mas, novamente, o conhecimento ainda precisa ser usado para resolver problemas práticos. Se você se concentrar apenas nos detalhes técnicos todos os dias, poderá se tornar um excelente hacker, mas nunca desenvolverá software de primeira classe. Portanto, o propósito fundamental ainda deve ser a criação de valor. Então... não vou mais falar sobre isso, não vai parecer um artigo técnico se eu falar mais sobre isso. ^_^
Anexo: Além de considerar a eficiência de execução, a otimização do programa também deve considerar questões de tamanho (tamanho pequeno pode carregar a memória mais rapidamente e concluir a decodificação de instruções e outras tarefas mais rapidamente. Por exemplo, para limpar o registro EAX, use SUB EAX, EAX ou XOR EAX). , EAX em vez de MOV EAX, $0 Embora seus ciclos de clock de execução sejam ambos 1, o comprimento da instrução do primeiro (2 bytes) é obviamente menor que o do último (5 bytes). Mas como tudo o que foi mencionado acima são todos detalhes, a questão do volume não foi mencionada. Mais problemas de redução de tamanho devem ser resolvidos pelo compilador. Basta prestar um pouco de atenção ao escrever o código ASM incorporado.