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


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.0x * 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의 지수, 나머지 비트 (부호 + 가수)는 변경되지 않습니다. 그리고 모두 20.5숫자 (예 : 달리 정밀도의 손실없이 정확하게 IEEE 754 표현 될 수 0.4또는 0.1그들은 수 없습니다).
Soonts

0

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


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

0

이처럼 길고 흥미로운 논의 끝에 여기에 대한 나의 견해가 있습니다.이 질문에 대한 최종 답은 없습니다. 일부 사람들이 지적했듯이 하드웨어 ( piotrkgast128 참조 )와 컴파일러 ( @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.