Çarpma mı bölme mi kullanmalıyım?


118

İşte aptalca eğlenceli bir soru:

Diyelim ki bir değişkenin değerinin yarısına ihtiyacımız olan basit bir işlem yapmalıyız. Bunu yapmanın tipik olarak iki yolu vardır :

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

Dil ile sağlanan standart operatörleri kullandığımızı varsayarsak, hangisinin performansı daha iyidir?

Çarpmanın genellikle daha iyi olduğunu tahmin ediyorum, bu yüzden kodlarken buna bağlı kalmaya çalışıyorum, ancak bunu onaylamak istiyorum.

Kişisel olarak Python 2.4-2.5'in cevabıyla ilgileniyor olsam da, diğer diller için de bir cevap göndermekten çekinmeyin! Ve isterseniz, diğer daha güzel yolları da (bitsel kaydırma operatörlerini kullanmak gibi) yayınlamaktan çekinmeyin.


5
Bir kıyaslama yaptınız mı? Sadece bir düzine satır kod. Bir kıyaslama yapmaktan ne öğrendiniz? [İpucu: Bunu yapmak, soruyu buraya göndermekten daha hızlı olurdu.]
S.Lott

4
Oldukça ilginç cevaplar / tartışmalar yaratan harika bir soru. Teşekkürler :)
stealthcopter

22
Yanıtı kıyaslayarak öğrenmiş olsa bile, bu yine de yararlı bir sorudur ve bazı ilginç ve faydalı yanıtlar üretmiştir. Ayrıca insanların konuya sadık kalmalarını ve söz konusu optimizasyonu yapmaya değip değmeyeceğine dair alakasız tavsiyeler sunan cevaplar ve yorumlardan kaçınmalarını diliyorum. Neden OP'nin soruyu daha geniş ölçekte yeniden yazma konusunda “gerçekten” tavsiye istediğini varsaymak yerine, soruyu yazılı olarak sorduğunu varsaymayalım.
Kevin Whitefoot

1
Bölünme, çarpmadan çok daha yavaştır. Ancak bazı akıllı derleyiciler / sanal makineler bölmeyi çarpmaya dönüştürür, bu nedenle testleriniz aynı sonuçları alır (her iki test de çarpmayı test eder).
Ivan Kuckir

4
Biraz konu dışı ama @ KevinWhitefoot'a ne kadar katılıyorum demek istiyorum. Teknik sorulara verilen teknik cevaplardan ziyade vaaz verenlerden okumak kadar sinir bozucu hiçbir şey yoktur. Yorumunuz için teşekkürler Kevin!
Jean-François

Yanıtlar:


78

Python:

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

çarpma% 33 daha hızlı

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

=> gerçek bir fark yok

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

=> sadece% 5 daha hızlı

sonuçlar: Python'da çarpmak bölmekten daha hızlıdır, ancak daha gelişmiş VM'ler veya JIT'ler kullanarak CPU'ya yaklaştıkça avantaj ortadan kalkar. Gelecekteki bir Python sanal makinesinin onu alakasız hale getirmesi oldukça olasıdır


Kıyaslama için zaman komutunun kullanımına ilişkin ipucu için teşekkürler!
Edmundito

2
Kararınız yanlış. JIT / VM daha iyi hale geldikçe daha alakalı hale gelir. Bölme, sanal makinenin daha düşük ek yüküne kıyasla daha yavaş hale gelir. Kesinliği garanti etmek için derleyicilerin kayan noktayı pek optimize edemediklerini unutmayın.
rasmus

7
@rasmus: JIT daha iyi hale geldikçe, bölme talebinde bulunmanıza rağmen bir CPU çarpma talimatı kullanma olasılığı artar.
Ben Voigt

68

Daima en net olanı kullanın. Yaptığınız başka herhangi bir şey, derleyiciyi alt etmeye çalışmaktır. Derleyici tamamen zekiyse, sonucu optimize etmek için elinden gelenin en iyisini yapar, ancak hiçbir şey bir sonraki kişinin berbat bit değiştirme çözümünüz için sizden nefret etmemesini sağlayamaz (Bu arada biraz manipülasyonu seviyorum, eğlenceli. Ama eğlenceli! = Okunabilir )

Erken optimizasyon, tüm kötülüklerin köküdür. Optimizasyonun üç kuralını her zaman hatırlayın!

  1. Optimize etmeyin.
  2. Eğer bir uzmansanız, 1. kurala bakın
  3. Bir uzmansanız ve ihtiyacı haklı çıkarabiliyorsanız, aşağıdaki prosedürü kullanın:

    • Optimize edilmemiş kodlayın
    • "Yeterince hızlı" ne kadar hızlı olduğunu belirleyin - Hangi kullanıcı gereksiniminin / hikayesinin bu ölçüyü gerektirdiğini not edin.
    • Bir hız testi yazın
    • Mevcut kodu test edin - Yeterince hızlıysa, bitirdiniz.
    • Optimize edilmiş olarak yeniden kodlayın
    • Optimize edilmiş kodu test edin. Metriği karşılamıyorsa, onu atın ve orijinali saklayın.
    • Testi karşılıyorsa, orijinal kodu yorum olarak saklayın

Ayrıca, gerekli olmadıklarında iç döngüleri kaldırmak veya bir ekleme sıralaması için bir dizi üzerinden bağlantılı bir liste seçmek gibi şeyler yapmak optimizasyon değil, sadece programlama.


7
bu tam Knuth alıntısı değil; bkz. en.wikipedia.org/wiki/…
Jason S

Hayır, konu hakkında pek çok farklı kaynaktan yaklaşık 40 farklı alıntı var. Birkaç parçayı bir araya getirdim.
Bill K

Son cümleniz, 1. ve 2. kuralların ne zaman uygulanacağını belirsizleştiriyor ve bizi başladığımız yere geri bırakıyor: Hangi optimizasyonların değerli hangilerinin değersiz olduğuna karar vermemiz gerekiyor. Cevabın açık olduğunu varsaymak bir cevap değildir.
Matt

2
Gerçekten bu kadar kafa karıştırıcı mı? İstemci spesifikasyonlarını gerçekten karşılamadığınız ve CPU'nun dil ve önbelleğe alma özellikleri de dahil olmak üzere tüm sistemi çok iyi bilmediğiniz sürece her zaman 1. ve 2. kuralı uygulayın. Bu noktada, YALNIZCA 3'teki prosedürü izleyin, "Hey, bu değişkeni bir alıcı çağırmak yerine yerel olarak önbelleğe alırsam, muhtemelen işler daha hızlı olacaktır. Önce yeterince hızlı olmadığını kanıtlayın, sonra her optimizasyonu ayrı ayrı test edin ve Yardımcı olmayanları atın. Yol boyunca ağır bir şekilde belgeleyin.
Bill K

49

Bence bu o kadar titizleşiyor ki, kodu daha okunaklı kılan her şeyi yapsanız daha iyi olur. İşlemleri milyonlarca olmasa da binlerce kez gerçekleştirmezseniz, birinin farkı anlayacağından şüpheliyim.

Eğer gerçekten seçim yapmanız gerekiyorsa, gidilecek tek yol kıyaslama yapmaktır. Hangi işlevlerin size sorun verdiğini bulun, ardından sorunların işlevin neresinde ortaya çıktığını bulun ve bu bölümleri düzeltin. Bununla birlikte, tek bir matematiksel işlemin (hatta birçok kez tekrarlanan) herhangi bir darboğazın nedeni olacağından hala şüpheliyim.


1
Radar işlemcileri yaptığımda, tek bir işlem fark yarattı. Ancak gerçek zamanlı performansa ulaşmak için makine kodunu elle optimize ediyorduk. Diğer her şey için basit ve açık olana oy veriyorum.
S.Lott

Sanırım bazı şeyler için tek bir ameliyatı düşünebilirsiniz. Ancak, oradaki uygulamaların% 99'unda önemli olmadığını umuyorum.
Thomas Owens

27
Özellikle de OP Python'da bir cevap aradığından beri. Bu kadar verimlilik gerektiren herhangi bir şeyin Python'da yazılacağından şüpheliyim.
Ed S.

4
Bölme, çoğu ışın izleyicinin temeli olan üçgen kesişim rutinindeki muhtemelen en pahalı işlemdir. Karşılıklı depolar ve bölmek yerine çarparsanız, birçok kez hızlanma yaşarsınız.
solinent

@solinent - evet bir hızlanma ama "birçok kez" şüpheliyim - kayan nokta bölme ve çarpma, söz konusu işlemci bölme için değil çarpma için gerçekten optimize edilmedikçe, yaklaşık 4: 1'den fazla farklı olmamalıdır.
Jason S

39

Çarpma daha hızlıdır, bölme daha doğrudur. Numaranız 2'nin kuvveti değilse biraz hassasiyet kaybedersiniz:

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

Derleyicinin tersine çevrilmiş sabiti mükemmel hassasiyete bulmasına izin verseniz bile, cevap yine de farklı olabilir.

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

Hız sorununun yalnızca C / C ++ veya JIT dillerinde ve hatta işlemin darboğazda bir döngüde olması durumunda önemli olması muhtemeldir.


Tam sayılara bölerseniz bölme doğrudur.
Baza

7
Paydalı kayan nokta bölümü> pay düşük sıralı bitlerde anlamsız değerler sunmalıdır; bölünme genellikle doğruluğu azaltır.
S.Lott

8
@ S.Lott: Hayır, bu doğru değil. Tüm IEEE-754 uyumlu kayan nokta uygulamaları, geçerli yuvarlama moduna göre her işlemin sonuçlarını mükemmel bir şekilde (yani en yakın kayan nokta numarasına) yuvarlamalıdır. Karşılıklı ile çarpmak her zaman daha fazla hataya neden olacaktır, çünkü en azından bir tane daha yuvarlama olması gerekir.
Electro

1
Bu cevabın 8 yaşın üzerinde olduğunu biliyorum ama yanıltıcıdır; önemli bir hassasiyet kaybı olmadan bölme gerçekleştirebilirsiniz: y = x * (1.0/3.0);ve derleyici genellikle derleme zamanında 1 / 3'ünü hesaplar. Evet, 1/3, IEEE-754'te mükemmel bir şekilde temsil edilemez, ancak kayan nokta aritmetiğini gerçekleştirirken , çarpma veya bölme yapıyor olsanız da, yine de hassasiyeti kaybedersiniz , çünkü düşük sıralı bitler yuvarlanır. Hesaplamanızın yuvarlama hatasına bu kadar duyarlı olduğunu biliyorsanız, sorunu en iyi şekilde nasıl çözeceğinizi de bilmelisiniz.
Jason S

1
@JasonS 1.0'dan başlayıp 1 ULP kadar artarak bir gecede çalışan bir programdan ayrıldım; İle çarpmanın sonucunu bölü (1.0/3.0)ile karşılaştırdım 3.0. 1.0000036666774155'e kadar ulaştım ve bu alanda sonuçların% 7,3'ü farklıydı. Sadece 1 bit farklı olduklarını sanıyorum, ancak IEEE aritmetiğinin en yakın doğru sonuca yuvarlanması garantili olduğundan, bölmenin daha doğru olduğuna dair ifademin arkasındayım. Farkın önemli olup olmadığı size bağlıdır.
Mark Ransom

25

Kodunuzu optimize etmek istiyor ancak yine de net olmak istiyorsanız, şunu deneyin:

y = x * (1.0 / 2.0);

Derleyici, bölmeyi derleme zamanında yapabilmelidir, böylece çalışma zamanında bir çarpma elde edersiniz. Kesinliğin y = x / 2.0durumdakiyle aynı olmasını beklerdim .

Bunun önemli olduğu durumlarda LOT, kayan nokta aritmetiğini hesaplamak için kayan nokta öykünmesinin gerekli olduğu gömülü işlemcilerdedir.


12
Kendinize (ve bunu kim -1 yaptıysa) uygun hale getirin - bu yerleşik dünyada standart bir uygulamadır ve bu alandaki yazılım mühendisleri bunu açıkça görür.
Jason S

4
+1 burada derleyicilerin kayan nokta işlemlerini istedikleri gibi optimize edemeyeceklerini anlayan tek kişi olduğu için. Hassasiyeti garantilemek için bir çarpmadaki işlenenlerin sırasını bile değiştiremezler (rahat bir mod kullanmadıkça).
rasmus

1
OMG, ilkokul matematiğinin belirsiz olduğunu düşünen en az 6 programcı var. AFAIK, IEEE 754 çarpımı değişmeli (ancak ilişkisel değildir).
maaartinus

13
Belki de asıl noktayı kaçırıyorsunuz. Cebirsel doğrulukla ilgisi yoktur. İdeal bir dünyada ikiye y = x / 2.0;bölebilmeniz gerekir : ancak gerçek dünyada, derleyiciyi daha ucuz bir çarpma işlemi yapması için kandırmanız gerekebilir. Belki neden y = x * (1.0 / 2.0);daha iyi olduğu daha az açıktır ve bunun y = x * 0.5;yerine belirtmek daha açık olacaktır . Ama değiştirmek 2.0bir etmek 7.0ve Ben daha çok görmek istiyorum y = x * (1.0 / 7.0);daha y = x * 0.142857142857;.
Jason S

3
Bu, yönteminizi kullanmanın neden daha okunaklı (ve kesin) olduğunu gerçekten netleştirir.
Juan Martinez

21

"Diğer diller" seçeneği için bir şeyler ekleyeceğim.
C: Bu gerçekten fark yaratmayan akademik bir alıştırma olduğu için , farklı bir şeye katkıda bulunacağımı düşündüm.

Optimizasyon yapmadan montaj için derledim ve sonuca baktım.
Kod:

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;

}

ile derlendi gcc tdiv.c -O1 -o tdiv.s -S

2'ye bölme:

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

ve 0,5 ile çarpma:

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)

Ancak, bunları ints olarak değiştirdiğimde double(ki bu muhtemelen python'un yapacağı şeydir), şunu elde ettim:

bölünme:

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

çarpma işlemi:

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

Bu kodun hiçbirini karşılaştırmadım, ancak sadece kodu inceleyerek, 2'ye bölmenin 2 ile çarpmadan daha kısa olduğunu görebilirsiniz. Çiftler kullanıldığında, çarpma daha kısadır çünkü derleyici işlemcinin kayan noktalı işlem kodlarını kullanır. muhtemelen aynı işlem için kullanmamaktan daha hızlı koşar (ama aslında bilmiyorum). Sonuç olarak, bu cevap, çoklu işlemin performansının 0,5'e karşı 2'ye bölünmesinin, dilin uygulanmasına ve üzerinde çalıştığı platforma bağlı olduğunu göstermiştir. Sonuçta, fark önemsizdir ve okunabilirlik dışında, neredeyse asla endişelenmemeniz gereken bir şeydir.

Bir yan not olarak, programımın main()geri döndüğünü görebilirsiniz a + b. Uçucu anahtar kelimeyi elimden aldığımda, derlemenin neye benzediğini asla tahmin edemezsiniz (program kurulumu hariç):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

hem bölmeyi, hem çarpmayı hem de toplamayı tek bir talimatta yaptı! Optimize edici herhangi bir saygıdeğer ise, bunun için endişelenmenize gerek yok.

Aşırı uzun cevap için özür dilerim.


1
Bu "tek bir talimat" değil. Sadece sürekli katlandı.
kvanberendonck

5
@kvanberendonck Elbette tek bir talimat. Bunları sayın: movl $5, %eax Optimizasyonun adı önemli ve hatta alakalı değil. Dört yıllık bir cevabı küçümsemek istedin.
Carson Myers

2
Optimizasyonun doğasını anlamak hala önemlidir, çünkü içeriğe duyarlıdır: Yalnızca ekliyor / çarpıyor / bölüyorsanız / vb. derleme zamanı sabitleri, burada derleyici tüm matematiği önceden yapabilir ve son cevabı çalışma zamanında bir kayda taşıyabilir. Bölme, genel durumda çarpmadan çok daha yavaştır (çalışma zamanı bölenleri), ancak sanırım karşılıklılarla çarpmanın yalnızca aynı paydaya zaten birden fazla böldüğünüzde yardımcı olacağını düşünüyorum. Muhtemelen bunların hepsini biliyorsunuzdur, ancak yeni programcıların hecelenmesi gerekebilir, bu yüzden ... her ihtimale karşı.
Mike S

10

İlk olarak, C veya ASSEMBLY'de çalışmadığınız sürece, muhtemelen hafızanın durduğu ve genel arama genel giderlerinin, çarpma ve ilgisizlik noktasına kadar bölme arasındaki farkı kesinlikle gölgede bırakacağı daha yüksek seviyeli bir dildesiniz. Öyleyse, bu durumda neyin daha iyi okunacağını seçin.

Çok yüksek bir seviyeden konuşuyorsanız, onu kullanma olasılığınız olan herhangi bir şey için ölçülebilir şekilde daha yavaş olmayacaktır. Diğer cevaplarda göreceksiniz, insanların sadece ikisi arasındaki milisaniyenin altındaki bir farkı ölçmek için bir milyon çarpma / bölme yapması gerekiyor.

Düşük seviye optimizasyon açısından hala merak ediyorsanız:

Bölünme, çarpmadan çok daha uzun bir ardışık düzene sahip olma eğilimindedir. Bu, sonucu almanın daha uzun sürdüğü anlamına gelir, ancak işlemciyi bağımlı olmayan görevlerle meşgul tutabilirseniz, o zaman size bir çarpmadan daha pahalıya mal olmaz.

Boru hattı farkının ne kadar süreceği tamamen donanıma bağlıdır. Kullandığım son donanım FPU çarpımı için 9 döngü ve FPU bölme için 50 döngü gibi bir şeydi. Kulağa çok geliyor, ama sonra bir hafıza kaybı için 1000 döngü kaybedersiniz, bu da işleri perspektif haline getirebilir.

Bir benzetme, bir TV şovunu izlerken mikrodalgaya bir turta koymaktır. Sizi TV programından ayırdığı toplam süre, onu mikrodalgaya koyup mikrodalgadan çıkarmanın ne kadar sürdüğüdür. Geri kalan zamanınızda hala TV programını izlediniz. Yani pastanın pişirilmesi 1 dakika yerine 10 dakika sürdüyse, aslında artık televizyon izleme sürenizi tüketmiyordu.

Pratikte, Multiply ve Divide arasındaki farkı önemseme düzeyine ulaşacaksanız, ardışık düzenleri, önbelleği, dal duraklarını, sıra dışı tahmini ve boru hattı bağımlılıklarını anlamanız gerekir. Bu soruyla nereye gitmeyi düşündüğünüz gibi gelmiyorsa, doğru cevap ikisi arasındaki farkı görmezden gelmektir.

Yıllar önce, bölünmelerden kaçınmak ve her zaman çoğalmaları kullanmak kesinlikle kritikti, ancak o zamanlar hafıza vuruşları daha az alakalıydı ve bölünmeler çok daha kötüydü. Bu günlerde okunabilirliği daha yüksek değerlendiriyorum, ancak okunabilirlik farkı yoksa, çarpımları tercih etmenin iyi bir alışkanlık olduğunu düşünüyorum.


7

Hangisi daha açık bir şekilde niyetinizi belirtirse yazın.

Programınız çalıştıktan sonra neyin yavaş olduğunu bulun ve bunu hızlandırın.

Bunu başka şekilde yapma.


6

Neye ihtiyacın varsa onu yap. Önce okuyucunuzu düşünün, bir performans sorununuz olduğundan emin olana kadar performans konusunda endişelenmeyin.

Bırakın derleyici performansı sizin için yapsın.


5

Tamsayılarla veya kayan nokta olmayan türlerle çalışıyorsanız, bit kaydırma operatörlerinizi unutmayın: << >>

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

7
bu optimizasyon, herhangi bir modern derleyicide perde arkasında otomatik olarak gerçekleştirilir.
Dustin Getz

Bunun yerine bir işlenenin (?) Değiştirilebilir bir sürümü olup olmadığını kontrol edip (bit işlemlerini kullanarak) test eden var mı? işlev mul (a, b) {if (b 2'dir) a << 1 döndürür; eğer (b 4 ise) a << 2 döndür; // ... vb return a * b; } Tahminimce IF çok pahalı ve daha az verimli olacaktı.
Christopher Lightfoot

Bu hayal ettiğime yakın bir yerde basılmadı; Boşver.
Christopher Lightfoot

Const işlemleri için işi normal bir derleyici yapmalıdır; ama burada python kullanıyoruz, bu yüzden bilmek için yeterince akıllı olup olmadığından emin değilim? (Olmalı).
Christopher Lightfoot

İyi bir kısayol, ancak gerçekte ne olduğu hemen net değil. Çoğu programcı, bitshift operatörlerini bile tanımıyor.
Blazemonger

4

Aslında genel bir kural olarak çarpmanın bölmeden daha hızlı olmasının iyi bir nedeni var. Donanımdaki kayan nokta bölümü, ya kaydırma ve koşullu çıkarma algoritmalarıyla (ikili sayılarla "uzun bölme") veya - bugünlerde daha büyük olasılıkla - Goldschmidt'in algoritması gibi yinelemelerle yapılır . Kaydırma ve çıkarma, hassasiyet biti başına en az bir döngüye ihtiyaç duyar (yinelemelerin, çarpmanın kaydır ve eklenmesi gibi paralelleştirilmesi neredeyse imkansızdır) ve yinelemeli algoritmalar en az bir çarpma yapar başına. Her iki durumda da, bölümün daha fazla döngü alması muhtemeldir. Elbette bu, derleyicilerdeki, veri hareketindeki veya hassasiyetteki tuhaflıkları hesaba katmaz. Genel olarak, bir programın zamana duyarlı bir bölümünde bir iç döngü kodluyorsanız,0.5 * xya da 1.0/2.0 * xdaha ziyade x / 2.0makul bir şeydir. "En açık olanı kodlayın" ın bilgiçliği kesinlikle doğrudur, ancak bunların üçü de okunabilirlik açısından o kadar yakındır ki, bu durumda bilgiçlik sadece bilgiçlikçidir.


3

Çarpmanın daha verimli olduğunu hep öğrendim.


"verimli" yanlış kelimedir. Çoğu işlemcinin bölündüğünden daha hızlı çoğaldığı doğrudur. Bununla birlikte, modern boru hatlı mimarilerle programınız hiçbir fark göremeyebilir. Diğerleri söylediğini gibi, gerçekte yolu daha iyi durumda sadece bir insana iyi okur yapıyor.
TED

3

Çarpma genellikle daha hızlıdır - kesinlikle asla daha yavaş değildir. Ancak, hız açısından kritik değilse, hangisi en açıksa onu yazın.


2

Kayan nokta bölmesi (genellikle) özellikle yavaştır, bu nedenle kayan nokta çarpımı da nispeten yavaş olsa da, muhtemelen kayan nokta bölmesinden daha hızlıdır.

Ancak profil oluşturma, bölmenin çarpmaya karşı biraz darboğaz olduğunu göstermedikçe, "gerçekten önemli değil" yanıtını vermeye daha meyilliyim. Yine de, çarpma ve bölme seçiminin uygulamanızda büyük bir performans etkisi olmayacağını tahmin ediyorum.


2

Montajda veya belki C'de programlama yaparken bu daha çok bir soru haline geliyor. Çoğu modern dilde bunun gibi optimizasyonun benim için yapıldığını düşünüyorum.


2

"Çarpmanın genellikle daha iyi olduğunu tahmin etmek daha iyidir, bu yüzden kodlarken buna bağlı kalmaya çalışırım" konusunda dikkatli olun.

Bu özel soru bağlamında, burada daha iyisi, "daha hızlı" anlamına gelir. Bu pek kullanışlı değil.

Hız hakkında düşünmek ciddi bir hata olabilir. Hesaplamanın özgül cebirsel biçiminde büyük hata çıkarımları vardır.

Hata analizi ile Kayan Nokta aritmetiği bölümüne bakınız . Kayan Nokta Aritmetiği ve Hata Analizinde Temel Konulara bakınız .

Bazı kayan nokta değerleri kesin olsa da, çoğu kayan nokta değeri yaklaşıktır; bunlar ideal bir değer artı bazı hatalardır. Her işlem ideal değer ve hata değeri için geçerlidir.

En büyük sorunlar, neredeyse eşit iki sayıyı manipüle etmeye çalışmaktan kaynaklanıyor. En sağdaki bitler (hata bitleri) sonuçlara hakim olur.

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

Bu örnekte, değerler küçüldükçe, neredeyse eşit sayılar arasındaki farkın, doğru cevabın sıfır olduğu sıfır olmayan sonuçlar oluşturduğunu görebilirsiniz.


1

C / C ++ 'da çarpmanın daha verimli olduğunu bir yerde okudum; Tercüme edilmiş diller hakkında hiçbir fikrim yok - diğer tüm ek yükler nedeniyle fark muhtemelen ihmal edilebilir.

Daha sürdürülebilir / okunabilir olanla ilgili bir sorun haline gelmedikçe - insanların bana bunu söylemesinden nefret ediyorum ama bu çok doğru.


1

Genel olarak çarpmayı öneririm, çünkü döngüleri böleninizin 0 olmadığından emin olmak için harcamanıza gerek yoktur. Böleniniz bir sabitse, elbette bu geçerli değildir.


1

Samsung GT-S5830 üzerinde profilli 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;
    }
}

Sonuçlar?

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

Bölme, çarpmadan yaklaşık% 20 daha hızlıdır (!)


1
Gerçekçi olmak gerekirse, test etmelisin a = i*0.5, değil a *= 0.5. Çoğu programcı işlemleri bu şekilde kullanacaktır.
Blazemonger

1

24 numaralı (çarpma daha hızlı) ve 30 numaralı gönderilerde olduğu gibi - ancak bazen ikisi de anlaşılması kadar kolaydır:

1*1e-6F;

1/1e6F;

~ İkisini de okumak kadar kolay buluyorum ve milyarlarca kez tekrar etmem gerekiyor. Bu yüzden çarpmanın genellikle daha hızlı olduğunu bilmek faydalıdır.


1

Bir fark vardır, ancak derleyiciye bağlıdır. İlk olarak vs2003 (c ++) 'da çift tipler (64 bit kayan nokta) için önemli bir fark görmedim. Bununla birlikte, testleri tekrar vs2010'da çalıştırdığımda, çarpımlar için 4 faktöre kadar daha hızlı büyük bir fark tespit ettim. Bunu takip ederek, vs2003 ve vs2010'un farklı fpu kodu ürettiği anlaşılıyor.

Pentium 4, 2.8 GHz, 2003'e karşı:

  • Çarpma: 8.09
  • Bölüm: 7.97

Xeon W3530, vs2003'te:

  • Çarpma: 4.68
  • Bölüm: 4.64

Xeon W3530, vs2010'da:

  • Çarpma: 5.33
  • Bölüm: 21.05

Öyle görünüyor ki, vs2003'te döngüdeki bir bölme (bölen birden çok kez kullanıldı) ters ile çarpmaya çevrildi. Vs2010'da bu optimizasyon artık uygulanmıyor (sanırım iki yöntem arasında biraz farklı sonuç olduğu için). Ayrıca, payınız 0.0 olduğunda cpu'nun bölmeleri daha hızlı gerçekleştirdiğini unutmayın. Çipte bulunan hassas algoritmayı bilmiyorum, ama belki sayıya bağlı.

Edit 18-03-2013: vs2010 için gözlem


Bir derleyicinin örneğin n/10.0formun bir ifadesiyle değiştirememesinin herhangi bir nedeni var mı acaba (n * c1 + n * c2)? Çoğu işlemcide bir bölmenin iki çarpma ve bir bölmeden daha uzun süreceğini beklerim ve herhangi bir sabitle bölmenin belirtilen formülasyonu kullanan her durumda doğru şekilde yuvarlanmış bir sonuç verebileceğine inanıyorum.
supercat

1

İşte aptalca eğlenceli bir cevap:

x / 2.0 , x * 0.5'e eşdeğer değildir

Diyelim ki bu yöntemi 22 Ekim 2008'de yazdınız.

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

Şimdi, 10 yıl sonra bu kod parçasını optimize edebileceğinizi öğreniyorsunuz. Yönteme, uygulamanız boyunca yüzlerce formülde başvurulur. Yani onu değiştirirsiniz ve% 5'lik dikkate değer bir performans artışı yaşarsınız.

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

Kodu değiştirmek doğru bir karar mıydı? Matematikte iki ifade gerçekten eşdeğerdir. Bilgisayar biliminde bu her zaman doğru değildir. Daha fazla ayrıntı için lütfen Doğruluk sorunlarının etkisini en aza indirme bölümünü okuyun . Hesaplanan değerleriniz - bir noktada - diğer değerlerle karşılaştırılırsa, uç durumların sonucunu değiştirirsiniz. Örneğin:

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

Alt satırda; ikisinden birine razı olduktan sonra, ona sadık kalın!


1
Downvote? Düşüncelerinizi açıklayan bir yoruma ne dersiniz? Bu cevap kesinlikle% 100 alakalı.
l33t

Bilgisayar biliminde, kayan nokta değerlerinin 2'nin katlarıyla çarpılması / bölünmesi, değer normal olmayan hale gelmedikçe veya taşmadıkça kayıpsızdır.
Yakında

Kayan nokta bölünme anında kayıpsız olmadığından, ifadenizin doğru olup olmadığı gerçekten önemli değildir. Gerçi öyle olsaydı çok şaşırırdım.
l33t

1
"Kayan nokta, bölünme anında kayıpsız değildir", yalnızca kullanımdan kaldırılmış x87 kodunu yayan eski bir derleyici ile oluşturduğunuzda. Modern donanımda sadece kayan / çift değişkenli kayıpsızdır, 32 veya 64 bit IEEE 754: en.wikipedia.org/wiki/IEEE_754 IEEE 754'ün çalışma şekli nedeniyle, 2'ye böldüğünüzde veya 0,5 ile çarptığınızda, azalırsınız üs 1, geri kalan bitler (işaret + mantis) değişmez. Ve her ikisi de 2ve 0.5sayılar (örneğin aksine hassasiyet kaybı olmaksızın, tam olarak IEEE 754 temsil edilebilir 0.4veya 0.1bunlar olamaz).
Yakında

0

Bir toplama / çıkarma işleminin maliyetinin 1 olduğunu varsayarsak, maliyeti 5'i çarpın ve maliyetleri yaklaşık 20'ye bölün.


Bu numaraları nereden aldın? deneyim? içgüdüsel duygu? internetteki makale? farklı veri türleri için nasıl değişecekler?
kroiz

0

Böylesine uzun ve ilginç bir tartışmadan sonra, benim şu konuyu ele alıyorum: Bu sorunun nihai cevabı yok. Bazı insanların da belirttiği gibi, hem donanıma (cf piotrk ve gast128 ) hem de derleyiciye (cf @Javier testleri) bağlıdır. Hız kritik değilse, uygulamanızın gerçek zamanlı büyük miktarda veriyi işlemesi gerekmiyorsa, bir bölme kullanarak netliği tercih edebilirsiniz, oysa işlem hızı veya işlemci yükü bir sorunsa, çarpma en güvenli olabilir. Son olarak, uygulamanızın hangi platformda kullanılacağını tam olarak bilmiyorsanız, kıyaslama anlamsızdır. Ve kod netliği için, tek bir yorum iş görür!


-3

Teknik olarak bölme diye bir şey yoktur, sadece ters elemanlarla çarpma vardır. Örneğin, asla 2'ye bölmezsiniz, aslında 0,5 ile çarparsınız.

'Bölümü' - bu bir saniye var olduğu edelim kendimize çocuk - çünkü 'böl' zor hep çarpma olduğu xkadar ydeğeri hesaplamak için bir ilk ihtiyaçlar y^{-1}öyle ki y*y^{-1} = 1daha sonra çarpma yapmak x*y^{-1}. Zaten biliyorsanız, y^{-1}o zaman hesaplamamak ybir optimizasyon olmalıdır.


3
Silikonda bulunan her iki komutun gerçekliğini tamamen görmezden gelen.
NPSF3000

@ NPSF3000 - Takip etmiyorum. Her iki işlemin de var olduğu varsayımı altında, basitçe bölme işleminin dolaylı olarak çarpımsal bir tersinin ve çarpmanın hesaplanmasını içerdiğini ileri sürer ki bu, her zaman tek bir çarpma yapmaktan daha zor olacaktır. Silikon bir uygulama detayıdır.
satnhak

@ BTyler. Silikonda her iki komut da mevcutsa ve her iki komut da [bekleneceği gibi] komutların görece karmaşık olduğundan aynı sayıda döngü alırsa, performans POV'undan tamamen alakasızdır.
NPSF3000

@ NPSF3000 - ancak ikisi de aynı sayıda döngü almazlar çünkü çarpma daha hızlıdır.
satnhak
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.