while (condition) {
...
}
Fluxo de trabalho:
- verificar condição;
- se falso, pula para fora do loop;
- execute uma iteração;
- pule para cima.
if (condition) do {
...
} while (condition);
Fluxo de trabalho:
- verificar condição;
- se falso, pula para além do loop;
- execute uma iteração;
- verificar condição;
- se verdadeiro, pule para a etapa 3.
Comparando esses dois, você pode facilmente ver que o último pode não fazer nenhum salto, desde que haja exatamente uma etapa no loop e, geralmente, o número de saltos será um a menos que o número de iterações. O primeiro terá que voltar para verificar a condição, apenas para sair do loop quando a condição for falsa.
Os saltos nas arquiteturas modernas de CPU com pipeline podem ser bastante caros: como a CPU está terminando a execução das verificações antes do salto, as instruções além desse salto já estão no meio do pipeline. Todo esse processamento deve ser descartado se a previsão do branch falhar. A execução posterior é atrasada enquanto o pipeline está sendo reprimido.
Explicando a previsão de desvio mencionada : para cada tipo de salto condicional, a CPU possui duas instruções, cada uma incluindo uma aposta no resultado. Por exemplo, você colocaria uma instrução dizendo " pule se não for zero, apostando no não zero " no final de um loop, porque o salto terá que ser feito em todas as iterações, exceto na última. Dessa forma, a CPU começa a bombear seu pipeline com as instruções que seguem o destino de salto, em vez daquelas que seguem a própria instrução de salto.
Nota importante
Por favor, não tome isso como um exemplo de como otimizar ao nível do código-fonte. Isso seria totalmente equivocado, pois, como já ficou claro em sua pergunta, a transformação da primeira forma para a segunda é algo que o compilador JIT faz rotineiramente, completamente por conta própria.