Vantagem da mudança sobre a instrução if-else

168

Qual é a melhor prática para usar uma switchinstrução vs usar uma ifinstrução para 30 unsignedenumerações em que cerca de 10 têm uma ação esperada (que atualmente é a mesma ação). O desempenho e o espaço precisam ser considerados, mas não são críticos. Eu abstraí o trecho para não me odiar pelas convenções de nomenclatura.

switch declaração:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if declaração:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}
Zing-
fonte
26
Isso é editado como 'subjetivo'? Realmente? Certamente "subjetivo" é para coisas que não podem ser provadas de uma maneira ou de outra?
Alexandra Franks
Certamente você pode vê-lo a partir do ponto em que gera o código mais eficiente, mas qualquer compilador moderno deve ser igualmente eficiente. No final, trata-se mais da cor da bicicleta.
jfs
8
Eu discordo, não acho que isso seja subjetivo. Uma diferença simples do ASM importa: você não pode simplesmente desconsiderar alguns segundos de otimização em muitos casos. E nesta pergunta, não é uma guerra ou debate religioso, há uma explicação racional de por que alguém seria mais rápido, basta ler a resposta aceita.
chakrit
1
Que é mais rápido: stackoverflow.com/questions/6805026/is-switch-faster-than-if
Ciro Santilli郝海东冠状病六四事件法轮功
@RichardFranks offtopic: grats! você é o primeiro ser humano tomado moderação no SO que eu já vi
jungle_mole

Respostas:

162

Use o interruptor.

Na pior das hipóteses, o compilador irá gerar o mesmo código que uma cadeia if-else, para que você não perca nada. Em caso de dúvida, coloque os casos mais comuns primeiro na instrução switch.

Na melhor das hipóteses, o otimizador pode encontrar uma maneira melhor de gerar o código. Coisas comuns que um compilador faz é construir uma árvore de decisão binária (salva comparações e saltos no caso médio) ou simplesmente criar uma tabela de saltos (funciona sem comparações).

Nils Pipenbrinck
fonte
2
Tecnicamente, ainda haverá uma comparação, para garantir que o valor da enum esteja dentro da tabela de salto.
0124816
Jep. Isso é verdade. Ativar enums e manipular todos os casos pode se livrar da última comparação.
Nils Pipenbrinck 19/09/08
4
Observe que teoricamente uma série de ifs poderia ser analisada como sendo uma opção de um compilador, mas por que arriscar? Ao usar um switch, você está se comunicando exatamente o que deseja, o que facilita a geração de código.
jakobengblom2
5
jakoben: Isso poderia ser feito, mas apenas para cadeias if / else do tipo comutador. Na prática, isso não ocorre porque os programadores usam o switch. Eu entrei na tecnologia do compilador e confie em mim: encontrar essas construções "inúteis" leva muito tempo. Para os compiladores, essa otimização faz sentido.
Nils Pipenbrinck 12/10/08
5
@NilsPipenbrinck com a facilidade de construção de pseudo-recursiva if- elsecadeias na programação modelo meta, e a dificuldade de gerar switch casecorrentes, que o mapeamento pode se tornar mais importante. (e sim, antigo comentário, mas a web é para sempre, ou pelo menos até a próxima terça-feira)
Yakk - Adam Nevraumont
45

Para o caso especial que você forneceu no seu exemplo, o código mais claro é provavelmente:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Obviamente, isso apenas move o problema para uma área diferente do código, mas agora você tem a oportunidade de reutilizar esse teste. Você também tem mais opções de como resolvê-lo. Você pode usar std :: set, por exemplo:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

Não estou sugerindo que esta seja a melhor implementação do RequerSpecialEvent, apenas que é uma opção. Você ainda pode usar uma chave ou cadeia if-else, uma tabela de pesquisa ou alguma manipulação de bits no valor, qualquer que seja. Quanto mais obscuro o seu processo de decisão se tornar, mais valor você terá por tê-lo em uma função isolada.

Mark Ransom
fonte
5
Isto é tão verdade. A legibilidade é muito melhor do que as instruções switch e if. Na verdade, eu mesmo ia responder algo assim, mas você me venceu. :-)
mlarsen 24/09/08
Se todos os seus valores de enumeração forem pequenos, não será necessário um hash, apenas uma tabela. por exemplo, const std::bitset<MAXERR> specialerror(initializer); use-o com if (specialerror[numError]) { fire_special_event(); }. Se você deseja verificar limites, bitset::test(size_t)lançará uma exceção nos valores fora dos limites. ( bitset::operator[]não verifica a faixa). cplusplus.com/reference/bitset/bitset/test . Isso provavelmente superará a implementação de uma tabela de salto gerada pelo compilador switch, esp. no caso não especial em que este será um único ramo não ocupado.
Peter Cordes
@ PeterCordes Eu ainda argumento que é melhor colocar a tabela em sua própria função. Como eu disse, há muitas opções que se abrem quando você faz isso, não tentei enumerar todas elas.
Mark Ransom
@ MarkRansom: Eu não quis discordar de abstraí-lo. Desde que você deu uma implementação de amostra usando std::set, pensei em apontar que provavelmente é uma má escolha. Acontece que o gcc já compila o código do OP para testar um bitmap em um imediato de 32 bits. godbolt: goo.gl/qjjv0e . O gcc 5.2 fará isso até para a ifversão. Além disso, o gcc mais recente usará a instrução de teste de bits em btvez de mudar para colocar um 1pouco no lugar certo e usando test reg, imm32.
Peter Cordes
Esse bitmap de constante imediata é uma grande vitória, porque não há falta de cache no bitmap. Funciona se os códigos de erro "especiais" estiverem todos no intervalo de 64 ou menos. (ou 32 para o código herdado de 32 bits.) O compilador subtrai o menor valor de caso, se for diferente de zero. O argumento é que os compiladores recentes são inteligentes o suficiente para que você provavelmente obtenha um bom código de qualquer lógica que use, a menos que você diga para usar uma estrutura de dados volumosa.
Peter Cordes
24

A troca é mais rápida.

Apenas tente se / else-30 valores diferentes dentro de um loop, e compará-lo com o mesmo código usando o switch para ver o quão mais rápido o switch é.

Agora, o switch tem um problema real : o switch deve saber em tempo de compilação os valores dentro de cada caso. Isso significa que o seguinte código:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

não irá compilar.

A maioria das pessoas usará define (Aargh!) E outras declararão e definirão variáveis ​​constantes na mesma unidade de compilação. Por exemplo:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

Portanto, no final, o desenvolvedor deve escolher entre "velocidade + clareza" vs. "código de acoplamento".

(Não que uma mudança não possa ser escrita para ser confusa como o inferno ... A maioria das mudanças que eu vejo atualmente são desta categoria "confusa" "... Mas essa é outra história ...)

Editar 21/09/2008:

bk1e adicionou o seguinte comentário: " Definir constantes como enumerações em um arquivo de cabeçalho é outra maneira de lidar com isso".

Claro que é.

O objetivo de um tipo externo era desacoplar o valor da fonte. Definir esse valor como uma macro, como uma simples declaração const int ou mesmo como uma enumeração tem o efeito colateral de incluir o valor. Portanto, se a definição, o valor de enumeração ou o valor const int for alterado, uma recompilação seria necessária. A declaração externa significa que não há necessidade de recompilar em caso de alteração de valor, mas, por outro lado, torna impossível usar o switch. A conclusão de usar a chave aumentará o acoplamento entre o código da chave e as variáveis ​​usadas como casos . Quando estiver ok, use switch. Quando não é, então, nenhuma surpresa.

.

Editar 15-01-13:

Vlad Lazarenko comentou a minha resposta, fornecendo um link para seu estudo aprofundado do código de montagem gerado por um switch. Muito esclarecedor: http://lazarenko.me/switch/

paercebal
fonte
Definir constantes como enumerações em um arquivo de cabeçalho é outra maneira de lidar com isso.
bk1e 21/09/08
1
@ Vlad Lazarenko: Obrigado pelo link! Foi uma leitura muito interessante.
paercebal
1
O link de @AhmedHussein user404725 está morto. Felizmente, eu o encontrei na WayBack Machine: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/… . De fato, a WayBack Machine pode ser uma bênção.
Jack Giffin
Muito obrigado, é muito útil
Ahmed Hussein
20

O compilador irá otimizá-lo assim mesmo - vá para o switch, pois é o mais legível.

Alexandra Franks
fonte
3
As chances são de que o compilador não toque se for o caso. De fato, gccnão fará isso com certeza (há uma boa razão para isso). Clang otimizará ambos os casos em uma pesquisa binária. Por exemplo, veja isso .
7

O Switch, apenas para facilitar a leitura. Gigante se as declarações são mais difíceis de manter e mais difíceis de ler na minha opinião.

ERROR_01 : // fall-through intencional

ou

(ERROR_01 == numErro) ||

O posterior é mais suscetível a erros e requer mais digitação e formatação do que o primeiro.

scubabbl
fonte
6

Código de legibilidade. Se você quiser saber o que tem melhor desempenho, use um criador de perfil, pois as otimizações e os compiladores variam, e os problemas de desempenho raramente são onde as pessoas pensam que estão.

Bdoserror
fonte
6

Use switch, é para isso e o que os programadores esperam.

Eu colocaria os rótulos redundantes de casos - apenas para fazer as pessoas se sentirem confortáveis, eu estava tentando lembrar quando / quais são as regras para deixá-las de fora.
Você não quer que o próximo programador que trabalha nele tenha que pensar desnecessariamente nos detalhes da linguagem (pode ser você em alguns meses!)

Martin Beckett
fonte
4

Compiladores são realmente bons em otimizar switch. O gcc recente também é bom em otimizar várias condições em um if.

Eu fiz alguns casos de teste no godbolt .

Quando os casevalores são agrupados, gcc, clang e icc são inteligentes o suficiente para usar um bitmap para verificar se um valor é um dos especiais.

por exemplo, gcc 5.2 -O3 compila o switchpara (e ifalgo muito semelhante):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Observe que o bitmap é um dado imediato, portanto, não há falta de cache de dados em potencial ao acessá-lo ou uma tabela de salto.

O gcc 4.9.2 -O3 compila o switchem um bitmap, mas o faz 1U<<errNumbercom mov / shift. Compila a ifversão em séries de ramificações.

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Observe como subtrai 1 de errNumber(com leapara combinar essa operação com uma movimentação). Isso permite ajustar o bitmap em um imediato de 32 bits, evitando o imediato de 64 bits, movabsqque requer mais bytes de instrução.

Uma sequência mais curta (no código da máquina) seria:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(A falha no uso jc fire_special_eventé onipresente e é um erro do compilador .)

rep reté usado em destinos de ramificação e após ramificações condicionais, para o benefício dos antigos AMD K8 e K10 (pré-Bulldozer): O que significa `rep ret`? . Sem ele, a previsão de ramificação não funciona tão bem nessas CPUs obsoletas.

bt(teste de bits) com um registro arg é rápido. Ele combina o trabalho de deslocar um 1 por errNumberbits e fazer um test, mas ainda é uma latência de 1 ciclo e apenas um único processador Intel. É lento com um argumento de memória por causa de sua semântica CISC: com um operando de memória para a "sequência de bits", o endereço do byte a ser testado é calculado com base no outro argumento (dividido por 8) e isn está limitado ao pedaço de 1, 2, 4 ou 8 bytes apontado pelo operando de memória.

Nas tabelas de instruções de Agner Fog , uma instrução de turno de contagem variável é mais lenta que a btda Intel recente (2 uops em vez de 1 e turno não faz todo o necessário).

Peter Cordes
fonte
4

As vantagens de switch () sobre o caso else são: - 1. O switch é muito mais eficiente em comparação com if else, porque cada caso não depende do caso anterior, ao contrário do caso contrário em que a declaração individual precisa ser verificada quanto à condição verdadeira ou falsa.

  1. Quando houver um não. de valores para uma única expressão, alternar entre maiúsculas e minúsculas se mais, pois caso contrário, caso contrário, o julgamento se baseará em apenas dois valores, ou seja, verdadeiro ou falso.

  2. Os valores na opção são definidos pelo usuário, enquanto os valores no caso contrário são baseados em restrições.

  3. Em caso de erro, as instruções no switch podem ser facilmente cruzadas e corrigidas, o que é comparativamente difícil de verificar no caso da instrução if else.

  4. A caixa do switch é muito mais compacta e fácil de ler e entender.

Vineeth Krishna K
fonte
2

Na IMO, este é um exemplo perfeito de como foi feita a troca.

Nescio
fonte
em c # este é o único caso em que o pensamento de queda acontece. Bom argumento aqui.
BCS
2

Se é provável que seus casos permaneçam agrupados no futuro - se mais de um caso corresponder a um resultado -, a opção poderá ser mais fácil de ler e manter.

TSomKes
fonte
2

Eles funcionam igualmente bem. O desempenho é praticamente o mesmo, dado um compilador moderno.

Eu prefiro as instruções if do que as case porque são mais legíveis e mais flexíveis - você pode adicionar outras condições não baseadas na igualdade numérica, como "|| max <min". Mas para o caso simples que você postou aqui, isso realmente não importa, basta fazer o que for mais legível para você.

SquareCog
fonte
2

switch é definitivamente preferido. É mais fácil olhar para a lista de casos de um comutador e saber ao certo o que está fazendo do que ler a longa condição.

A duplicação na ifcondição é difícil para os olhos. Suponha que um deles ==foi escrito !=; você notaria? Ou se uma instância de 'numError' fosse escrita 'nmuError', que acabou de compilar?

Geralmente, eu prefiro usar o polimorfismo em vez do switch, mas sem mais detalhes do contexto, é difícil dizer.

Quanto ao desempenho, sua melhor aposta é usar um criador de perfil para medir o desempenho do seu aplicativo em condições semelhantes às esperadas na natureza. Caso contrário, você provavelmente está otimizando no lugar errado e da maneira errada.

Jay Bazuzi
fonte
2

Concordo com a compacidade da solução do switch, mas na IMO você está seqüestrando o switch aqui.
O objetivo do comutador é ter uma manipulação diferente, dependendo do valor.
Se você tivesse que explicar seu algo em pseudo-código, usaria um if porque, semanticamente, é isso: se o que_erro fizer isso ...
Então, a menos que você pretenda algum dia alterar seu código para ter um código específico para cada erro , Eu usaria se .

Francesca
fonte
2
Discordo, pela mesma razão que discordo do caso de queda. Eu li o interruptor como "Nos casos 01,07,0A, 10,15,16 e 20 disparam evento especial". Não há queda para outra seção., Este é apenas um artefato da sintaxe C ++, em que você repete a palavra-chave 'case' para cada valor.
MSalters 19/09/08
1

Eu escolheria a declaração if por uma questão de clareza e convenção, embora tenha certeza de que alguns discordariam. Afinal, você quer fazer algo, ifalguma condição é verdadeira! Mudar com uma ação parece um pouco ... desnecessário.

William Keller
fonte
1

Eu diria que use SWITCH. Dessa forma, você só precisa implementar resultados diferentes. Seus dez casos idênticos podem usar o padrão. Se uma alteração for necessária, basta implementar explicitamente a alteração, sem a necessidade de editar o padrão. Também é muito mais fácil adicionar ou remover casos de um SWITCH do que editar IF e ELSEIF.

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

Talvez até teste sua condição (neste caso, numerror) em relação a uma lista de possibilidades, uma matriz talvez para que seu SWITCH nem seja usado, a menos que haja um resultado definitivo.

Lewis
fonte
Existem cerca de 30 erros no total. 10 exigem a ação especial, então eu estou usando o padrão para os erros ~ 20, que não exigem uma ação ...
Zing-
1

Como você possui apenas 30 códigos de erro, codifique sua própria tabela de salto e faça você mesmo todas as opções de otimização (o salto será sempre o mais rápido), em vez de esperar que o compilador faça a coisa certa. Também torna o código muito pequeno (além da declaração estática da tabela de salto). Ele também tem o benefício de que, com um depurador, você pode modificar o comportamento em tempo de execução, caso precise, apenas cutucando os dados da tabela diretamente.

Greg Whitfield
fonte
Uau, isso parece uma maneira de transformar um problema simples em um problema complexo. Por que enfrentar todos esses problemas quando o compilador fará um ótimo trabalho para você. Além disso, é aparentemente um manipulador de erros, portanto, não é provável que seja tão crítico quanto à velocidade. Uma opção é de longe a coisa mais fácil de ler e manter.
MrZebra 19/09/08
Uma tabela é dificilmente complexa - na verdade, é provavelmente mais simples que uma mudança para o código. E a declaração mencionou que o desempenho era um fator.
Greg Whitfield
Isso soa como otimização prematura. Desde que você mantenha seus valores de enumeração pequenos e contíguos, o compilador deve fazer isso por você. Colocar o comutador em uma função separada mantém o código que o usa agradável e pequeno, como sugere Mark Ransom em sua resposta, oferece o mesmo benefício de código pequeno.
Peter Cordes
Além disso, se você for implementar alguma coisa, faça um std::bitset<MAXERR> specialerror;, então if (specialerror[err]) { special_handler(); }. Isso será mais rápido do que uma tabela de salto, esp. no caso não tomado.
Peter Cordes
1

Não tenho certeza sobre as práticas recomendadas, mas usaria o switch - e, em seguida, capturaria a queda intencional via 'padrão'

da5id
fonte
1

Esteticamente, eu tendem a favorecer essa abordagem.

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

Torne os dados um pouco mais inteligentes, para que possamos tornar a lógica um pouco mais burra.

Eu percebo que parece estranho. Aqui está a inspiração (de como eu faria isso em Python):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()
mbac32768
fonte
4
Sintaxe de uma língua não tem um efeito sobre a forma como implementar uma solução ... => Parece feio em C e agradável em Python. :)
rlerallut
Usar bitmaps? Se error_0a for 0x0a etc, você poderá colocá-los em bits por muito tempo. long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... Em seguida, use if (special_events & (1LL << numError) fire_special_event ()
#
1
Que nojo. Você transformou uma operação de pior caso O (1) (se tabelas de salto são geradas) em pior caso O (N) (onde N é o número de casos manipulados) e usou uma a breakexterna case(sim, uma menor pecado, mas um pecado). :)
Mac
Que nojo? Ele disse que desempenho e espaço não são críticos. Eu estava simplesmente propondo outra maneira de encarar o problema. Se podemos representar um problema de uma maneira em que os humanos pensam menos, então normalmente não me importo se isso significa que os computadores precisam pensar mais.
mbac32768 12/12
1
while (true) != while (loop)

Provavelmente, o primeiro é otimizado pelo compilador, o que explicaria por que o segundo loop é mais lento ao aumentar a contagem de loop.

MarioFrost
fonte
Isso parece ser um comentário à resposta de McAnix. Esse é apenas um dos problemas com essa tentativa de sincronização ifversus switchcomo uma condição de final de loop em Java.
Peter Cordes
1

Quando se trata de compilar o programa, não sei se há alguma diferença. Mas, quanto ao programa em si e mantendo o código o mais simples possível, eu pessoalmente acho que depende do que você deseja fazer. if else if else declarações têm suas vantagens, que eu acho que são:

Para testar uma variável em relação a intervalos específicos, você pode usar funções (Biblioteca padrão ou Pessoal) como condicionais.

(exemplo:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

No entanto, as instruções If else if else podem ficar complicadas e confusas (apesar de suas melhores tentativas) com pressa. As instruções de troca tendem a ser mais claras, limpas e fáceis de ler; mas só pode ser usado para testar valores específicos (exemplo:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

Eu prefiro as instruções if - else if - else, mas realmente depende de você. Se você deseja usar as funções como condições, ou deseja testar algo em relação a um intervalo, matriz ou vetor e / ou não se importa em lidar com o aninhamento complicado, eu recomendaria o uso de If else se outros blocos. Se você deseja testar valores únicos ou deseja um bloco limpo e fácil de ler, recomendo que você use os blocos de caso switch ().

Jordan Effinger
fonte
0

Não sou a pessoa que lhe fala sobre velocidade e uso de memória, mas observar uma declaração de switch é muito mais fácil de entender do que uma declaração if grande (especialmente 2-3 meses abaixo da linha)

Ed Brown
fonte
0

Eu sei que é velho, mas

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

A variação da contagem de loop muda muito:

Enquanto if / else: 5ms Switch: 1ms Max Loops: 100000

While if / else: 5ms Switch: 3ms Max Loops: 1000000

While if / else: 5ms Switch: 14ms Loops máximos: 10000000

While if / else: 5ms Switch: 149ms Max Loops: 100000000

(adicione mais instruções se quiser)

McAnix
fonte
3
Bom ponto, mas sry, cara, você está no idioma errado. Variando a linguagem muda muito;)
Gabriel Schreiber
2
O if(max) breakloop é executado em tempo constante, independentemente da contagem de loop? Parece que o compilador JIT é inteligente o suficiente para otimizar o loop counter2=max. E talvez seja mais lento do que mudar se a primeira chamada para currentTimeMillistiver mais sobrecarga, porque nem tudo ainda é compilado por JIT? Colocar os loops na outra ordem provavelmente daria resultados diferentes.
Peter Cordes