Lembre-se de que o seguinte é apenas comparando a diferença entre compilação nativa e JIT e não cobre as especificidades de nenhum idioma ou estrutura em particular. Pode haver razões legítimas para escolher uma plataforma específica além disso.
Quando afirmamos que o código nativo é mais rápido, estamos falando do caso de uso típico de código compilado nativamente versus código compilado JIT, em que o uso típico de um aplicativo compilado JIT deve ser executado pelo usuário, com resultados imediatos (por exemplo, não esperando primeiro no compilador). Nesse caso, não acho que alguém possa afirmar com uma cara séria que o código compilado JIT pode corresponder ou vencer o código nativo.
Vamos supor que temos um programa escrito em alguma linguagem X, e podemos compilá-lo com um compilador nativo e novamente com um compilador JIT. Cada fluxo de trabalho possui os mesmos estágios envolvidos, que podem ser generalizados como (Código -> Representação Intermediária -> Código da Máquina -> Execução). A grande diferença entre dois é que etapas são vistas pelo usuário e quais são vistas pelo programador. Com a compilação nativa, o programador vê tudo, exceto o estágio de execução, mas com a solução JIT, a compilação no código da máquina é vista pelo usuário, além da execução.
A afirmação de que A é mais rápido que B refere-se ao tempo necessário para a execução do programa, conforme visto pelo usuário . Se assumirmos que os dois trechos de código executam identicamente no estágio Execution, devemos assumir que o fluxo de trabalho JIT é mais lento para o usuário, pois ele também deve ver o tempo T da compilação para o código da máquina, em que T> 0. Então , para qualquer possibilidade de o fluxo de trabalho JIT executar o mesmo que o fluxo de trabalho nativo, para o usuário, devemos diminuir o tempo de Execução do código, de forma que Execução + Compilação para código de máquina seja menor do que apenas o estágio Execution do fluxo de trabalho nativo. Isso significa que devemos otimizar o código melhor na compilação JIT do que na compilação nativa.
Isso, no entanto, é bastante inviável, já que para realizar as otimizações necessárias para acelerar a Execução, precisamos gastar mais tempo na fase de compilação para o código da máquina e, assim, qualquer tempo que salvarmos como resultado do código otimizado será realmente perdido, pois nós o adicionamos à compilação. Em outras palavras, a "lentidão" de uma solução baseada em JIT não é meramente devido ao tempo adicional para a compilação JIT, mas o código produzido por essa compilação é mais lento que uma solução nativa.
Vou usar um exemplo: Registrar alocação. Como o acesso à memória é milhares de vezes mais lento que o acesso ao registro, idealmente, queremos usar registros sempre que possível e ter o mínimo de acesso possível, mas temos um número limitado de registros e precisamos derramar estado na memória quando precisarmos. um registro. Se usarmos um algoritmo de alocação de registro que leva 200ms para calcular e, como resultado, economizamos 2ms em tempo de execução - não estamos fazendo o melhor uso possível para um compilador JIT. Soluções como o algoritmo de Chaitin, que pode produzir código altamente otimizado, são inadequadas.
O papel do compilador JIT é encontrar o melhor equilíbrio entre o tempo de compilação e a qualidade do código produzido, no entanto, com um grande viés no tempo de compilação rápido, pois você não deseja deixar o usuário esperando. O desempenho do código que está sendo executado é mais lento no caso JIT, pois o compilador nativo não fica muito vinculado (otimizado) pelo tempo na otimização do código, portanto, é livre para usar os melhores algoritmos. A possibilidade de que a compilação + execução geral para um compilador JIT possa superar apenas o tempo de execução do código compilado nativamente é efetivamente 0.
Mas nossas VMs não se limitam apenas à compilação JIT. Eles empregam técnicas de compilação antecipadas, armazenamento em cache, hot swap e otimizações adaptativas. Então, vamos modificar nossa alegação de que o desempenho é o que o usuário vê e limitar o tempo necessário para a execução do programa (suponha que tenhamos compilado AOT). Podemos efetivamente tornar o código em execução equivalente ao compilador nativo (ou talvez melhor?). Uma grande reivindicação das VMs é que elas podem produzir código de melhor qualidade do que um compilador nativo, porque ele tem acesso a mais informações - a do processo em execução, como a frequência com que uma determinada função pode ser executada. A VM pode aplicar otimizações adaptáveis ao código mais essencial via hot swap.
No entanto, existe um problema com esse argumento - ele pressupõe que a otimização guiada por perfil e similares é algo exclusivo das VMs, o que não é verdade. Também podemos aplicá-lo à compilação nativa - compilando nosso aplicativo com o perfil ativado, registrando as informações e recompilando o aplicativo com esse perfil. Provavelmente, também vale a pena ressaltar que a troca a quente de código não é algo que apenas um compilador JIT pode fazer, podemos fazê-lo para código nativo - embora as soluções baseadas em JIT para fazer isso estejam mais prontamente disponíveis e muito mais fáceis para o desenvolvedor. Portanto, a grande questão é: uma VM pode nos oferecer algumas informações que a compilação nativa não pode, o que pode aumentar o desempenho do nosso código?
Eu não posso ver isso sozinho. Também podemos aplicar a maioria das técnicas de uma VM típica ao código nativo - embora o processo esteja mais envolvido. Da mesma forma, podemos aplicar qualquer otimização de um compilador nativo de volta a uma VM que usa compilação AOT ou otimizações adaptativas. A realidade é que a diferença entre código executado nativamente e executado em uma VM não é tão grande quanto acreditamos. No final, eles levam ao mesmo resultado, mas adotam uma abordagem diferente para chegar lá. A VM usa uma abordagem iterativa para produzir código otimizado, onde o compilador nativo espera isso desde o início (e pode ser aprimorado com uma abordagem iterativa).
Um programador de C ++ pode argumentar que ele precisa das otimizações desde o início e não deve esperar uma VM descobrir como fazê-las, se houver. Este é provavelmente um ponto válido com a nossa tecnologia atual, pois o nível atual de otimizações em nossas VMs é inferior ao que os compiladores nativos podem oferecer - mas isso nem sempre pode ser o caso se as soluções AOT em nossas VMs melhorarem etc.