AOT: Passei uma vez pelo código e o binário vai ser justamente isso aqui x.
JIT: Tive que passar várias vezes pelo mesmo trecho de código então acho que posso otimizar isso aqui x.
Valeu pelas dicas, vou assistir. Eu vejo o trade-off assim:
AOT: Não vai perder tempo durante o run-time compilando nada. Não precisa esquentar (warmup).
JIT: Vai perder tempo durante o run-time compilando os hot spots. Vai precisar de um tempo de warmup para peak performance.
Só que o JIT pode fazer otimizações muito mais agressivas que o AOT. Acho que o pessoal sempre achou que AOT com -O3 era a coisa mais rápida do mundo. Pelo testes que eu venho fazendo aí parece que perde muitas vezes para o JIT. Runtime information ajuda muito nas otimizações mais agressivas, principalmente no inlining. Sem saber os hotspots, o AOT vai acabar deixando de fazer inlining nas coisas mais importantes. Uma idéia que pensei aqui: por que os compiiladores de C++ não possuem alguma diretriz para forçar inlining de algum método no matter what? Ou tem isso e eu desconheço?
O que se faz é usar PGO (profiling information) that can be fed into the AOT compiler. Tanto o AOT quanto o JIT podem ter isso. Mas daí o JIT leva muita vantagem, porque se os HotSpots mudarem em tempo de execução ele pode se re-adaptar, ou seja, recompilar os novos hot spots. AOT não existe depois que o código começa a executar.
Na minha cabeça (posso estar errado) o problema é que se o código muda, daí vc tem que refazer o PGO para refletir o novo código. Se o código muda pouco no problem. Se muda muito aí complica.
Agora para concluir, minhas pesquisas com o PGO do GraalVM e da ZingVM (ReadyNow) mostraram uma melhorar de no máximo 50%. Ou seja, não chega perto do JIT em tempo de execução não. É uma ajuda, mas não é a solução para não precisar de JIT.
NOTE: PGO = Profile-guided optimizations