곱셈이나 나눗셈을 사용해야합니까?


118

다음은 어리석은 재미있는 질문입니다.

변수 값의 절반이 필요한 간단한 작업을 수행해야한다고 가정 해 보겠습니다. 있다 일반적으로 이 일을 두 가지 방법 :

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

언어와 함께 제공되는 표준 연산자를 사용한다고 가정하면 어느 것이 더 나은 성능을 제공합니까?

나는 곱셈이 일반적으로 더 낫다고 생각하기 때문에 코딩 할 때 그것을 고수하려고 노력하지만 이것을 확인하고 싶습니다.

개인적으로 Python 2.4-2.5 에 대한 답변에 관심이 있지만 다른 언어에 대한 답변도 자유롭게 게시 할 수 있습니다! 그리고 원한다면 다른 멋진 방법 (비트 시프트 연산자 사용과 같은)도 자유롭게 게시 할 수 있습니다.


5
벤치 마크를 실행 했습니까? 약 12 줄의 코드입니다. 벤치 마크를 실행하면서 무엇을 배웠습니까? [힌트 : 여기에 질문을 게시하는 것보다 더 빠를 것입니다.]
— S.Lott

4
매우 흥미로운 답변 / 토론을 생성 한 훌륭한 질문입니다. 감사합니다 :)
— stealthcopter

22
벤치마킹을 통해 답을 배웠더라도 여전히 유용한 질문이며 흥미롭고 유용한 답변을 생성했습니다. 또한 사람들이 요점을 고수하고 문제의 최적화를 할 가치가 있는지 여부에 대한 관련없는 조언을 제공하는 답변에 대한 답변과 댓글을 작성하지 않기를 바랍니다. OP가 더 큰 규모의 재 작성에 대한 조언을 '정말로'원한다고 가정하는 대신 작성된대로 질문을한다고 가정하지 않겠습니까?
— Kevin Whitefoot 2013

1
나누기는 곱셈보다 훨씬 느립니다. 그러나 일부 스마트 컴파일러 / VM은 분할을 곱셈으로 변환하므로 테스트 결과가 동일합니다 (두 테스트 모두 곱셈 테스트).
— 이반 Kuckir

4
주제에서 약간 벗어 났지만 @KevinWhitefoot에 얼마나 동의하는지 말하고 싶습니다. 기술적 질문에 대한 기술적 답변보다 설교자들로부터 읽는 것만 큼 실망스러운 것은 없습니다. 의견을 보내 주신 Kevin에게 감사드립니다!
— Jean-François

답변:


78

파이썬 :

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

곱셈은 ​​33 % 더 빠릅니다

루아 :

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

=> 실제 차이 없음

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

=> 5 % 만 더 빠름

결론 : Python에서는 나누는 것보다 곱하는 것이 더 빠르지 만 고급 VM 또는 JIT를 사용하여 CPU에 가까워지면 이점이 사라집니다. 미래의 Python VM이이를 무의미하게 만들 가능성이 높습니다.


벤치마킹을 위해 time 명령을 사용하는 팁을 주셔서 감사합니다!
— Edmundito

2
당신의 결론이 잘못되었습니다. JIT / VM이 좋아질수록 관련성이 높아집니다. 분할은 VM의 낮은 오버 헤드에 비해 느려집니다. 일반적으로 컴파일러는 정밀도를 보장하기 위해 부동 소수점을 많이 최적화 할 수 없습니다.
— 라스무스

7
@rasmus : JIT가 좋아지면 나눗셈을 요청했지만 CPU 곱셈 명령을 사용할 가능성이 높아집니다.
— Ben Voigt 2013

68

항상 가장 명확한 것을 사용하십시오. 당신이하는 다른 일은 컴파일러를 능가하려는 것입니다. 컴파일러가 지능적이라면 결과를 최적화하기 위해 최선을 다할 것입니다.하지만 그 어떤 것도 당신의 엉뚱한 비트 시프 팅 솔루션에 대해 다음 사람이 당신을 싫어하지 않게 만들 수는 없습니다. )

조기 최적화는 모든 악의 근원입니다. 항상 최적화의 세 가지 규칙을 기억하십시오!

  1. 최적화하지 마십시오.
  2. 전문가 인 경우 규칙 # 1을 참조하십시오.
  3. 전문가이고 필요를 정당화 할 수있는 경우 다음 절차를 사용하십시오.

    • 최적화되지 않은 코딩
    • "충분히 빠른"속도 결정-어떤 사용자 요구 사항 / 스토리에 해당 메트릭이 필요한지 확인합니다.
    • 속도 테스트 작성
    • 기존 코드 테스트-충분히 빠르면 완료된 것입니다.
    • 최적화 된 레코딩
    • 최적화 된 코드를 테스트합니다. 측정 기준에 맞지 않으면 버리고 원본을 보관하십시오.
    • 테스트를 충족하면 원본 코드를 주석으로 유지하십시오.

또한 필요하지 않을 때 내부 루프를 제거하거나 삽입 정렬을 위해 배열에 연결된 목록을 선택하는 것과 같은 작업을 수행하는 것은 최적화가 아니라 프로그래밍입니다.


7
그것은 전체 Knuth 인용문이 아닙니다. en.wikipedia.org/wiki/…
— Jason S

아니요, 다양한 출처에서 나온 주제에 대한 약 40 개의 다른 인용문이 있습니다. 나는 몇 가지를 함께 모아 놓았습니다.
— Bill K

마지막 문장은 규칙 # 1과 # 2를 언제 적용해야하는지 명확하지 않게하여 시작했던 곳으로 돌아갑니다. 어떤 최적화가 가치 있고 어떤 최적화가 아닌지 결정해야합니다. 대답이 명백한 척하는 것은 대답이 아닙니다.
— Matt

2
정말 그렇게 헷갈 리나요? 실제로 클라이언트 사양을 충족하지 않고 CPU의 언어 및 캐싱 특성을 포함하여 전체 시스템에 매우 익숙하지 않는 한 항상 규칙 1과 2를 적용하십시오. 이 시점에서 3의 절차 만 따르십시오. "게터를 호출하는 대신이 변수를 로컬로 캐시하면 작업이 더 빠를 것입니다. 먼저 충분히 빠르지 않다는 것을 증명 한 다음 각 최적화를 개별적으로 테스트하고 도움이되지 않는 것은 버리십시오. 모든 과정에서 많은 양의 문서를 작성하십시오.
— Bill K

49

나는 이것이 너무 간결 해져서 코드를 더 읽기 쉽게 만드는 것이 더 나을 것이라고 생각합니다. 수백만 번은 아니더라도 수천 번의 작업을 수행하지 않는 한 누구도 그 차이를 눈치 채지 못할 것입니다.

정말로 선택을해야한다면 벤치마킹이 유일한 방법입니다. 어떤 기능이 문제를 일으키는 지 찾은 다음 기능에서 문제가 발생하는 위치를 찾아 해당 섹션을 수정하십시오. 그러나 나는 여전히 하나의 수학적 연산 (한 번이라도 여러 번 반복)이 병목 현상의 원인이 될 것이라고 의심합니다.


1
레이더 프로세서를 만들었을 때 한 번의 작업으로 차이가 생겼습니다. 그러나 우리는 실시간 성능을 얻기 위해 기계 코드를 수동으로 최적화했습니다. 다른 모든 것에 대해서는 간단하고 분명한 것에 투표합니다.
— S.Lott

몇 가지에 대해서는 단일 작업에 관심이있을 수 있습니다. 하지만 99 %의 애플리케이션에서는 중요하지 않을 것으로 예상합니다.
— Thomas Owens

27
특히 OP가 Python에서 답을 찾고 있었기 때문에. 그 정도의 효율성이 필요한 것이 파이썬으로 작성 될지 의심 스럽습니다.
— Ed S.

4
분할은 대부분의 레이트 레이서의 기초 인 삼각형 교차 루틴에서 가장 비용이 많이 드는 작업 일 것입니다. 나누는 대신 역수를 저장하고 곱하면 여러 번 속도 향상을 경험할 수 있습니다.
— solinent

@solinent-예, 속도가 빨라졌지만 "여러 번"의심 스럽습니다. 문제의 프로세서가 실제로 나누기가 아닌 곱셈에 최적화되어 있지 않는 한 부동 소수점 나누기와 곱셈은 약 4 : 1 이상 차이가 없어야합니다.
— Jason S

39

곱셈이 더 빠르고 나눗셈이 더 정확합니다. 숫자가 2의 거듭 제곱이 아니면 정밀도를 잃게됩니다.

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

컴파일러가 반전 된 상수를 완벽한 정밀도로 알아 내도록하더라도 대답은 여전히 ​​다를 수 있습니다.

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

속도 문제는 C / C ++ 또는 JIT 언어에서만 문제가 될 수 있으며, 작업이 병목 상태의 루프에있는 경우에만 중요합니다.


정수로 나누면 나누기가 정확합니다.
— plinth

7
분모> 분자로 부동 소수점 나누기는 하위 비트에 의미없는 값을 도입해야합니다. 분할은 일반적으로 정확도를 감소시킵니다.
— S.Lott

8
@ S.Lott : 아니요, 사실이 아닙니다. 모든 IEEE-754 호환 부동 소수점 구현은 현재 반올림 모드와 관련하여 모든 연산의 결과를 완벽하게 (즉, 가장 가까운 부동 소수점 수로) ​​반올림해야합니다. 역수를 곱하면 적어도 한 번 더 반올림해야하기 때문에 항상 더 많은 오류가 발생합니다.
— Electro

1
이 답변은 8 년이 넘었지만 오해의 소지가 있습니다. 정밀도 손실없이 분할을 수행 할 수 y = x * (1.0/3.0);있으며 컴파일러는 일반적으로 컴파일 타임에 1/3을 계산합니다. 예, 1/3은 IEEE-754에 완벽하게 표현할 수없는,하지만 당신은 부동 소수점 연산을 수행 할 때 당신은 정밀도를 잃고 어쨌든 당신은 곱셈이나 나눗셈을하고있는 것인지 하위 비트가 둥근 때문에,. 계산이 반올림 오류에 그렇게 민감하다는 것을 알고 있다면 문제를 가장 잘 처리하는 방법도 알아야합니다.
— Jason S

1
@JasonS 방금 1.0에서 시작하여 1 ULP까지 카운트 업하는 프로그램을 밤새 실행되도록 두었습니다. 곱한 결과를으로 나눈 결과를 비교 (1.0/3.0)했습니다 3.0. 나는 1.0000036666774155를 얻었고 그 공간에서 7.3 %의 결과가 달랐습니다. 나는 그것들이 1 비트 만 다를 뿐이라고 생각하지만 IEEE 산술은 가장 가까운 정확한 결과로 반올림되도록 보장되기 때문에 나눗셈이 더 정확하다는 내 진술을지지합니다. 차이가 중요한지 여부는 귀하에게 달려 있습니다.
— Mark Ransom 2017

25

코드를 최적화하고 싶지만 여전히 명확하다면 다음을 시도하십시오.

y = x * (1.0 / 2.0);

컴파일러는 컴파일 타임에 분할을 수행 할 수 있어야하므로 런타임에 곱셈을 얻을 수 있습니다. 정밀도는 y = x / 2.0케이스 와 동일 할 것으로 예상합니다 .

이것이 중요한 경우 부동 소수점 산술을 계산하기 위해 부동 소수점 에뮬레이션이 필요한 임베디드 프로세서에 LOT가 있습니다.


12
자신에게 적합합니다 (그리고 누구든지 -1)-임베디드 세계의 표준 관행이며 해당 분야의 소프트웨어 엔지니어가이를 분명히 알고 있습니다.
— Jason S

4
+1은 컴파일러가 원하는대로 부동 소수점 연산을 최적화 할 수 없다는 것을 깨닫는 유일한 사람입니다. 정밀도를 보장하기 위해 곱셈에서 피연산자의 순서를 변경할 수도 없습니다 (완화 모드를 사용하지 않는 한).
— rasmus

1
OMG, 최소 6 명의 프로그래머가 초등 수학이 불분명하다고 생각하고 있습니다. AFAIK, IEEE 754 곱셈은 교환 적 (비연 관적)입니다.
— maaartinus

13
아마도 당신은 요점을 놓치고있을 것입니다. 대수적 정확성과는 관련이 없습니다. 이상적인 세계에서는 다음 두 가지로 나눌 수 있어야합니다. y = x / 2.0;하지만 실제 세계에서는 컴파일러를 연결하여 저렴한 곱셈을 수행해야 할 수 있습니다. 왜 y = x * (1.0 / 2.0);더 나은지 명확하지 않을 수 있으며 y = x * 0.5;대신 진술 하는 것이 더 명확 할 것입니다. 그러나 변경 2.0A와 7.0나는 차라리 볼 것 y = x * (1.0 / 7.0);보다 y = x * 0.142857142857;.
— Jason S

3
이것은 당신의 방법을 사용하는 것이 더 읽기 쉽고 정확한 이유를 분명하게합니다.
— Juan Martinez

21

"다른 언어"옵션에 대한 내용을 추가합니다.
C : 이건 정말 큰 차이가없는 학문적 운동이기 때문에 뭔가 다른 것에 기여할 것이라고 생각했습니다.

최적화없이 어셈블리로 컴파일하고 결과를 확인했습니다.
코드:

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;

}

컴파일 gcc tdiv.c -O1 -o tdiv.s -S

2로 나누기 :

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

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)

그러나 그 ints를 doubles로 변경했을 때 (파이썬이 아마도 할 것입니다), 나는 이것을 얻었습니다.

분할:

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

곱셈:

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

이 코드를 벤치마킹하지는 않았지만 코드를 살펴보면 정수를 사용하는 것만으로도 2로 나누는 것이 2로 나누는 것보다 짧다는 것을 알 수 있습니다. double을 사용하면 컴파일러가 프로세서의 부동 소수점 연산 코드를 사용하기 때문에 곱셈이 더 짧습니다. 동일한 작업에 사용하지 않는 것보다 더 빨리 실행될 수 있습니다 (실제로는 모르겠습니다). 따라서 궁극적으로이 답변은 0.5 곱셈의 성능과 2로 나누기의 성능은 언어의 구현과 실행되는 플랫폼에 달려 있음을 보여줍니다. 궁극적으로 그 차이는 무시할 만하 며 가독성 측면을 제외하고는 거의 걱정할 필요가 없습니다.

참고로 내 프로그램 main()에서 a + b. volatile 키워드를 제거하면 어셈블리가 어떻게 생겼는지 추측하지 못할 것입니다 (프로그램 설정 제외).

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

하나의 명령어로 나누기, 곱하기, 더하기를 모두 수행했습니다! 옵티마이 저가 어떤 종류의 존경받을 만하다면 이것에 대해 걱정할 필요가 없습니다.

답변이 너무 길어서 죄송합니다.


1
"단일 명령"이 아닙니다. 계속해서 접혔습니다.
— kvanberendonck 2013

5
@kvanberendonck 물론 단일 명령입니다. 세어 movl $5, %eax 보세요 : 최적화의 이름은 중요하지 않거나 관련성이 없습니다. 당신은 단지 4 년 된 답변에 굴복하고 싶었습니다.
— Carson Myers

2
최적화의 특성은 상황에 따라 달라지기 때문에 이해하는 데 여전히 중요합니다. 추가 / 곱하기 / 나누기 등의 경우에만 적용됩니다. 컴파일러가 모든 수학을 미리 수행하고 런타임에 최종 답변을 레지스터로 옮길 수있는 컴파일 타임 상수. 나누기는 일반적인 경우 (런타임 제수)에서 곱하기보다 훨씬 느리지 만 역수로 곱하면 어쨌든 같은 분모로 두 번 이상 나누는 경우에만 도움이된다고 생각합니다. 당신은 아마 그 모든 것을 알고있을 것입니다. 그러나 초보 프로그래머들은 철자가 필요할 수도 있습니다.
— Mike S

10

첫째, C 또는 ASSEMBLY로 작업하지 않는 한, 아마도 메모리 지연과 일반적인 호출 오버 헤드가 곱셈과 나누기의 차이를 무관 한 지점까지 절대적으로 작게 만드는 더 높은 수준의 언어에있을 것입니다. 따라서이 경우 더 잘 읽는 것을 선택하십시오.

매우 높은 수준에서 이야기하는 경우 사용할 가능성이있는 모든 것에 대해 눈에 띄게 느려지지 않습니다. 다른 답변에서 볼 수 있듯이 사람들은 둘 사이의 밀리 초 미만의 차이를 측정하기 위해 백만 번의 곱셈 / 나눗셈을 수행해야합니다.

여전히 궁금하다면 낮은 수준의 최적화 관점에서 :

Divide는 곱하기보다 파이프 라인이 훨씬 더 긴 경향이 있습니다. 즉, 결과를 얻는 데 시간이 더 오래 걸리지 만 프로세서를 비 종속 작업으로 바쁘게 유지할 수 있다면 곱하기보다 더 많은 비용이 들지 않습니다.

파이프 라인 차이는 완전히 하드웨어에 따라 다릅니다. 마지막으로 사용한 하드웨어는 FPU 곱셈에 9 사이클, FPU 분할에 50 사이클 정도였습니다. 많이 들리지만 기억을 놓치면 1000 사이클을 잃게되므로 상황을 원근감있게 표현할 수 있습니다.

비유는 TV 쇼를 보는 동안 전자 레인지에 파이를 넣는 것입니다. TV 쇼를 보지 않는 데 걸린 총 시간은 전자 레인지에 넣고 전자 레인지에서 꺼내는 데 걸린 시간입니다. 나머지 시간은 여전히 ​​TV 쇼를 봤습니다. 따라서 파이가 요리하는 데 1 분이 아닌 10 분이 걸린다면 실제로 TV 시청 시간을 더 이상 사용하지 않은 것입니다.

실제로 Multiply와 Divide의 차이를 신경 쓰는 수준에 도달하려면 파이프 라인, 캐시, 분기 지연, 비 순차 예측 및 파이프 라인 종속성을 이해해야합니다. 이것이 당신 이이 질문으로 가고자했던 곳과 같지 않다면 정답은 둘의 차이를 무시하는 것입니다.

수년 전에는 분할을 피하고 항상 곱셈을 사용하는 것이 절대적으로 중요했지만 당시에는 메모리 히트가 덜 적절했고 분할이 훨씬 더 나빴습니다. 요즘 나는 가독성을 더 높게 평가하지만 가독성 차이가 없다면 곱셈을 선택하는 것이 좋은 습관이라고 생각합니다.


7

당신의 의도를 더 명확하게 나타내는 것을 작성하십시오.

프로그램이 작동 한 후 무엇이 느린 지 파악하고 더 빠르게 만드십시오.

다른 방식으로하지 마십시오.


6

필요한 것은 무엇이든하십시오. 먼저 독자를 생각하고 성능 문제가 있음을 확신 할 때까지 성능에 대해 걱정하지 마십시오.

컴파일러가 성능을 수행하도록하십시오.


5

정수 또는 비 부동 소수점 유형으로 작업하는 경우 비트 시프 팅 연산자를 잊지 마십시오. << >>

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

7
이 최적화는 모든 최신 컴파일러의 배후에서 자동으로 수행됩니다.
— Dustin Getz

피연산자 (?)에 대신 사용할 수있는 이동 가능한 버전이 있는지 확인 (비트 연산 사용)하는 사람이 있습니까? function mul (a, b) {if (b is 2) return a << 1; (b가 4)이면 a << 2를 반환합니다. // ... etc return a * b; } 제 생각에는 IF가 너무 비싸서 효율성이 떨어질 것입니다.
— Christopher Lightfoot

그것은 내가 상상했던 것과 가까운 곳에서는 인쇄되지 않았습니다. 신경 쓰지 마.
— Christopher Lightfoot

const 연산의 경우 일반 컴파일러가 작업을 수행해야합니다. 하지만 여기서 우리는 파이썬을 사용하고 있으므로 그것이 충분히 똑똑한 지 확실하지 않습니까? (그것은해야한다).
— Christopher Lightfoot

좋은 지름길이지만 실제로 일어나는 일이 즉시 명확하지 않다는 점을 제외하면. 대부분의 프로그래머는 비트 시프트 연산자조차 인식하지 못합니다.
— Blazemonger 2011

4

사실 일반적으로 곱셈이 나눗셈보다 빠르다는 좋은 이유가 있습니다. 하드웨어의 부동 소수점 나누기는 시프트 및 조건부 빼기 알고리즘 (이진수로 "긴 나누기") 또는-요즘에는 Goldschmidt의 알고리즘 과 같은 반복으로 수행 됩니다. 시프트 및 빼기는 정밀도 비트 당 적어도 하나의 사이클이 필요하며 (반복은 시프트 및 곱셈의 더하기와 같이 병렬화가 거의 불가능합니다) 반복 알고리즘은 반복 당 적어도 한 번의 곱셈을 수행합니다.. 두 경우 모두 부서에 더 많은주기가 걸릴 가능성이 높습니다. 물론 이것은 컴파일러, 데이터 이동 또는 정밀도의 단점을 설명하지 않습니다. 전반적으로, 그러나, 당신은 프로그램, 글을 쓰는 시간에 민감한 부분에 내부 루프를 코딩하는 경우 0.5 * x또는 1.0/2.0 * x이 아닌 것은 x / 2.0할 수있는 합리적인 일이다. "가장 명확한 코드를 작성하라"라는 pedantry는 절대적으로 사실이지만,이 세 가지 모두 가독성이 너무 가깝기 때문에이 경우 pedantry는 현학적 인 것입니다.


3

저는 곱셈이 더 효율적이라는 것을 항상 배웠습니다.


"효율적"은 잘못된 단어입니다. 대부분의 프로세서가 나누는 것보다 빠르게 번식하는 것은 사실입니다. 그러나 최신 파이프 라인 아키텍처를 사용하면 프로그램에 차이가 없을 수 있습니다. 많은 사람들이 말하는 것처럼, 당신은 정말 방법이 더 떨어져 단지 인간에 가장 읽고 일을.
— TED

3

곱셈은 ​​일반적으로 더 빠르며 결코 느리지 않습니다. 그러나 속도가 중요하지 않은 경우 가장 명확한 것을 작성하십시오.


2

부동 소수점 나누기는 (일반적으로) 특히 느리므로 부동 소수점 곱셈도 비교적 느리지 만 부동 소수점 나누기보다 빠를 것입니다.

그러나 프로파일 링에서 분할이 약간의 병목 현상과 곱셈이라는 것을 보여주지 않는 한, 저는 "그건 중요하지 않습니다"라고 대답하는 경향이 있습니다. 하지만 곱셈과 나눗셈의 선택이 응용 프로그램의 성능에 큰 영향을 미치지는 않을 것이라고 생각합니다.


2

이것은 어셈블리 나 아마도 C로 프로그래밍 할 때 더 많은 질문이됩니다. 저는 대부분의 현대 언어에서 이와 같은 최적화가 저를 위해 수행되고 있다고 생각합니다.


2

"곱하기가 일반적으로 더 낫다고 생각하기 때문에 코딩 할 때 그것을 고수하려고합니다."

이 특정 질문의 맥락에서 더 나은 것은 "더 빠름"을 의미합니다. 별로 유용하지 않습니다.

속도에 대해 생각하는 것은 심각한 실수가 될 수 있습니다. 계산의 특정 대수 형식에는 심각한 오류 의미가 있습니다.

오류 분석을 사용한 부동 소수점 산술을 참조하십시오 . 부동 소수점 산술 및 오류 분석의 기본 문제를 참조하십시오 .

일부 부동 소수점 값은 정확하지만 대부분의 부동 소수점 값은 근사치입니다. 그들은 이상적인 가치와 약간의 오류입니다. 모든 작업은 이상적인 값과 오류 값에 적용됩니다.

가장 큰 문제는 거의 동일한 두 개의 숫자를 조작하려고 할 때 발생합니다. 가장 오른쪽 비트 (오류 비트)가 결과를 지배하게됩니다.

>>> 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

이 예에서는 값이 작아 질수록 거의 같은 수의 차이가 정답이 0 인 0이 아닌 결과를 생성한다는 것을 알 수 있습니다.


1

나는 어딘가에서 C / C ++에서 곱셈이 더 효율적이라는 것을 읽었습니다. 통역 언어에 대한 아이디어가 없습니다. 다른 모든 오버 헤드로 인해 그 차이는 무시할 수 있습니다.

더 유지하기 쉽고 가독성이 좋은 것에 문제가되지 않는 한-사람들이 나에게 이것을 말하는 것이 싫지만 너무 사실입니다.


1

제수가 0이 아닌지 확인하기 위해주기를 소비 할 필요가 없기 때문에 일반적으로 곱셈을 제안합니다. 물론 제수가 상수 인 경우에는 적용되지 않습니다.


1

Samsung GT-S5830에 프로파일 링 된 Java Android

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;
    }
}

결과?

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

나누기는 곱셈 (!)보다 약 20 % 빠릅니다.


1
현실하려면 테스트해야 a = i*0.5하지 a *= 0.5. 이것이 대부분의 프로그래머가 작업을 사용하는 방법입니다.
— Blazemonger 2011

1

게시물 # 24 (곱하기가 더 빠름) 및 # 30과 마찬가지로-때로는 둘 다 이해하기 쉽습니다.

1*1e-6F;

1/1e6F;

~ 두 가지 모두 읽기 쉽고 수십억 번 반복해야합니다. 따라서 곱셈이 일반적으로 더 빠르다는 것을 아는 것이 유용합니다.


1

차이가 있지만 컴파일러에 따라 다릅니다. 처음에는 vs2003 (C ++)에서 이중 유형 (64 비트 부동 소수점)에 대해 큰 차이가 없었습니다. 그러나 vs2010에서 테스트를 다시 실행하면 곱셈에 대해 최대 4 배까지 빠른 차이를 발견했습니다. 이것을 추적하면 vs2003과 vs2010이 다른 fpu 코드를 생성하는 것으로 보입니다.

Pentium 4, 2.8GHz, vs2003 :

  • 곱셈 : 8.09
  • 나눗셈 : 7.97

Xeon W3530, vs2003 :

  • 곱셈 : 4.68
  • 나눗셈 : 4.64

Xeon W3530, vs2010 :

  • 곱셈 : 5.33
  • 나눗셈 : 21.05

vs2003에서 루프의 나눗셈이 (따라서 제수가 여러 번 사용되었으므로) 역으로 곱셈으로 변환 된 것 같습니다. vs2010에서는이 최적화가 더 이상 적용되지 않습니다 (두 방법간에 결과가 약간 다르기 때문에 가정합니다). 또한 cpu는 분자가 0.0이 되 자마자 나눗셈을 더 빠르게 수행합니다. 칩에 고정 된 정확한 알고리즘은 모르지만 숫자에 따라 다를 수 있습니다.

18-03-2013 편집 : vs2010에 대한 관찰


컴파일러가 예 n/10.0를 들어 형식의 표현으로 바꿀 수없는 이유가 있는지 궁금합니다 (n * c1 + n * c2). 나는 대부분의 프로세서에서 나눗셈이 두 번의 곱셈과 나눗셈보다 더 오래 걸릴 것으로 예상하고, 상수로 나누면 표시된 공식을 사용하는 모든 경우에 올바르게 반올림 된 결과를 얻을 수 있다고 생각합니다.
— supercat 2014-06-03

1

어리석은 재미있는 대답이 있습니다.

x / 2.0 은 x * 0.5 와 동일 하지 않습니다.

이 방법을 2008 년 10 월 22 일에 작성했다고 가정 해 보겠습니다.

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

이제 10 년 후이 코드를 최적화 할 수 있다는 것을 알게됩니다. 이 방법은 응용 프로그램 전체에서 수백 개의 공식에서 참조됩니다. 그래서 당신은 그것을 바꾸고 놀라운 5 % 성능 향상을 경험합니다.

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

코드를 변경 한 것이 올바른 결정 이었습니까? 수학에서 두 표현은 실제로 동일합니다. 컴퓨터 과학에서 항상 그런 것은 아닙니다. 자세한 내용 은 정확도 문제의 영향 최소화 를 참조하십시오. 계산 된 값이 다른 값과 비교되는 경우-엣지 케이스의 결과가 변경됩니다. 예 :

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

결론은 다음과 같습니다. 둘 중 하나에 만족하면 그것에 충실하십시오!


1
반대 투표? 당신의 생각을 설명하는 코멘트는 어떻습니까? 이 답변은 확실히 100 % 관련이 있습니다.
— l33t

컴퓨터 과학에서 부동 소수점 값을 2의 거듭 제곱으로 곱하거나 나누는 것은 값이 비정규 화되거나 오버플로되지 않는 한 무손실입니다.
— Soonts

부동 소수점은 나눗셈시 무손실이 아니므로 진술이 사실인지 여부는 실제로 중요하지 않습니다. 그랬다면 매우 놀랄 것입니다.
— l33t

1
더 이상 사용되지 않는 x87 코드를 내보내는 고대 컴파일러로 빌드 할 때만 "부동 소수점은 분할시 무손실이 아닙니다." 최신 하드웨어에서 float / double 변수 만있는 것은 손실이 없습니다. 32 비트 또는 64 비트 IEEE 754 : en.wikipedia.org/wiki/IEEE_754 IEEE 754가 작동하는 방식으로 인해 2로 나누거나 0.5를 곱하면 감소합니다. 1의 지수, 나머지 비트 (부호 + 가수)는 변경되지 않습니다. 그리고 모두 2와 0.5숫자 (예 : 달리 정밀도의 손실없이 정확하게 IEEE 754 표현 될 수 0.4또는 0.1그들은 수 없습니다).
— Soonts

0

추가 / 하위 트랙 작업 비용이 1이라고 가정하면 비용 5를 곱하고 비용을 약 20으로 나눕니다.


이 번호는 어디서 얻었습니까? 경험? 직감? 인터넷 기사? 다른 데이터 유형에 대해 어떻게 변경됩니까?
— kroiz 2014.02.12

0

이처럼 길고 흥미로운 논의 끝에 여기에 대한 나의 견해가 있습니다.이 질문에 대한 최종 답은 없습니다. 일부 사람들이 지적했듯이 하드웨어 ( piotrk 및 gast128 참조 )와 컴파일러 ( @Javier 의 테스트 참조)에 따라 다릅니다 . 속도가 중요하지 않은 경우 애플리케이션이 실시간으로 방대한 양의 데이터를 처리 할 필요가없는 경우 분할을 사용하여 명확성을 선택할 수 있지만 처리 속도 또는 프로세서로드가 문제가되는 경우 곱셈이 가장 안전 할 수 있습니다. 마지막으로 애플리케이션을 배포 할 플랫폼을 정확히 알지 못하는 한 벤치 마크는 의미가 없습니다. 그리고 코드의 명확성을 위해 하나의 주석으로 작업을 수행 할 수 있습니다!


-3

기술적으로 나눗셈과 같은 것은 없으며 역 요소에 의한 곱셈 만 있습니다. 예를 들어 2로 나누지 않고 실제로 0.5를 곱합니다.

'부'- 그것은 잠시 존재 함을하자 아이 자신은 - 때문에 '분열'에 더 열심히 항상 곱셈이다 x하여 y값을 계산하기 위해 하나 개의 제 요구 y^{-1}등 그 y*y^{-1} = 1다음 곱셈을 x*y^{-1}. 이미 알고 있다면 y^{-1}계산하지 않는 것이 y최적화 여야합니다.


3
실리콘에 존재하는 두 명령의 현실을 완전히 무시합니다.
— NPSF3000

@ NPSF3000-나는 따르지 않는다. 두 연산이 모두 존재한다는 가정 하에서 나누기 연산은 단순히 단일 곱셈을 수행하는 것보다 항상 더 어려운 곱셈 역과 곱셈의 계산을 암시 적으로 포함한다고 주장합니다. 실리콘은 구현 세부 사항입니다.
— satnhak

@ BTyler. 두 명령이 모두 실리콘에 존재하고 두 명령이 상대적으로 복잡한 명령보다 동일한 사이클 수를 사용하는 경우 성능 POV와 완전히 관련이 없습니다.
— NPSF3000

@ NPSF3000-그러나 곱셈이 더 빠르기 때문에 둘 다 동일한 수의 사이클을 사용하지 않습니다.
— satnhak
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.