A maior parte do código de alto desempenho nos modernos jogos de console é escrita usando uma espécie de meio termo entre assembly e C ++: intrínsecas do compilador . Essas construções parecem e analisam como funções C ++, mas na verdade são traduzidas em instruções de máquina única . Assim, por exemplo, minha função "prender cada valor do vetor V a ser> = ae <= b" se parece com
// for each v.x, ensure v.x >= a.x && v.x <= b.x
inline __m128 ClampSIMD( const __m128 &v, const __m128 & a, const __m128 & b )
{
return _mm_max_ps( a, _mm_min_ps( v, b ) );
}
Em funções como essas, ainda estou pensando em termos das instruções específicas da máquina , mas tenho a conveniência de escrevê-las em C, para que não precise me preocupar com a coloração e programação do registro, as operações de carregamento e outros detalhes chatos.
Você ainda precisa estar ciente de quais instruções a CPU suporta, especialmente porque os compiladores modernos são péssimos na vetorização de código, em comparação com a capacidade de um ser humano inteligente fazer o trabalho. Às vezes, detalhes sutis de como você organiza seu código podem ter implicações enormes para o desempenho que não são óbvias sem entender o que a máquina está fazendo.
Embora não possamos codificar no assembly, ainda assim depuramos no assembly. A otimização dos compiladores reorganiza agressivamente o código de maneiras que os depuradores não conseguem acompanhar, com tanta freqüência ao depurar um "modo de liberação" que a melhor coisa a fazer é abrir o desmontador e rastrear o código dessa maneira. Essa palestra da GDC sobre "Depuração forense" de falhas ilustra muitos dos porquês e comos da depuração nesse nível.