As instruções switch são geralmente mais rápidas do que as instruções if-else-if equivalentes (como, por exemplo, descrito neste artigo ) devido às otimizações do compilador.
Como essa otimização realmente funciona? Alguém tem uma boa explicação?
As instruções switch são geralmente mais rápidas do que as instruções if-else-if equivalentes (como, por exemplo, descrito neste artigo ) devido às otimizações do compilador.
Como essa otimização realmente funciona? Alguém tem uma boa explicação?
Respostas:
O compilador pode construir tabelas de salto onde aplicável. Por exemplo, ao usar o refletor para examinar o código produzido, você verá que, para grandes interruptores em strings, o compilador irá gerar um código que usa uma tabela hash para despachá-los. A tabela de hash usa as strings como chaves e delega os case
códigos como valores.
Isso tem melhor tempo de execução assintótico do que muitos if
testes encadeados e é realmente mais rápido mesmo para relativamente poucas strings.
Esta é uma pequena simplificação, pois normalmente qualquer compilador moderno que encontra uma if..else if ..
sequência que poderia ser convertida trivialmente em uma instrução switch por uma pessoa, o compilador também o fará. Mas apenas para adicionar diversão extra, o compilador não é restrito pela sintaxe, portanto, pode gerar internamente "switch" como declarações que possuem uma mistura de intervalos, alvos únicos, etc - e eles podem (e fazem) fazer isso para switch e if. .outras declarações.
De qualquer forma, uma extensão da resposta de Konrad é que o compilador pode gerar uma tabela de salto, mas isso não é necessariamente garantido (nem desejável). Por uma variedade de razões, as tabelas de salto fazem coisas ruins para os preditores de ramificação nos processadores modernos, e as próprias tabelas fazem coisas ruins para o comportamento do cache, por exemplo.
switch(a) { case 0: ...; break; case 1: ...; break; }
Se um compilador realmente gerasse uma tabela de salto para isso, provavelmente seria mais lento que o if..else if..
código de estilo alternativo por causa da tabela de salto derrotando a previsão de ramificação.
As estatísticas de no-match podem não ser boas.
Se você realmente fizer o download da fonte, os valores sem correspondência são conhecidos como 21, em ambos os casos, if e switch. Um compilador deve ser capaz de abstrair, sabendo qual instrução deve ser executada o tempo todo, e uma CPU deve ser capaz de fazer previsões de ramificações corretamente.
O caso mais interessante é quando nem todos os casos quebram, em minha opinião, mas esse pode não ter sido o escopo do experimento.
As instruções switch / case podem ser tipicamente mais rápidas de nível 1, mas quando você começa a usar 2 ou mais, as instruções switch / case levam 2 a 3 vezes mais tempo do que as instruções if / else aninhadas.
Este artigo apresenta algumas comparações de velocidade destacando as diferenças de velocidade quando tais instruções são aninhadas.
Por exemplo, de acordo com seus testes, código de amostra como o seguinte:
if (x % 3 == 0)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else if (x % 3 == 1)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else if (x % 3 == 2)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
terminou na metade do tempo que a instrução switch / case equivalente levou para ser executada:
switch (x % 3)
{
case 0:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
case 1:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
case 2:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
default:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
}
Sim, é um exemplo rudimentar, mas ilustra o ponto.
Portanto, uma conclusão pode ser usar switch / case para tipos simples que têm apenas um nível de profundidade, mas para comparações mais complexas e vários níveis aninhados, use as construções if / else clássicas?