Qual das seguintes técnicas é a melhor opção para dividir um número inteiro por 2 e por quê?
Técnica 1:
x = x >> 1;
Técnica 2:
x = x / 2;
Aqui x
está um número inteiro.
c++
c
optimization
division
micro-optimization
Abhineet
fonte
fonte
x
novamente, nenhum deles é apropriado desta maneira: deve ser umx >>= 1
oux /= 2
, dependendo do que você pretende expressar com a operação. Não porque é mais rápido (qualquer compilador moderno compilará todas as variantes equivalentes para montagem rápida e idêntica de qualquer maneira), mas porque é menos confuso.x = x >>> 1
. Observe também que, dependendo da plataforma e do compilador, pode ser bastante razoável otimizar manualmente divisões e multiplicações usando turnos. - Pensando em microcontroladores, por exemplo, sem suporte direto da ALU para multiplicação.x /= 2
, porquex >>= 1
parece muito com o ligamento monádico;)x = x / 2
vez dex /= 2
. Preferência subjetiva talvez :)⬜=
combinações, elas devem ser usadas sempre que possível. Ele remove o ruído e enfatiza o fato de quex
é modificado , enquanto o=
operador geral sugere que ele adquire um valor completamente novo, independente do antigo. - Sempre evitando os operadores combinados (de modo que seja legível por isso alguém que só sabe operadores matemáticos) pode ter seu ponto bem, mas depois que você precisa abandonar o extremamente útil++
,--
,+=
também.Respostas:
Use a operação que melhor descreve o que você está tentando fazer.
Observe que eles não são exatamente equivalentes. Eles podem fornecer resultados diferentes para números inteiros negativos. Por exemplo:
(ideona)
fonte
%
e/
devem ser consistentes para os operandos positivos e negativos, para que isso ocorra(a/b)*b+(a%b)==a
independentemente dos sinais dea
eb
. Geralmente, o autor faz escolhas que obtêm o melhor desempenho possível da CPU.O primeiro parece dividir? Não. Se você deseja dividir, use
x / 2
. O compilador pode otimizá-lo para usar a troca de bits, se possível (é chamado de redução de força), o que torna uma micro-otimização inútil se você fizer isso por conta própria.fonte
Para empilhar: há muitas razões para favorecer o uso
x = x / 2;
Aqui estão algumas:expressa sua intenção com mais clareza (supondo que você não esteja lidando com bits de registro de twiddling ou algo assim)
o compilador reduzirá isso para uma operação de turno de qualquer maneira
mesmo que o compilador não o reduza e escolha uma operação mais lenta que a mudança, a probabilidade de que isso acabe afetando o desempenho do seu programa de uma maneira mensurável é muito pequena (e se isso o afetar de forma mensurável, você terá uma experiência real). razão para usar um turno)
se a divisão fizer parte de uma expressão maior, é mais provável que você tenha a precedência correta se usar o operador de divisão:
aritmética assinada pode complicar ainda mais o problema de precedência mencionado acima
para reiterar - o compilador já fará isso por você de qualquer maneira. De fato, ele converterá a divisão por uma constante em uma série de turnos, somas e multiplicações para todos os tipos de números, não apenas para potências de dois. Veja esta pergunta para obter links para mais informações sobre isso.
Em resumo, você não compra nada codificando uma mudança quando realmente pretende multiplicar ou dividir, exceto, talvez, uma maior possibilidade de introduzir um bug. Faz uma vida desde que os compiladores não eram inteligentes o suficiente para otimizar esse tipo de coisa para uma mudança quando apropriado.
fonte
a/b/c*d
(ondea..d
as variáveis numéricas são denotadas) em vez de muito mais legível(a*d)/(b*c)
.a*d
oub*c
iria produzir um estouro, a forma menos legível não é equivalente e tem uma vantagem óbvia. PS Concordo que os parênteses são seu melhor amigo.a/b/c*d
código R - em um contexto em que o estouro significaria que algo estava seriamente errado com os dados - e não em, digamos, um bloco crítico de desempenho do código C).x=x/2;
é apenas "mais claro" do quex>>=1
sex
nunca for um número negativo ímpar ou se não se importar com erros pontuais. Caso contrário,x=x/2;
ex>>=1;
ter significados diferentes. Se o que se precisa é o valor calculadox>>=1
, eu consideraria isso mais claro quex = (x & ~1)/2
oux = (x < 0) ? (x-1)/2 : x/2
ou qualquer outra formulação que eu possa pensar em usar a divisão por dois. Da mesma forma, se é necessário o valor calculado porx/=2
, isso é mais claro que((x + ((unsigned)x>>31)>>1)
.Depende do que você quer dizer com melhor .
Se você deseja que seus colegas o odeiem ou dificulte a leitura do código, eu definitivamente aceitaria a primeira opção.
Se você deseja dividir um número por 2, vá com o segundo.
Os dois não são equivalentes, eles não se comportam da mesma forma se o número for negativo ou dentro de expressões maiores - o deslocamento de bits tem precedência menor que
+
ou-
, a divisão tem precedência mais alta.Você deve escrever seu código para expressar qual é sua intenção. Se o desempenho é sua preocupação, não se preocupe, o otimizador faz um bom trabalho nesse tipo de micro-otimização.
fonte
Basta usar o split (
/
), presumindo que seja mais claro. O compilador otimizará adequadamente.fonte
ASSUME(x >= 0); x /= 2;
maisx >>= 1;
, mas esse ainda é um ponto importante a ser abordado.Concordo com outras respostas que você deve favorecer
x / 2
porque sua intenção é mais clara e o compilador deve otimizá-lo para você.No entanto, outro motivo para preferir o
x / 2
excessox >> 1
é que o comportamento de>>
depende da implementação sex
for assinadoint
e for negativo.Na seção 6.5.7, ponto 5 da norma ISO C99:
fonte
x>>scalepower
números negativos será exatamente o necessário para dividir um valor por uma potência de dois para fins como renderização de tela, enquanto o usox/scalefactor
será errado a menos que se aplique correções a valores negativos.x / 2
é mais claro ex >> 1
não é muito mais rápido (de acordo com uma micro-referência, cerca de 30% mais rápido para uma Java JVM). Como outros observaram, para números negativos o arredondamento é um pouco diferente; portanto, você deve considerar isso quando quiser processar números negativos. Alguns compiladores podem converter automaticamentex / 2
parax >> 1
se souberem que o número não pode ser negativo (mesmo que eu não pudesse verificar isso).Even
x / 2
pode não usar a instrução da CPU de divisão (lenta), porque alguns atalhos são possíveis , mas ainda é mais lento quex >> 1
.(Este é um C / C ++ pergunta, outras linguagens de programação têm mais operadores. Para Java, há também o deslocamento para a direita sem sinal,
x >>> 1
que é novamente diferente. Ele permite calcular corretamente o valor médio (média) de dois valores, de modo que(a + b) >>> 1
a vontade retorne o valor médio mesmo para valores muito grandes dea
eb
Isso é necessário, por exemplo, para pesquisa binária se os índices da matriz puderem ser muito grandes. Houve um erro em muitas versões da pesquisa binária , porque eles costumavam(a + b) / 2
calcular a média. A solução correta é usar em seu(a + b) >>> 1
lugar.)fonte
x/2
parax>>1
nos casos em quex
pode ser negativo. Se o que se quer é o valor quex>>1
calcularia, isso quase certamente será mais rápido do que qualquer expressão envolvendox/2
que calcule o mesmo valor.x/2
parax>>1
se ele souber que o valor não é negativo. Vou tentar atualizar minha resposta.div
instrução, convertendox/2
para(x + (x<0?1:0)) >> 1
(onde >> é um deslocamento aritmético para a direita, que muda em bits de sinal). São necessárias 4 instruções: copie o valor, shr (para obter apenas o bit do sinal em um reg), adicione sar. goo.gl/4F8Ms4Knuth disse:
Então eu sugiro usar
x /= 2;
Dessa forma, o código é fácil de entender e também acho que a otimização dessa operação dessa forma, não significa uma grande diferença para o processador.
fonte
(n+8)>>4
funciona bem. Você pode oferecer uma abordagem tão clara ou eficiente sem usar o turno certo assinado?Dê uma olhada na saída do compilador para ajudá-lo a decidir. Eu executei este teste no x86-64 com o
gcc (GCC) 4.2.1 20070719 [FreeBSD]
Veja também as saídas do compilador online no godbolt .
O que você vê é que o compilador usa uma
sarl
instrução (desvio aritmético para a direita) em ambos os casos, portanto reconhece a semelhança entre as duas expressões. Se você usar a divisão, o compilador também precisará ajustar os números negativos. Para fazer isso, ele muda o bit de sinal para o bit de menor ordem e o adiciona ao resultado. Isso corrige a questão de um por um ao mudar números negativos, em comparação com o que uma divisão faria.Como o caso de divisão faz 2 turnos, enquanto o caso explícito de turno apenas um, podemos agora explicar algumas das diferenças de desempenho medidas por outras respostas aqui.
Código C com saída de montagem:
Para dividir, sua entrada seria
e isso compila para
Da mesma forma para o turno
com saída:
fonte
d
. Esse particionamento é útil para muitos propósitos. Mesmo se alguém preferir ter o ponto de interrupção em algum lugar que não seja entre 0 e -1, adicionar um deslocamento o moverá. Uma divisão inteira que satisfaça o segundo axioma dividirá a linha numérica em regiões que são principalmente de tamanhod
, mas uma delas é de tamanho2*d-1
. Não exatamente divisões "iguais". Você pode oferecer sugestões de quando a partição ímpar é realmente útil?sar
). goo.gl/KRgIkb . Esta publicação na lista de discussão ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) confirma que o gcc usa historicamente turnos aritméticos para entradas assinadas, portanto, é altamente improvável que o FreeBSD gcc 4.2.1 tenha usado turno não assinado. Atualizei sua postagem para corrigir isso e o parágrafo anterior dizendo que ambos usavam shr, quando na verdade é SAR que ambos usam. O SHR é como extrai o bit de sinal para o/
caso. Também incluiu um link godbolt.Apenas uma nota adicional -
x * = 0,5 geralmente será mais rápido em alguns idiomas baseados em VM - principalmente actionscript, pois a variável não precisará ser verificada quanto à divisão por 0.
fonte
eval
) faziam isso acontecer de novo a cada vez. Independentemente disso, sim, é um teste muito ruim, porque é uma otimização muito boba.Use
x = x / 2;
ORx /= 2;
Porque é possível que um novo programador trabalhe nele no futuro. Portanto, será mais fácil descobrir o que está acontecendo na linha de código. Todos podem não estar cientes dessas otimizações.fonte
Estou dizendo com o objetivo de programar competições. Geralmente eles têm entradas muito grandes, onde a divisão por 2 ocorre muitas vezes e sabe-se que a entrada é positiva ou negativa.
x >> 1 será melhor que x / 2. Eu verifiquei no ideone.com executando um programa em que ocorreram mais de 10 ^ 10 divisões por 2 operações. x / 2 levou quase 5,5s enquanto x >> 1 levou quase 2,6s para o mesmo programa.
fonte
x/2
parax>>1
. Para valores assinados, quase todas as implementações definemx>>1
ter um significado equivalente a,x/2
mas podem ser computadas mais rapidamente quandox
positivo, e é útil diferente dex/2
quandox
negativo.Eu diria que há várias coisas a considerar.
O deslocamento de bits deve ser mais rápido, pois não é realmente necessário um cálculo especial para mudar os bits; no entanto, como apontado, existem problemas em potencial com números negativos. Se você tiver números positivos e estiver procurando velocidade, eu recomendaria o deslocamento de bits.
O operador de divisão é muito fácil para os humanos lerem. Portanto, se você estiver procurando legibilidade de código, poderá usá-lo. Observe que o campo da otimização do compilador já percorreu um longo caminho, facilitando a leitura e compreensão do código, é uma boa prática.
Se você busca desempenho puro, eu recomendaria a criação de alguns testes que poderiam executar as operações milhões de vezes. Faça a amostra da execução várias vezes (o tamanho da amostra) para determinar qual é estatisticamente melhor com seu SO / Hardware / Compilador / Código.
fonte
>>
faz e não ao que/
faz.No que diz respeito à CPU, as operações de troca de bits são mais rápidas que as operações de divisão. No entanto, o compilador sabe disso e otimizará apropriadamente na medida do possível, para que você possa codificar da maneira que fizer mais sentido e ficar mais tranquilo sabendo que seu código está sendo executado com eficiência. Mas lembre-se de que um
unsigned int
(em alguns casos) pode ser otimizado melhor do que umint
por razões apontadas anteriormente. Se você não precisar de aritmética assinada, não inclua o bit de sinal.fonte
x = x / 2; é o código adequado para uso .. mas uma operação depende do seu próprio programa de como a saída que você deseja produzir.
fonte
Torne suas intenções mais claras ... por exemplo, se você deseja dividir, use x / 2 e deixe o compilador otimizá-lo para mudar de operador (ou qualquer outra coisa).
Os processadores de hoje não permitem que essas otimizações tenham impacto no desempenho de seus programas.
fonte
A resposta para isso dependerá do ambiente em que você estiver trabalhando.
x /= 2
emx >>= 1
, a presença de um símbolo de divisão vai aumentar mais as sobrancelhas naquele ambiente de usando uma mudança para efetuar uma divisão.x >>= 1
um comentário explicando seu raciocínio provavelmente será o melhor apenas para fins de clareza de propósito.x /= 2
. É melhor salvar o próximo programador que, por acaso, examinar seu código nos 10 segundos de operação dupla, do que provar desnecessariamente que sabia que o turno era mais eficiente na otimização do compilador.Todos estes assumem números inteiros não assinados. A mudança simples provavelmente não é o que você deseja assinar. Além disso, DanielH traz um bom argumento sobre o uso
x *= 0.5
de determinadas linguagens como o ActionScript.fonte
mod 2, teste para = 1. não sei a sintaxe em c. mas isso pode ser mais rápido.
fonte
geralmente o turno certo divide:
isso às vezes é usado para acelerar os programas à custa da clareza. Eu não acho que você deveria fazer isso. O compilador é inteligente o suficiente para executar a aceleração automaticamente. Isso significa que fazer uma mudança não ganha nada à custa da clareza .
Dê uma olhada nesta página da Practical C ++ Programming.
fonte
(x+128)>>8
, calcularia valoresx
não próximos do máximo, como poderia fazê-lo de forma concisa sem um turno? Observe que(x+128)/256
não vai funcionar. Você conhece alguma expressão legal que o faça?Obviamente, se você estiver escrevendo seu código para o próximo usuário que o lê, procure a clareza de "x / 2".
No entanto, se a velocidade é seu objetivo, tente de duas maneiras e cronometrar os resultados. Alguns meses atrás, trabalhei em uma rotina de convolução de bitmap que envolvia percorrer uma matriz de números inteiros e dividir cada elemento por 2. Fiz todo o tipo de coisas para otimizá-lo, incluindo o velho truque de substituir "x >> 1" por "x" / 2 ".
Quando cronometrei os dois sentidos, descobri, para minha surpresa, que x / 2 era mais rápido que x >> 1
Isso estava usando o Microsoft VS2008 C ++ com as otimizações padrão ativadas.
fonte
Em termos de desempenho. As operações de turno da CPU são significativamente mais rápidas que os códigos operacionais divididos. Assim, dividir por dois ou multiplicar por 2 etc, todos se beneficiam das operações de turno.
Quanto à aparência. Como engenheiros, quando nos tornamos tão apegados aos cosméticos que nem mesmo as mulheres bonitas usam! :)
fonte
X / Y é o correto ... e ">>" operador de deslocamento .. se queremos dois dividir um número inteiro, podemos usar (/) operador de dividendos. O operador shift é usado para mudar os bits.
x = x / 2; x / = 2; podemos usar assim ..
fonte
Enquanto x >> 1 é mais rápido que x / 2, o uso adequado de >> ao lidar com valores negativos é um pouco mais complicado. Requer algo semelhante ao seguinte:
fonte