Eu li todos os lugares que operador ternário é suposto ser mais rápido do que, ou pelo menos o mesmo que, o seu equivalente if
- else
bloco.
No entanto, fiz o seguinte teste e descobri que não é o caso:
Random r = new Random();
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);
long value = 0;
DateTime begin = DateTime.UtcNow;
foreach (int i in array)
{
if (i > 0)
{
value += 2;
}
else
{
value += 3;
}
// if-else block above takes on average 85 ms
// OR I can use a ternary operator:
// value += i > 0 ? 2 : 3; // takes 157 ms
}
DateTime end = DateTime.UtcNow;
MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString());
Meu computador levou 85 ms para executar o código acima. Mas se eu comentar o pedaço if
- else
e descomentar a linha do operador ternário, levará cerca de 157 ms.
Por que isso está acontecendo?
c#
performance
conditional-operator
user1032613
fonte
fonte
DateTime
para medir o desempenho. UseStopwatch
. Em seguida, tempo um pouco mais longo - é um tempo muito curto para medir.Random
objeto, para que ele sempre dê a mesma sequência. Se você testar código diferente com dados diferentes, poderá ver muito bem as diferenças no desempenho.Respostas:
Para responder a essa pergunta, examinaremos o código de montagem produzido pelos XIT e X64 JITs para cada um desses casos.
X86, se / então
X86, ternário
X64, se / então
X64, ternário
Primeiro: por que o código X86 é muito mais lento que o X64?
Isso ocorre devido às seguintes características do código:
i
da matriz, enquanto o X86 JIT coloca várias operações de pilha (acesso à memória) no loop.value
é um número inteiro de 64 bits, que requer 2 instruções de máquina no X86 (add
seguidas poradc
), mas apenas 1 no X64 (add
).Segundo: por que o operador ternário é mais lento no X86 e no X64?
Isso ocorre devido a uma diferença sutil na ordem das operações que afetam o otimizador do JIT. Para JIT o operador ternário, em vez de codificar diretamente
2
e3
nasadd
próprias instruções da máquina, o JIT criando uma variável intermediária (em um registro) para conter o resultado. Esse registro é então estendido de sinal de 32 bits para 64 bits antes de adicioná-lovalue
. Como tudo isso é realizado nos registros do X64, apesar do aumento significativo da complexidade do operador ternário, o impacto líquido é um pouco minimizado.O X86 JIT, por outro lado, é impactado em maior medida porque a adição de um novo valor intermediário no loop interno faz com que ele "derrame" outro valor, resultando em pelo menos 2 acessos adicionais à memória no loop interno (consulte os acessos para
[ebp-14h]
no código ternário X86).fonte
EDIT: Todas as alterações ... veja abaixo.
Eu não posso reproduzir seus resultados no CLR x64, mas eu puder em x86. No x64, vejo uma pequena diferença (menos de 10%) entre o operador condicional e o if / else, mas é muito menor do que você está vendo.
Fiz as seguintes alterações em potencial:
/o+ /debug-
e execute fora do depuradorStopwatch
Resultados com
/platform:x64
(sem as linhas "ignorar"):Resultados com
/platform:x86
(sem as linhas "ignorar"):Detalhes do meu sistema:
Então, ao contrário de antes, acho que você está vendo uma diferença real - e tudo tem a ver com o x86 JIT. Eu não gostaria de dizer exatamente o que está causando a diferença - posso atualizar o post mais tarde com mais detalhes, se eu puder me preocupar em entrar no cordbg :)
Curiosamente, sem classificar a matriz primeiro, termino com testes que demoram cerca de 4,5x, pelo menos em x64. Meu palpite é que isso tem a ver com previsão de ramificação.
Código:
fonte
A diferença realmente não tem muito a ver com if / else vs ternário.
Olhando para as desmontagens instaladas (não vou me refazer aqui, veja a resposta do @ 280Z28), acontece que você está comparando maçãs e laranjas . Em um caso, você cria duas
+=
operações diferentes com valores constantes e a que você escolhe depende de uma condição e, no outro caso, cria um+=
onde o valor a ser adicionado depende de uma condição.Se você realmente deseja comparar if / else e ternário, seria uma comparação mais justa (agora ambos serão igualmente "lentos", ou podemos até dizer que o ternário é um pouco mais rápido):
vs.
Agora, a desmontagem do tornado
if/else
é mostrada abaixo. Observe que isso é um pouco pior que o caso ternário, pois parou de usar os registradores para a variável de loop (i
) também.fonte
diff
, mas o ternário ainda é muito mais lento - nem um pouco o que você disse. Você fez o experimento antes de postar esta "resposta"?Editar:
Adicionado um exemplo que pode ser feito com a instrução if-else, mas não com o operador condicional.
Antes da resposta, consulte [O que é mais rápido? ] no blog do Sr. Lippert. E acho que a resposta do Sr. Ersönmez é a mais correta aqui.
Estou tentando mencionar algo que devemos ter em mente com uma linguagem de programação de alto nível.
Primeiro, nunca ouvi dizer que o operador condicional deveria ser mais rápido ou ter o mesmo desempenho da instrução if-else em C♯ .
O motivo é simples: e se não houver operação com a instrução if-else:
O requisito do operador condicional é que deve haver um valor para ambos os lados e, em C♯, também exige que ambos os lados
:
tenham o mesmo tipo. Isso apenas o torna diferente da instrução if-else. Assim, sua pergunta se torna uma pergunta perguntando como a instrução do código da máquina é gerada para que a diferença de desempenho.Com o operador condicional, semanticamente é:
Qualquer que seja a expressão avaliada, há um valor.
Mas com a declaração if-else:
Se a expressão for avaliada como verdadeira, faça algo; caso contrário, faça outra coisa.
Um valor não está necessariamente envolvido com a instrução if-else. Sua suposição só é possível com a otimização.
Outro exemplo para demonstrar a diferença entre eles seria o seguinte:
o código acima compila, no entanto, substitua a instrução if-else pelo operador condicional, que não compila:
O operador condicional e as instruções if-else são conceituais da mesma forma quando você faz a mesma coisa, possivelmente ainda mais rápido com o operador condicional em C , pois C está mais próximo da montagem da plataforma.
Para o código original que você forneceu, o operador condicional é usado em um loop foreach, o que atrapalha as coisas para ver a diferença entre eles. Então, estou propondo o seguinte código:
e a seguir estão duas versões do IL de otimizado e não. Como eles são longos, estou usando uma imagem para mostrar, o lado direito é o otimizado:
Nas duas versões do código, a IL do operador condicional parece mais curta que a instrução if-else, e ainda há uma dúvida do código da máquina finalmente gerado. A seguir, são apresentadas as instruções dos dois métodos, e a imagem anterior não é otimizada; a última é a otimizada:
Instruções não otimizadas: (Clique para ver a imagem no tamanho original.)
Instruções otimizadas: (Clique para ver a imagem no tamanho original.)
Neste último, o bloco amarelo é o código executado somente se
i<=0
, e o bloco azul é quandoi>0
. Em qualquer versão das instruções, a instrução if-else é mais curta.Observe que, para instruções diferentes, o [ CPI ] não é necessariamente o mesmo. Logicamente, para instruções idênticas, mais instruções custam um ciclo mais longo. Mas se o tempo de busca da instrução e o pipe / cache também foram levados em consideração, o tempo total real de execução depende do processador. O processador também pode prever as ramificações.
Os processadores modernos têm ainda mais núcleos; as coisas podem ser mais complexas com isso. Se você era um usuário de processador Intel, pode consultar o [ Manual de referência da otimização de arquiteturas Intel® 64 e IA-32 ].
Não sei se havia um CLR implementado por hardware, mas se sim, você provavelmente fica mais rápido com o operador condicional porque a IL é obviamente menor.
Nota: Todo o código da máquina é de x86.
fonte
Fiz o que Jon Skeet fez e executei 1 iteração e 1.000 iterações e obtive um resultado diferente do OP e Jon. Na mina, o ternário é apenas um pouco mais rápido. Abaixo está o código exato:
A saída do meu programa:
Outra execução em milissegundos:
Isso está sendo executado no XP de 64 bits e eu executei sem depuração.
Editar - Executando em x86:
Há uma grande diferença usando o x86. Isso foi feito sem a depuração na mesma máquina xp de 64 bits de antes, mas criado para as CPUs x86. Parece mais com os OPs.
fonte
O código do assembler gerado contará a história:
Gera:
Enquanto que:
Gera:
Portanto, o ternário pode ser mais curto e mais rápido, simplesmente devido ao uso de menos instruções e sem saltos, se você estiver procurando por verdadeiro / falso. Se você usar valores diferentes de 1 e 0, receberá o mesmo código que um if / else, por exemplo:
Gera:
Qual é o mesmo que o if / else.
fonte
Executar sem depurar ctrl + F5 parece que o depurador diminui significativamente o ifs e o ternário, mas parece que o operador ternário diminui muito mais.
Quando executo o seguinte código, aqui estão meus resultados. Eu acho que a pequena diferença de milissegundo é causada pelo compilador otimizando o max = max e removendo-o, mas provavelmente não está fazendo essa otimização para o operador ternário. Se alguém pudesse verificar a montagem e confirmar isso, seria incrível.
Código
fonte
Observando a IL gerada, há 16 menos operações nela do que na instrução if / else (copiar e colar o código do @ JonSkeet). No entanto, isso não significa que deve ser um processo mais rápido!
Para resumir as diferenças na IL, o método if / else se traduz praticamente da mesma forma que o código C # lê (executando a adição dentro da ramificação), enquanto o código condicional carrega 2 ou 3 na pilha (dependendo do valor) e depois o adiciona ao valor fora do condicional.
A outra diferença é a instrução de ramificação usada. O método if / else usa um brtrue (branch if true) para pular a primeira condição e um branch incondicional para pular da primeira saída da instrução if. O código condicional usa um bgt (branch se maior que) em vez de um brtrue, o que poderia ser uma comparação mais lenta.
Além disso (depois de ler sobre a previsão de ramificação), pode haver uma penalidade de desempenho por ser menor. O ramo condicional possui apenas 1 instrução dentro do ramo, mas o if / else possui 7. Isso também explicaria por que há uma diferença entre usar long e int, porque alterar para int reduz o número de instruções nos ramos if / else em 1 (diminuindo a leitura antecipada)
fonte
No código a seguir, se / else parece ser aproximadamente 1,4 vezes mais rápido que o operador ternário. No entanto, descobri que a introdução de uma variável temporária diminui o tempo de execução do operador ternário em aproximadamente 1,4 vezes:
fonte
Muitas respostas ótimas, mas achei algo interessante, mudanças muito simples causam impacto. Depois de fazer as alterações abaixo, para executar o operador if-else e ternário, levará o mesmo tempo.
em vez de escrever abaixo da linha
Eu usei isso,
Uma das respostas abaixo também menciona que o que é uma maneira ruim de escrever operador ternário.
Espero que isso ajude você a escrever um operador ternário, em vez de pensar em qual é o melhor.
Operador ternário aninhado: Encontrei operador ternário aninhado e vários blocos if else também levarão o mesmo tempo para serem executados.
fonte