Qual a diferença entre um compilador JIT e um compilador comum?

22

Tem havido muita publicidade sobre os compiladores JIT para linguagens como Java, Ruby e Python. Como os compiladores JIT são diferentes dos compiladores C / C ++ e por que os compiladores escritos para Java, Ruby ou Python são chamados de compiladores JIT, enquanto os compiladores C / C ++ são chamados de compiladores?

Ken Li
fonte

Respostas:

17

Os compiladores JIT compilam o código rapidamente, imediatamente antes da execução ou mesmo quando já estão em execução. Dessa forma, a VM em que o código está sendo executado pode procurar padrões na execução do código para permitir otimizações que seriam possíveis apenas com informações em tempo de execução. Além disso, se a VM decidir que a versão compilada não é boa o suficiente por qualquer motivo (por exemplo, muitas falhas de cache ou código frequentemente lançando uma exceção específica), poderá decidir recompilar de uma maneira diferente, levando a uma experiência muito mais inteligente. compilação.

Por outro lado, os compiladores C e C ++ não são tradicionalmente JIT. Eles são compilados em um único tiro apenas uma vez na máquina do desenvolvedor e, em seguida, um executável é produzido.

Victor Stafusa
fonte
Existem compiladores JIT que acompanham as falhas de cache e adaptam sua estratégia de compilação de acordo? @Victor
Martin Berger
@MartinBerger Não sei se algum compilador JIT disponível faz isso, mas por que não? À medida que os computadores são mais poderosos e a ciência da computação se desenvolve, as pessoas podem aplicar mais otimizações ao programa. Por exemplo, quando Java nasceu, é muito lento, talvez 20 vezes em comparação com aqueles compilados, mas agora o desempenho não está ficando para tanto e pode ser comparável a alguns compiladores
phuclv
Já ouvi isso em algum post de blog há muito tempo. O compilador pode compilar de maneira diferente, dependendo da CPU atual. Por exemplo, ele pode vetorizar algum código automaticamente se detectar que a CPU suporta SSE / AVX. Quando extensões SIMD mais recentes estão disponíveis, elas podem ser compiladas com as mais novas, para que o programador não precise alterar nada. Ou se pode organizar as operações / dados para aproveitar o cache maior ou reduzir Cache Miss
phuclv
@ LưuVĩnhPhúc Chào! Fico feliz em acreditar nisso, mas a diferença é que, por exemplo, o tipo de CPU ou o tamanho do cache são algo estático que não está mudando ao longo da computação, então também pode ser feito facilmente por um compilador estático. As falhas de cache OTOH são muito dinâmicas.
Martin Berger
12

JIT é a abreviação de compilador just-in-time, e o nome é misson: durante o tempo de execução, determina otimizações de código que valem a pena e as aplica. Ele não substitui os compiladores comuns, mas faz parte dos intérpretes. Observe que idiomas como Java, que usam código intermediário, têm ambos : um compilador normal para tradução de código intermediário e intermediário e um JIT incluído no interpretador para aumentar o desempenho.

As otimizações de código certamente podem ser executadas por compiladores "clássicos", mas observe a principal diferença: os compiladores JIT têm acesso aos dados em tempo de execução. Esta é uma enorme vantagem; explorá-lo corretamente pode ser difícil, obviamente.

Considere, por exemplo, código como este:

m(a : String, b : String, k : Int) {
  val c : Int;
  switch (k) {
    case 0 : { c = 7; break; }
    ...
    case 17 : { c = complicatedMethod(k, a+b); break; }
  }

  return a.length + b.length - c + 2*k;
}

Um compilador normal não pode fazer muito sobre isso. Um compilador JIT, no entanto, pode detectar que isso msó é chamado k==0por algum motivo (coisas assim podem acontecer à medida que o código muda com o tempo); ele pode criar uma versão menor do código (e compilá-lo no código nativo, embora eu considere isso um ponto secundário, conceitualmente):

m(a : String, b : String) {
  return a.length + b.length - 7;
}

Nesse ponto, provavelmente alinhará a chamada do método, já que é trivial agora.

Aparentemente, o Sun descartou a maioria das otimizações javacusadas no Java 6; Disseram-me que essas otimizações dificultavam muito o JIT e o código compilado ingenuamente era mais rápido no final. Vai saber.

Rafael
fonte
A propósito, na presença de apagamento de tipo (por exemplo, genéricos em Java), o JIT está realmente em desvantagem nos tipos wrt.
Raphael
Portanto, o tempo de execução é mais complicado do que o de uma linguagem compilada, porque o ambiente de tempo de execução deve coletar dados para otimizar.
Saadtaame 21/03
2
Em muitos casos, um JIT não seria capaz de substituir mcom uma versão que não verificar kuma vez que seria incapaz de provar que mpoderia nunca mais ser chamado com um diferente de zero k, mas mesmo sem ser capaz de provar que poderia substituir com static miss_count; if (k==0) return a.length+b.length-7; else if (miss_count++ < 16) { ... unoptimized code for m ...} else { ... consider other optimizations...}.
supercat
1
Concordo com o @supercat e acho que o seu exemplo é realmente muito enganador. Somente se k=0 sempre , o que significa que não é uma função da entrada ou do ambiente, é seguro deixar o teste - mas determinar isso exige análise estática, que é muito cara e, portanto, acessível apenas em tempo de compilação. Um JIT pode vencer quando um caminho através de um bloco de código é usado com muito mais frequência do que outros, e uma versão do código especializada para esse caminho seria muito mais rápida. Mas o JIT ainda emitirá um teste para verificar se o caminho rápido se aplica e seguir o "caminho lento", se não.
Jrandom_hacker 4/04
Um exemplo: Uma função pega um Base* pparâmetro e chama funções virtuais por meio dele; A análise de tempo de execução mostra que o objeto real apontado para sempre (ou quase sempre) parece ser do Derived1tipo. O JIT poderia produzir uma nova versão da função com chamadas estaticamente resolvidas (ou mesmo embutidas) a Derived1métodos. Esse código seria precedido por uma condicional que verifica se po ponteiro da Derived1tabela está apontando para a tabela esperada ; caso contrário, ele salta para a versão original da função com suas chamadas de método resolvidas dinamicamente mais lentamente.
Jrandom_hacker 4/04