Pergunta

Eu não posso, para a vida de mim, lembre-se exatamente o que o nosso professor disse que o dia e eu estou esperando que você provavelmente conhece.

O módulo é "Algoritmos e Estruturas de Dados" e ele nos disse algo ao longo das linhas de:

O if instrução é o mais caro [algo].[algo] registra [algo].

Sim, eu tenho um horrível memória, e eu estou realmente muito triste, mas eu fui buscar no google por horas e nada surgiu.Qualquer idéias?

Foi útil?

Solução

No nível mais baixo (no hardware), sim, ses são caros.Para entender o porquê, você tem que entender como tubulações trabalho.

A atual instrução a ser executada é armazenado em algo que normalmente chamado de ponteiro de instrução (IP) ou o contador de programa (PC);estes termos são sinônimos, mas diferentes termos são utilizados com diferentes arquiteturas.Para mais instruções, o computador que a próxima instrução é apenas o PC atual e o comprimento da instrução atual.Para a maioria das arquitecturas RISC, as instruções são todas um comprimento constante, para que o PC pode ser incrementado por um valor constante.Para arquiteturas CISC como x86, as instruções podem ser de comprimento variável, de modo que a lógica que decodifica a instrução para descobrir por quanto tempo o atual instrução é encontrar a localização da próxima instrução.

Para ramo instruções, no entanto, a próxima instrução a ser executada não é o próximo local, após a instrução actual.Ramos são gotos - eles dizem que o processador onde a próxima instrução.Ramos pode ser condicional ou incondicional, e o local de destino pode ser fixo ou calculado.

Condicional vs.incondicional é fácil de entender - uma ramificação condicional só é feito se uma determinada condição é verdadeira (por exemplo, se um número é igual a outra);se o ramo não é tomada, o controle passa para a próxima instrução após o ramo como normal.Para incondicional ramos, o ramo é sempre tomada.Ramos condicionais mostrar-se em if as declarações e os testes de controle de for e while loops.Incondicional ramos de mostrar-se em loops infinitos, chamadas de função, a função retorna, break e continue declarações, o infame goto instrução, e muitos mais (estas listas estão longe de ser exaustiva).

O ramo de destino é outra questão importante.A maioria das agências tem um fixo ramo de destino - eles vão para um local específico no código que é corrigido em tempo de compilação.Isso inclui if declarações, loops de todos os tipos, regular chamadas de função, e muitos mais. Calculado ramos calcular o destino do ramo em tempo de execução.Isso inclui switch instruções (às vezes), retornando de uma função, a função virtual chamadas e chamadas de ponteiro de função.

Então, o que isso tudo significa para o desempenho?Quando o processador vê um ramo de instruções aparecem em seu pipeline, ele precisa descobrir uma maneira de continuar a encher o seu pipeline.A fim de descobrir quais as instruções que virão depois de o ramo do fluxo de programa, ele precisa saber duas coisas:(1) se a filial será tomado e (2) o destino do ramo.Descobrir isso é chamado de a previsão de ramificação, e é um problema desafiador.Se o processador palpites corretamente, o programa continua em plena velocidade.Se, em vez disso, o processador de palpites incorretamente, ele só passou algum tempo de computação a coisa errada.Ele tem, agora, para liberar seu pipeline e recarregá-lo com as instruções do correto caminho de execução.Linha inferior:um grande impacto no desempenho.

Assim, a razão por que, se as instruções são caros é devido a ramo mispredictions.Esta é apenas no nível mais baixo.Se você estiver escrevendo código de alto nível, você não precisa se preocupar com esses detalhes em tudo.Você só deve se preocupar se você estiver escrevendo extremamente críticas ao desempenho de código em C ou assembly.Se esse for o caso, a escrita do ramo de código livre pode ser muitas vezes superior ao código de ramos, mesmo se várias instruções são necessários.Há alguns bit legal-girando truques que você pode fazer para calcular coisas como abs(), min(), e max() sem ramificação.

Outras dicas

"Expensive" é um termo muito relativo, especialmente com relação a uma declaração "if" desde que você também tem que levar em conta o custo da doença. Isso pode variar de algumas instruções curto CPU para testar o resultado de uma função que chama para um banco de dados remoto.

Eu não me preocuparia com isso. A menos que você está fazendo incorporado programação você provavelmente não deve estar preocupado com o custo de "if" em tudo. Para a maioria dos programadores é só não vai nunca ser o fator determinante no desempenho do seu aplicativo.

Ramos, especialmente em RISC arquitetura microprocessadores, são algumas das instruções mais caras. Isto porque em muitas arquiteturas, o compilador prevê que caminho de execução serão tomadas mais provável e coloca essas instruções seguintes no executável, então eles já estará no cache da CPU quando o ramo acontece. Se o ramo vai a outra maneira, tem que ir de volta para a memória principal e buscar as novas instruções - que é bastante caro. Em muitas arquiteturas RISC, todas as instruções são um ciclo com exceção de ramo (que muitas vezes é 2 ciclos). Nós não estamos falando de um grande custo aqui, então não se preocupe com isso. Além disso, o compilador irá otimizar melhor do que você faz 99% do tempo :) Uma das coisas realmente impressionantes sobre a arquitetura EPIC (Itanium é um exemplo) é que ele armazena em cache (e começa a processar) instruções de ambos os lados do ramo, em seguida, descarta o conjunto não necessita uma vez que o resultado da filial é conhecido. Isso evita que o acesso à memória extra de uma arquitetura típica no caso em que se ramifica ao longo do caminho imprevisível.

Confira o artigo melhor desempenho através da filial Eliminação no Desempenho celular . Outra diversão é este post sobre branchless seleções no Blog Tempo real detecção de colisão.

Além das excelentes respostas já publicadas em resposta a esta pergunta, eu gostaria de colocar em um lembrete de que, embora "se" declarações são consideradas operações de baixo nível caros, tentando utilizar técnicas de programação livre de filial em uma maior ambiente de nível, como uma linguagem de script ou uma camada de lógica de negócios (independentemente da linguagem), pode ser ridiculamente inadequadas.

A grande maioria das vezes, os programas devem ser escritos para maior clareza primeiro e otimizado para segunda performance. Existem inúmeros domínios de problemas onde o desempenho é fundamental, mas o simples fato é que a maioria dos desenvolvedores não estão escrevendo módulos para uso no fundo do núcleo de um motor de renderização ou de uma simulação de alta performance dinâmica dos fluidos que se estende por semanas a fio. Quando a prioridade é para a sua solução para o "trabalho apenas" a última coisa em sua mente deve ser ou não você pode economizar na sobrecarga de uma instrução condicional no seu código.

No menor if nível possível consiste em (após calcular todos os pré-requisitos do específicas do aplicativo para determinado if):

  • alguma instrução de teste
  • salto para algum lugar no código se o teste for bem-sucedido, continue a frente de outra forma.

Os custos associados com isso:

  • uma comparação de baixo nível - geralmente operação 1 cpu, super barato
  • salto potencial - que pode ser caro

Reson por saltos são caros:

  • você pode saltar para o código arbirary que vidas em qualquer lugar na memória, se ele sair que não é armazenada em cache pelo cpu - temos um problema, porque precisamos de acesso à memória principal, que é mais lento
  • CPUs modernos fazem de predição ramo. Eles tentam adivinhar se se terá sucesso ou não e executar frente código na calha, de modo a acelerar as coisas. Se a previsão falhar toda cálculo feito antes por gasoduto tem de ser invalidada. Isso também é uma operação cara

Assim, para resumir:

  • Se pode ser expesive, se você realmente, realmente, relly se preocupam com o desempenho.
  • Você deve se preocupar com ele se e só se você está escrevendo raytracer tempo real ou simulação biológica ou algo similar. Não há nenhuma razão para se preocupar com isso na maior parte do mundo real.

if em si é não lento. Lentidão é sempre aposta i relativo para minha vida que você não já sentiu o "overhead" de uma instrução if. Se você estiver indo para fazer um código de alto desempenho, você migh quer evitar ramos de qualquer maneira. O que torna if lento é que o processador é o pré-carregamento de código de após a if baseado em alguma heurística e outros enfeites. Ele também irá parar de pipelines de executar código diretamente depois da instrução if filial no código de máquina, já que o processador ainda não sabe que caminho vai tomar (em um processador em conduta, múltiplas instruções são intercalados e executado). Código executado poderia ter para ser executado em sentido inverso (se o outro ramo foi tirada. Ele é chamado branch misprediction), ou noop de ser preenchida a esses lugares para que isso não aconteça.

Se if é mal, então switch é mau também, e &&, || também. Não se preocupe com isso.

Talvez a ramificação mata o prefetching instrução CPU?

Os processadores modernos possuem pipelines de execução longos que significa que várias instruções são executadas em vários estágios ao mesmo tempo. Eles podem não saber sempre o resultado de uma instrução quando a próxima começa a correr. Quando se deparam com um salto condicional (se) eles às vezes têm que esperar até que o gasoduto está vazio antes que eles possam saber que maneira o ponteiro de instrução deve ir.

Eu penso dele como um trem de carga longa. Ele pode transportar uma grande quantidade de carga rápida em uma linha reta, mas cantos mal.

Pentium 4 (Prescott) tinha uma famosa encanamento longo de 31 etapas.

Mais sobre Wikipedia

A única coisa que eu posso imaginar isso pode estar se referindo a é o fato de que uma declaração if geralmente pode resultar em um galho. Dependendo das especificações da arquitetura do processador, ramos pode causar barracas de conduta ou outros menos de situações ideais.

No entanto, isso é extremamente situação específica - a maioria dos processadores modernos têm capacidades de previsão ramo que tentam minimizar os efeitos negativos da ramificação. Outro exemplo seria como a arquitetura ARM (e provavelmente outros) pode lidar com lógica condicional - o ARM tem execução condicional nível de instrução, de modo simples condicionais resultados lógicos em nenhuma ramificação -. As instruções simplesmente executar como NOPs se não estiverem reunidas as condições

Tudo o que disse - obter a sua lógica correta antes de se preocupar com essas coisas. código incorreto é tão unoptimized como você pode começar.

Como apontado por muitos, desvios condicionais pode ser muito lenta em um computador moderno.

Dito isto, há um monte de desvios condicionais que não vivem no if, você não pode sempre dizer o que o compilador vai chegar a, e se preocupar com quanto tempo afirmações básicas irá tomar é quase sempre a coisa errada a fazer. (Se você pode dizer o que o compilador irá gerar de forma confiável, você não pode ter um compilador otimizar bom.)

CPUs estão profundamente pipeline. Qualquer instrução de desvio (se / for / while / switch / etc) significa que a CPU não sabe realmente o que instrução para carregar e executar em seguida.

A CPU quer barracas enquanto esperam para saber o que fazer, ou a CPU tem um palpite. No caso de uma CPU mais velho, ou se o palpite é errado, você vai ter que sofrer uma tenda tubagem enquanto ele vai e cargas a instrução correta. Dependendo da CPU isso pode ser tão elevada quanto 10-20 instruções valor de stall.

CPUs modernos tentam evitar isso fazendo boa previsão de desvios, e executando vários caminhos ao mesmo tempo, e só mantendo o real. Isso ajuda muito, mas só pode ir tão longe.

Boa sorte na classe.

Além disso, se você tem que se preocupar com isso na vida real, provavelmente você está fazendo design OS, gráficos em tempo real, computação científica, ou algo semelhante CPU-bound. Perfil Antes de se preocupar.

Observe também que dentro de um loop é não necessariamente muito caro.

Modern CPU assume após a primeira visita de uma instrução if, que o "if-corpo" é para ser tomado (ou dito de outra maneira: ele também assume um loop-corpo a ser tomadas várias vezes) (*). Após a segunda e outras visitas, ele (o CPU) pode talvez olhar para o Ramo História Table , e ver como a condição foi a última vez (era verdade? Era falsa?). Se fosse falsa pela última vez, em seguida, execução especulativa vai avançar para a "outra" do caso, ou para além do loop.

(*) A regra é, na verdade, " ramo frente não tomadas, ramo trás tomado ". Em uma instrução if, há única a [frente] salto (a ponto depois que o corpo se-) se os avalia a condição false (lembre-se: a CPU de qualquer maneira assume a não tomar uma filial / salto), mas em um loop, há talvez um ramo para a frente para a posição após o loop (não deve ser tomada) e um ramo para trás em cima repetetion (a serem tomadas).

Esta é também uma das razões pelas quais uma chamada para uma função virtual ou uma função de ponteiro-call não é tão mau como muitos supõem ( http://phresnel.org/blog/ )

Escreva seus programas a maneira mais clara, mais simples, mais limpa que não é obviamente ineficiente. Que faz o melhor uso do recurso mais caro, você. Seja ela escrita ou mais tarde depuração (requer entendimento) o programa. Se o desempenho não é suficiente, medida quais são os gargalos, e ver como mitigá-los. Só em ocasiões extremamente raros que você vai ter que se preocupar com instruções individuais (fonte) quando fazê-lo. O desempenho é sobre como selecionar os algoritmos adequados e estruturas de dados na primeira linha, programação cuidadosa, recebendo uma máquina bastante rápido. Use um bom compilador, você ficaria surpreso ao ver o tipo de código de reestruturação um compilador moderno faz. Reestruturar código para o desempenho é uma espécie de medida de último recurso, o código se torna mais complexa (assim buggier), mais difícil de modificar, e, assim, all-around mais caro.

Eu tive essa discussão com um amigo meu uma vez. Ele estava usando um algoritmo muito ingênuo do círculo, mas alegou que seu para ser mais rápido do que o meu (o tipo que só calcula 1 / 8th do círculo) porque meu costumava se. No final, se a declaração foi substituído por sqrt e de alguma forma que era mais rápido. Talvez porque a FPU tem sqrt construído em?

Alguns de CPU (como X86) fornece previsão de desvios a nível de programação para evitar tal latência previsão de desvios.

Alguns expõe compilador (como GCC) estes como uma extensão para maiores linguagens de programação nível (como C / C ++).

Consulte provável () / improvável () macros no kernel do Linux - como eles trabalham? Qual é o seu benefício? .

O mais caro em termos de uso ALU? Ele usa-se registros de CPU para armazenar os valores a serem comparados e toma tempo para buscar e comparar os valores cada vez que a instrução if é executado.

Portanto, uma otimização do que é fazer uma comparação e armazenar o resultado como uma variável antes do loop é executado.

Apenas tentando interpretar suas palavras que faltam.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top