Devo usar multiplicação ou divisão?


118

Aqui está uma pergunta boba e divertida:

Digamos que tenhamos que realizar uma operação simples em que precisamos da metade do valor de uma variável. Normalmente, existem duas maneiras de fazer isso:

y = x / 2.0;
// or...
y = x * 0.5;

Supondo que estejamos usando os operadores padrão fornecidos com a linguagem, qual deles tem melhor desempenho?

Suponho que a multiplicação normalmente é melhor, então tento mantê-la ao codificar, mas gostaria de confirmar isso.

Embora pessoalmente eu esteja interessado na resposta para Python 2.4-2.5, fique à vontade para postar uma resposta para outras linguagens! E se desejar, sinta-se à vontade para postar outras maneiras mais elaboradas (como usar operadores de mudança bit a bit) também.


5
Você fez um benchmark? São apenas cerca de uma dúzia de linhas de código. O que você aprendeu executando um benchmark? [Dica: fazer isso teria sido mais rápido do que postar a pergunta aqui.]
S.Lott,

4
Ótima pergunta, que gerou algumas respostas / discussões bastante interessantes. Obrigado :)
stealthcopter

22
Mesmo que ele tivesse aprendido a resposta por meio de benchmarking, ainda é uma pergunta útil e gerou algumas respostas interessantes e úteis. Também gostaria que as pessoas fossem direto ao ponto e evitassem escrever respostas e comentários sobre as respostas oferecendo conselhos irrelevantes sobre se vale ou não a pena fazer a otimização em questão. Por que não presumir que o OP está fazendo a pergunta da forma como está escrita, em vez de presumir que ele "realmente" deseja um conselho para uma reescrita em maior escala.
Kevin Whitefoot

1
A divisão é muito mais lenta do que a multiplicação. Mas alguns compiladores / VMs inteligentes transformam a divisão em multiplicação, portanto, seus testes terão os mesmos resultados (ambos os testes testam a multiplicação).
Ivan Kuckir

4
Um pouco fora do assunto, mas só quero dizer o quanto concordo com @KevinWhitefoot. Não há nada mais frustrante do que ler sermões em vez de respostas técnicas estranhas a perguntas técnicas. Obrigado Kevin pelo seu comentário!
Jean-François

Respostas:


78

Pitão:

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 / 2.0'
real    0m26.676s
user    0m25.154s
sys     0m0.076s

time python -c 'for i in xrange(int(1e8)): t=12341234234.234 * 0.5'
real    0m17.932s
user    0m16.481s
sys     0m0.048s

multiplicação é 33% mais rápida

Lua:

time lua -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m7.956s
user    0m7.332s
sys     0m0.032s

time lua -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m7.997s
user    0m7.516s
sys     0m0.036s

=> nenhuma diferença real

LuaJIT:

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 / 2.0 end'
real    0m1.921s
user    0m1.668s
sys     0m0.004s

time luajit -O -e 'for i=1,1e8 do t=12341234234.234 * 0.5 end'
real    0m1.843s
user    0m1.676s
sys     0m0.000s

=> é apenas 5% mais rápido

Conclusões: em Python é mais rápido multiplicar do que dividir, mas à medida que você se aproxima da CPU usando VMs ou JITs mais avançados, a vantagem desaparece. É bem possível que uma futura VM Python a torne irrelevante


Obrigado pela dica sobre como usar o comando time para benchmarking!
Edmundito,

2
Sua conclusão está errada. Ele se torna mais relevante conforme o JIT / VM fica melhor. A divisão fica mais lenta em comparação com a sobrecarga mais baixa da VM. Lembre-se de que os compiladores geralmente não podem otimizar muito o ponto flutuante para garantir a precisão.
rasmus

7
@rasmus: Conforme o JIT fica melhor, torna-se mais provável o uso de uma instrução de multiplicação da CPU, mesmo que você tenha solicitado a divisão.
Ben Voigt

68

Sempre use o que for mais claro. Qualquer outra coisa que você faça é tentar ser mais esperto que o compilador. Se o compilador for inteligente, fará o melhor para otimizar o resultado, mas nada pode fazer o próximo cara não odiar você por sua solução de deslocamento de bits de baixa qualidade (eu adoro manipulação de bits, a propósito, é divertido. Mas divertido! = Legível )

Otimização prematura é a raiz de todo o mal. Lembre-se sempre das três regras de otimização!

  1. Não otimize.
  2. Se você for um especialista, consulte a regra nº 1
  3. Se você for um especialista e puder justificar a necessidade, use o seguinte procedimento:

    • Codifique não otimizado
    • determinar quão rápido é "Rápido o suficiente" - Observe qual requisito / história do usuário requer essa métrica.
    • Escreva um teste de velocidade
    • Teste o código existente - se for rápido o suficiente, está feito.
    • Recodifique-o otimizado
    • Teste o código otimizado. SE não atender à métrica, jogue fora e guarde o original.
    • Se passar no teste, mantenha o código original como comentários

Além disso, fazer coisas como remover loops internos quando não são necessários ou escolher uma lista vinculada em um array para uma classificação por inserção não são otimizações, apenas programação.


7
essa não é a citação completa de Knuth; consulte en.wikipedia.org/wiki/…
Jason S

Não, existem cerca de 40 citações diferentes sobre o assunto de muitas fontes diferentes. Eu meio que juntei alguns.
Bill K

Sua última frase não deixa claro quando aplicar as regras nº 1 e nº 2, deixando-nos de volta onde começamos: Precisamos decidir quais otimizações valem a pena e quais não. Fingir que a resposta é óbvia não é uma resposta.
Matt

2
É realmente tão confuso para você? Sempre aplique as regras 1 e 2, a menos que você realmente não atenda às especificações do cliente e esteja muito familiarizado com todo o sistema, incluindo o idioma e as características de cache da CPU. Nesse ponto, SOMENTE siga o procedimento em 3, não pense apenas "Ei, se eu armazenar essa variável em cache localmente em vez de chamar um getter, as coisas provavelmente serão mais rápidas. Primeiro prove que não é rápido o suficiente, em seguida, teste cada otimização separadamente e jogue fora os que não ajudam. Documente pesadamente ao longo do caminho.
Bill K

49

Acho que isso está ficando tão minucioso que seria melhor você fazer o que quer que torne o código mais legível. A menos que você execute as operações milhares, senão milhões, de vezes, duvido que alguém notará a diferença.

Se você realmente precisa fazer uma escolha, o benchmarking é o único caminho a percorrer. Descubra quais funções estão causando problemas e, em seguida, descubra onde os problemas ocorrem na função e corrija essas seções. No entanto, ainda duvido que uma única operação matemática (mesmo uma repetida muitas e muitas vezes) seria a causa de qualquer gargalo.


1
Quando eu costumava fazer processadores de radar, uma única operação fazia a diferença. Mas estávamos otimizando manualmente o código de máquina para obter desempenho em tempo real. Para todo o resto, voto pelo simples e óbvio.
S.Lott,

Acho que, para algumas coisas, você pode se preocupar com uma única operação. Mas eu esperaria que em 99% dos aplicativos que existem, isso não importa.
Thomas Owens,

27
Especialmente porque o OP estava procurando uma resposta em Python. Duvido que qualquer coisa que precise dessa quantidade de eficiência seria escrita em Python.
Ed S.

4
Uma divisão é provavelmente a operação mais cara em uma rotina de intersecção de triângulo, que é a base para a maioria dos raytracers. Se você armazenar o recíproco e multiplicar em vez de dividir, terá uma aceleração muitas vezes.
solinente

@solinent - sim um aumento de velocidade, mas eu duvido "muitas vezes" - a divisão e multiplicação de ponto flutuante não deve ser diferente em mais do que cerca de 4: 1, a menos que o processador em questão seja realmente realmente otimizado para multiplicação e não divisão.
Jason S

39

A multiplicação é mais rápida, a divisão é mais precisa. Você perderá alguma precisão se seu número não for uma potência de 2:

y = x / 3.0;
y = x * 0.333333;  // how many 3's should there be, and how will the compiler round?

Mesmo se você deixar o compilador descobrir a constante invertida com precisão perfeita, a resposta ainda pode ser diferente.

x = 100.0;
x / 3.0 == x * (1.0/3.0)  // is false in the test I just performed

O problema de velocidade provavelmente só importa nas linguagens C / C ++ ou JIT e, mesmo assim, apenas se a operação estiver em um loop em um gargalo.


A divisão é precisa se você estiver dividindo por números inteiros.
plinto

7
A divisão de ponto flutuante com denominador> numerador deve introduzir valores sem sentido nos bits de ordem inferior; a divisão geralmente reduz a precisão.
S.Lott,

8
@ S.Lott: Não, isso não é verdade. Todas as implementações de ponto flutuante em conformidade com IEEE-754 devem arredondar os resultados de cada operação perfeitamente (ou seja, para o número de ponto flutuante mais próximo) em relação ao modo de arredondamento atual. Multiplicar pelo recíproco sempre vai introduzir mais erro, pelo menos porque mais um arredondamento deve acontecer.
Electro de

1
Eu sei que esta resposta tem mais de 8 anos, mas é enganosa; você pode realizar a divisão sem perda significativa de precisão: y = x * (1.0/3.0);e o compilador geralmente calculará 1/3 em tempo de compilação. Sim, 1/3 não é perfeitamente representável no IEEE-754, mas quando você está executando aritmética de ponto flutuante, você perde a precisão de qualquer maneira , quer esteja fazendo multiplicação ou divisão, porque os bits de ordem inferior são arredondados. Se você sabe que seu cálculo é tão sensível a erros de arredondamento, também deve saber como lidar da melhor forma com o problema.
Jason S

1
@JasonS Acabei de deixar um programa em execução durante a noite, começando em 1.0 e contando até 1 ULP; Eu comparei o resultado da multiplicação por (1.0/3.0)com a divisão por 3.0. Cheguei a 1.0000036666774155, e nesse espaço 7,3% dos resultados foram diferentes. Presumo que eles foram diferentes apenas em 1 bit, mas como a aritmética IEEE é garantida para arredondar para o resultado correto mais próximo, mantenho minha afirmação de que a divisão é mais precisa. Se a diferença é significativa depende de você.
Mark Ransom

25

Se você deseja otimizar seu código, mas ainda é claro, tente isto:

y = x * (1.0 / 2.0);

O compilador deve ser capaz de fazer a divisão em tempo de compilação, para que você obtenha uma multiplicação em tempo de execução. Eu esperaria que a precisão fosse a mesma doy = x / 2.0 case.

Onde isso pode importar, MUITO é em processadores embutidos onde a emulação de ponto flutuante é necessária para calcular a aritmética de ponto flutuante.


12
Faça como quiser (e quem quer que seja) - é uma prática padrão no mundo embarcado e os engenheiros de software nesse campo acham isso claro.
Jason S

4
+1 por ser o único aqui percebendo que os compiladores não podem otimizar as operações de ponto flutuante como eles querem. Eles não podem nem mesmo alterar a ordem dos operandos em uma multiplicação para garantir a precisão (a menos que use um modo relaxado).
rasmus

1
OMG, há pelo menos 6 programadores pensando que a matemática elementar não é clara. AFAIK, IEEE 754 multiplicação é comutativa (mas não associativa).
maaartinus

13
Talvez você esteja perdendo o ponto. Não tem nada a ver com correção algébrica. Em um mundo ideal, você deve apenas ser capaz de dividir por dois:, y = x / 2.0;mas no mundo real, pode ser necessário persuadir o compilador a realizar uma multiplicação menos dispendiosa. Talvez seja menos claro porque y = x * (1.0 / 2.0);é melhor, e seria mais claro afirmar em y = x * 0.5;vez disso. Mas mude o 2.0para um 7.0e prefiro ver do y = x * (1.0 / 7.0);que y = x * 0.142857142857;.
Jason S

3
Isso realmente deixa claro porque é mais legível (e preciso) usar seu método.
Juan Martinez

21

Só vou adicionar algo para a opção "outros idiomas".
C: Uma vez que este é apenas um exercício acadêmico que realmente não faz diferença, pensei em contribuir com algo diferente.

Compilei para a montagem sem otimizações e olhei o resultado.
O código:

int main() {

    volatile int a;
    volatile int b;

    asm("## 5/2\n");
    a = 5;
    a = a / 2;

    asm("## 5*0.5");
    b = 5;
    b = b * 0.5;

    asm("## done");

    return a + b;

}

compilado com gcc tdiv.c -O1 -o tdiv.s -S

a divisão por 2:

movl    $5, -4(%ebp)
movl    -4(%ebp), %eax
movl    %eax, %edx
shrl    $31, %edx
addl    %edx, %eax
sarl    %eax
movl    %eax, -4(%ebp)

e a multiplicação por 0,5:

movl    $5, -8(%ebp)
movl    -8(%ebp), %eax
pushl   %eax
fildl   (%esp)
leal    4(%esp), %esp
fmuls   LC0
fnstcw  -10(%ebp)
movzwl  -10(%ebp), %eax
orw $3072, %ax
movw    %ax, -12(%ebp)
fldcw   -12(%ebp)
fistpl  -16(%ebp)
fldcw   -10(%ebp)
movl    -16(%ebp), %eax
movl    %eax, -8(%ebp)

No entanto, quando eu mudei aqueles int s paradouble s (que é o que provavelmente o python faria), obtive o seguinte:

divisão:

flds    LC0
fstl    -8(%ebp)
fldl    -8(%ebp)
flds    LC1
fmul    %st, %st(1)
fxch    %st(1)
fstpl   -8(%ebp)
fxch    %st(1)

multiplicação:

fstpl   -16(%ebp)
fldl    -16(%ebp)
fmulp   %st, %st(1)
fstpl   -16(%ebp)

Não fiz benchmark de nenhum deste código, mas apenas examinando o código, você pode ver que usando inteiros, a divisão por 2 é mais curta do que a multiplicação por 2. Usando duplas, a multiplicação é mais curta porque o compilador usa os opcodes de ponto flutuante do processador, que provavelmente correr mais rápido (mas na verdade eu não sei) do que não usá-los para a mesma operação. Portanto, em última análise, essa resposta mostrou que o desempenho da multiplicação por 0,5 vs. divisão por 2 depende da implementação da linguagem e da plataforma em que ela é executada. Em última análise, a diferença é insignificante e é algo com que você nunca deve se preocupar, exceto em termos de legibilidade.

Como observação lateral, você pode ver que no meu programa main()retorna a + b. Quando eu remover a palavra-chave volatile, você nunca adivinhará a aparência do assembly (excluindo a configuração do programa):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

ele fez a divisão, multiplicação e adição em uma única instrução! Obviamente, você não precisa se preocupar com isso se o otimizador for respeitável.

Desculpe pela resposta excessivamente longa.


1
Não é uma "única instrução". Ele simplesmente foi dobrado constantemente.
kvanberendonck

5
@kvanberendonck Claro que é uma única instrução. Conte-os: movl $5, %eax o nome da otimização não é importante ou mesmo relevante. Você só queria ser condescendente com a resposta de uma criança de quatro anos.
Carson Myers

2
A natureza da otimização ainda é importante entender, porque é sensível ao contexto: só se aplica se você estiver adicionando / multiplicando / dividindo / etc. constantes de tempo de compilação, onde o compilador pode simplesmente fazer toda a matemática com antecedência e mover a resposta final para um registrador em tempo de execução. A divisão é muito mais lenta do que a multiplicação no caso geral (divisores em tempo de execução), mas suponho que multiplicar por recíprocos só ajuda se você dividir pelo mesmo denominador mais de uma vez. Você provavelmente sabe de tudo isso, mas os programadores mais novos podem precisar explicá-lo bem, então ... apenas no caso.
Mike S

10

Em primeiro lugar, a menos que você esteja trabalhando em C ou ASSEMBLY, você provavelmente está em uma linguagem de nível superior, onde a memória travada e sobrecargas de chamadas gerais irão absolutamente diminuir a diferença entre multiplicar e dividir ao ponto da irrelevância. Portanto, basta escolher o que é melhor nesse caso.

Se você estiver falando de um nível muito alto, não será mensuravelmente mais lento para qualquer coisa para a qual você possa usá-lo. Você verá em outras respostas, as pessoas precisam multiplicar / dividir um milhão apenas para medir alguma diferença de submilissegundo entre os dois.

Se você ainda estiver curioso, do ponto de vista de otimização de baixo nível:

O Divide tende a ter um pipeline significativamente mais longo do que o Multiply. Isso significa que leva mais tempo para obter o resultado, mas se você puder manter o processador ocupado com tarefas não dependentes, isso não custará mais do que uma multiplicação.

O tamanho da diferença de pipeline depende totalmente do hardware. O último hardware que usei foi algo como 9 ciclos para uma multiplicação de FPU e 50 ciclos para uma divisão de FPU. Parece muito, mas você perderia 1000 ciclos por uma perda de memória, então isso pode colocar as coisas em perspectiva.

Uma analogia é colocar uma torta no microondas enquanto você assiste a um programa de TV. O tempo total que você levou para longe do programa de TV é quanto tempo foi para colocá-lo no micro-ondas e tirá-lo do micro-ondas. O resto do tempo você ainda assistia ao programa de TV. Portanto, se a torta levasse 10 minutos para cozinhar em vez de 1 minuto, ela não consumia mais o tempo de exibição da TV.

Na prática, se você chegar ao nível de se preocupar com a diferença entre Multiply e Divide, precisará entender pipelines, cache, paralisações de ramificação, predição fora de ordem e dependências de pipeline. Se isso não soa como você pretendia chegar com essa pergunta, a resposta correta é ignorar a diferença entre as duas.

Muitos (muitos) anos atrás, era absolutamente crítico evitar divisões e sempre usar multiplicações, mas naquela época os acertos de memória eram menos relevantes e as divisões eram muito piores. Hoje em dia eu dou um valor mais alto à legibilidade, mas se não houver diferença na legibilidade, acho um bom hábito optar por multiplicar.


7

Escreva o que declarar mais claramente sua intenção.

Depois que seu programa funcionar, descubra o que é lento e torne-o mais rápido.

Não faça o contrário.


6

Faça o que você precisar. Pense no seu leitor primeiro, não se preocupe com o desempenho até ter certeza de que há um problema de desempenho.

Deixe o compilador fazer o desempenho para você.


5

Se você estiver trabalhando com inteiros ou tipos de ponto não flutuante, não se esqueça dos operadores de bitshifting: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);

7
essa otimização é executada automaticamente nos bastidores em qualquer compilador moderno.
Dustin Getz,

Alguém testou se está verificando (usando operações de bit) se um operando (?) Tem uma versão deslocável para usar isso? função mul (a, b) {if (b é 2) retorna a << 1; if (b é 4) retorna a << 2; // ... etc return a * b; } Meu palpite é que o IF é tão caro que seria menos eficiente.
Christopher Lightfoot,

Isso não foi impresso nem perto do que eu imaginei; Deixa pra lá.
Christopher Lightfoot,

Para operações const, um compilador normal deve fazer o trabalho; mas aqui estamos usando python, então não tenho certeza se é inteligente o suficiente para saber? (Deveria ser).
Christopher Lightfoot,

Bom atalho, exceto que não está imediatamente claro o que realmente está acontecendo. A maioria dos programadores nem mesmo reconhece os operadores de bitshift.
Blazemonger

4

Na verdade, há uma boa razão para que, como regra geral, a multiplicação seja mais rápida do que a divisão. A divisão de ponto flutuante no hardware é feita com algoritmos de mudança e subtração condicional ("divisão longa" com números binários) ou - mais provavelmente hoje em dia - com iterações como a de Goldschmidt algoritmo . Shift e subtract precisam de pelo menos um ciclo por bit de precisão (as iterações são quase impossíveis de paralelizar, assim como o shift-and-add da multiplicação), e algoritmos iterativos fazem pelo menos uma multiplicação por iteração. Em ambos os casos, é altamente provável que a divisão leve mais ciclos. É claro que isso não leva em consideração peculiaridades em compiladores, movimentação de dados ou precisão. De modo geral, entretanto, se você estiver codificando um loop interno em uma parte sensível ao tempo de um programa, escrever 0.5 * xou, 1.0/2.0 * xao invés disso, x / 2.0é uma coisa razoável a se fazer. O pedantismo de "codificar o que é mais claro" é absolutamente verdadeiro, mas todos os três são tão próximos em legibilidade que o pedantismo, neste caso, é apenas pedante.


3

Sempre aprendi que a multiplicação é mais eficiente.


"eficiente" é a palavra errada. É verdade que a maioria dos processadores se multiplica mais rápido do que se divide. No entanto, com arquiteturas modernas em pipeline, seu programa pode não ver nenhuma diferença. Como muitos outros estão dizendo, você está realmente muito melhor apenas fazendo o que parece melhor para um humano.
TED de

3

A multiplicação geralmente é mais rápida - certamente nunca mais lenta. No entanto, se não for crítico para a velocidade, escreva o que estiver mais claro.


2

A divisão de ponto flutuante é (geralmente) especialmente lenta, portanto, embora a multiplicação de ponto flutuante também seja relativamente lenta, é provavelmente mais rápida do que a divisão de ponto flutuante.

Mas estou mais inclinado a responder "isso realmente não importa", a menos que a criação de perfil tenha mostrado que a divisão é um pequeno gargalo em relação à multiplicação. Estou supondo, porém, que a escolha de multiplicação versus divisão não terá um grande impacto no desempenho de seu aplicativo.


2

Isso se torna mais problemático quando você está programando em assembly ou talvez em C. Acho que, com a maioria das linguagens modernas, essa otimização está sendo feita para mim.


2

Desconfie de "adivinhar que a multiplicação é normalmente melhor, então tento segui-la quando codifico",

No contexto desta questão específica, melhor aqui significa "mais rápido". O que não é muito útil.

Pensar em velocidade pode ser um erro sério. Existem profundas implicações de erro na forma algébrica específica do cálculo.

Consulte aritmética de ponto flutuante com análise de erro . Consulte Problemas básicos em aritmética de ponto flutuante e análise de erros .

Embora alguns valores de ponto flutuante sejam exatos, a maioria dos valores de ponto flutuante é uma aproximação; eles são algum valor ideal mais algum erro. Cada operação se aplica ao valor ideal e ao valor de erro.

Os maiores problemas vêm de tentar manipular dois números quase iguais. Os bits mais à direita (os bits de erro) passam a dominar os resultados.

>>> for i in range(7):
...     a=1/(10.0**i)
...     b=(1/10.0)**i
...     print i, a, b, a-b
... 
0 1.0 1.0 0.0
1 0.1 0.1 0.0
2 0.01 0.01 -1.73472347598e-18
3 0.001 0.001 -2.16840434497e-19
4 0.0001 0.0001 -1.35525271561e-20
5 1e-05 1e-05 -1.69406589451e-21
6 1e-06 1e-06 -4.23516473627e-22

Neste exemplo, você pode ver que, à medida que os valores ficam menores, a diferença entre números quase iguais cria resultados diferentes de zero em que a resposta correta é zero.


1

Eu li em algum lugar que a multiplicação é mais eficiente em C / C ++; Nenhuma ideia sobre idiomas interpretados - a diferença é provavelmente insignificante devido a todas as outras despesas gerais.

A menos que se torne um problema, fique com o que é mais fácil de manter / legível - eu odeio quando as pessoas me dizem isso, mas é tão verdade.


1

Eu sugeriria a multiplicação em geral, porque você não precisa gastar os ciclos garantindo que seu divisor não seja 0. Isso não se aplica, é claro, se seu divisor for uma constante.


1

Android Java, perfilado em Samsung GT-S5830

public void Mutiplication()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a *= 0.5f;
    }
}
public void Division()
{
    float a = 1.0f;

    for(int i=0; i<1000000; i++)
    {
        a /= 2.0f;
    }
}

Resultados?

Multiplications():   time/call: 1524.375 ms
Division():          time/call: 1220.003 ms

A divisão é cerca de 20% mais rápida do que a multiplicação (!)


1
Para ser realista, você deve testar a = i*0.5, não a *= 0.5. É assim que a maioria dos programadores usará as operações.
Blazemonger

1

Tal como acontece com os posts # 24 (a multiplicação é mais rápida) e # 30 - mas às vezes ambos são igualmente fáceis de entender:

1*1e-6F;

1/1e6F;

Acho que ambos são fáceis de ler e tenho que repeti-los bilhões de vezes. Portanto, é útil saber que a multiplicação geralmente é mais rápida.


1

Há uma diferença, mas depende do compilador. No início, no vs2003 (c ++), não obtive diferença significativa para os tipos duplos (ponto flutuante de 64 bits). No entanto, executando os testes novamente no vs2010, detectei uma grande diferença, até fator 4 mais rápido para multiplicações. Rastreando isso, parece que o vs2003 e o vs2010 geram um código fpu diferente.

Em um Pentium 4, 2,8 GHz, vs2003:

  • Multiplicação: 8,09
  • Divisão: 7,97

Em um Xeon W3530, vs2003:

  • Multiplicação: 4,68
  • Divisão: 4,64

Em um Xeon W3530, vs2010:

  • Multiplicação: 5,33
  • Divisão: 21,05

Parece que no vs2003 uma divisão em um loop (então o divisor foi usado várias vezes) foi convertida em uma multiplicação com o inverso. No vs2010, essa otimização não é mais aplicada (suponho que seja porque há um resultado ligeiramente diferente entre os dois métodos). Observe também que a CPU realiza divisões mais rápido assim que o numerador for 0,0. Não sei o algoritmo preciso conectado ao chip, mas talvez dependa do número.

Editar 18-03-2013: a observação para vs2010


Eu me pergunto se há algum motivo pelo qual um compilador não pode substituir, por exemplo, n/10.0por uma expressão do formulário (n * c1 + n * c2)? Eu esperaria que na maioria dos processadores uma divisão levasse mais do que duas multiplicações e uma divisão, e acredito que a divisão por qualquer constante pode produzir um resultado arredondado corretamente em todos os casos usando a formulação indicada.
supercat

1

Aqui está uma resposta boba e divertida:

x / 2.0 não é equivalente a x * 0,5

Digamos que você escreveu esse método em 22 de outubro de 2008.

double half(double x) => x / 2.0;

Agora, 10 anos depois, você aprende que pode otimizar esse trecho de código. O método é referenciado em centenas de fórmulas em todo o seu aplicativo. Então, você o altera e experimenta uma notável melhoria de desempenho de 5%.

double half(double x) => x * 0.5;

Foi a decisão certa alterar o código? Em matemática, as duas expressões são realmente equivalentes. Na ciência da computação, isso nem sempre é verdade. Leia Minimizando o efeito dos problemas de precisão para obter mais detalhes. Se seus valores calculados forem - em algum ponto - comparados com outros valores, você mudará o resultado dos casos extremos. Por exemplo:

double quantize(double x)
{
    if (half(x) > threshold))
        return 1;
    else
        return -1;
}

Resumindo; uma vez que você se contentar com qualquer um dos dois, mantenha-o!


1
Votar negativamente? Que tal um comentário explicando seus pensamentos? Essa resposta é definitivamente 100% relevante.
l33t

Na ciência da computação, multiplicar / dividir os valores de ponto flutuante por potências de 2 é sem perdas, a menos que o valor se torne desnormalizado ou transborde.
Soonts

Uma vez que o ponto flutuante não está isento de perdas no momento da divisão, realmente não importa se sua afirmação é verdadeira. Embora eu ficaria muito surpreso se fosse.
l33t

1
“O ponto flutuante não é sem perdas no momento da divisão” somente quando você está construindo com um compilador antigo que emite código x87 obsoleto. Em hardware moderno, apenas ter uma variável float / double não tem perdas, seja IEEE 754 de 32 ou 64 bits: en.wikipedia.org/wiki/IEEE_754 Devido à maneira como o IEEE 754 funciona, quando você divide por 2 ou multiplica por 0,5, você diminui o expoente por 1, o resto dos bits (sinal + mantissa) não mudam. E os números 2e 0.5podem ser representados exatamente no IEEE 754, sem nenhuma perda de precisão (ao contrário de, por exemplo , 0.4ou 0.1, eles não podem).
Soonts

0

Bem, se assumirmos que uma operação adicionar / subtrair custa 1, então multiplique os custos 5 e divida os custos por cerca de 20.


De onde você tirou esses números? experiência? intuição? artigo na internet? como eles mudariam para diferentes tipos de dados?
kroiz de

0

Depois de uma discussão tão longa e interessante, aqui está minha opinião sobre isso: Não há uma resposta final para essa pergunta. Como algumas pessoas apontaram, isso depende de ambos, do hardware (cf piotrk e gast128 ) e do compilador (cf @Javier 's tests). Se a velocidade não for crítica, se seu aplicativo não precisar processar em tempo real uma grande quantidade de dados, você pode optar pela clareza usando uma divisão, enquanto se a velocidade de processamento ou a carga do processador forem um problema, a multiplicação pode ser a mais segura. Finalmente, a menos que você saiba exatamente em qual plataforma seu aplicativo será implementado, o benchmark não tem sentido. E para maior clareza do código, um único comentário faria o trabalho!


-3

Tecnicamente, não existe divisão, existe apenas multiplicação por elementos inversos. Por exemplo, você nunca divide por 2, na verdade você multiplica por 0,5.

'Divisão' - vamos nos enganar que existe por um segundo - é sempre mais difícil que a multiplicação porque para 'dividir' xpor yum primeiro é necessário calcular o valor y^{-1}tal que y*y^{-1} = 1e depois fazer a multiplicação x*y^{-1}. Se você já sabe y^{-1}, não calculá-lo ydeve ser uma otimização.


3
O que ignora completamente a realidade de ambos os comandos existentes no silício.
NPSF3000 de

@ NPSF3000 - Não entendo. Partindo do pressuposto de que ambas as operações existem, ele simplesmente afirma que a operação de divisão envolve implicitamente o cálculo de um inverso multiplicativo e uma multiplicação, o que sempre será mais difícil do que apenas fazer uma única multiplicação. O silício é um detalhe de implementação.
satnhak de

@ BTyler. Se ambos os comandos existem no silício e ambos os comandos levam o mesmo número de ciclos [como seria de se esperar], então o quão relativamente complexas as instruções são é completamente irrelevante de um POV de desempenho.
NPSF3000

@ NPSF3000 - mas eles não fazem o mesmo número de ciclos porque a multiplicação é mais rápida.
satnhak de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.