Qual é a melhor prática para usar uma switch
instrução vs usar uma if
instrução para 30 unsigned
enumeraçõ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();
}
Respostas:
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).
fonte
if
-else
cadeias na programação modelo meta, e a dificuldade de gerarswitch
case
correntes, 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)Para o caso especial que você forneceu no seu exemplo, o código mais claro é provavelmente:
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:
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.
fonte
const std::bitset<MAXERR> specialerror(initializer);
use-o comif (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 compiladorswitch
, esp. no caso não especial em que este será um único ramo não ocupado.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 aif
versão. Além disso, o gcc mais recente usará a instrução de teste de bits embt
vez de mudar para colocar um1
pouco no lugar certo e usandotest reg, imm32
.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:
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:
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 ...)
.
fonte
O compilador irá otimizá-lo assim mesmo - vá para o switch, pois é o mais legível.
fonte
gcc
nã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 .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.
fonte
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.
fonte
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!)
fonte
Compiladores são realmente bons em otimizar
switch
. O gcc recente também é bom em otimizar várias condições em umif
.Eu fiz alguns casos de teste no godbolt .
Quando os
case
valores 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
switch
para (eif
algo muito semelhante):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
switch
em um bitmap, mas o faz1U<<errNumber
com mov / shift. Compila aif
versão em séries de ramificações.Observe como subtrai 1 de
errNumber
(comlea
para 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,movabsq
que requer mais bytes de instrução.Uma sequência mais curta (no código da máquina) seria:
(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 porerrNumber
bits e fazer umtest
, 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
bt
da Intel recente (2 uops em vez de 1 e turno não faz todo o necessário).fonte
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.
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.
Os valores na opção são definidos pelo usuário, enquanto os valores no caso contrário são baseados em restrições.
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.
A caixa do switch é muito mais compacta e fácil de ler e entender.
fonte
Na IMO, este é um exemplo perfeito de como foi feita a troca.
fonte
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.
fonte
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ê.
fonte
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
if
condiçã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.
fonte
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 .
fonte
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,
if
alguma condição é verdadeira! Mudar com uma ação parece um pouco ... desnecessário.fonte
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.
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.
fonte
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.
fonte
std::bitset<MAXERR> specialerror;
, entãoif (specialerror[err]) { special_handler(); }
. Isso será mais rápido do que uma tabela de salto, esp. no caso não tomado.Não tenho certeza sobre as práticas recomendadas, mas usaria o switch - e, em seguida, capturaria a queda intencional via 'padrão'
fonte
Esteticamente, eu tendem a favorecer essa abordagem.
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):
fonte
break
externacase
(sim, uma menor pecado, mas um pecado). :)Provavelmente, o primeiro é otimizado pelo compilador, o que explicaria por que o segundo loop é mais lento ao aumentar a contagem de loop.
fonte
if
versusswitch
como uma condição de final de loop em Java.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:
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:
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 ().
fonte
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)
fonte
Eu sei que é velho, mas
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)
fonte
if(max) break
loop é executado em tempo constante, independentemente da contagem de loop? Parece que o compilador JIT é inteligente o suficiente para otimizar o loopcounter2=max
. E talvez seja mais lento do que mudar se a primeira chamada paracurrentTimeMillis
tiver mais sobrecarga, porque nem tudo ainda é compilado por JIT? Colocar os loops na outra ordem provavelmente daria resultados diferentes.