Haruskah saya menggunakan perkalian atau pembagian?

118

Inilah pertanyaan konyol yang menyenangkan:

Katakanlah kita harus melakukan operasi sederhana di mana kita membutuhkan setengah dari nilai variabel. Ada biasanya dua cara untuk melakukan hal ini:

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

Dengan asumsi kami menggunakan operator standar yang disediakan dengan bahasa, mana yang memiliki kinerja lebih baik?

Saya menebak perkalian biasanya lebih baik jadi saya mencoba untuk tetap berpegang pada itu ketika saya membuat kode, tapi saya ingin mengkonfirmasi ini.

Meskipun secara pribadi saya tertarik dengan jawaban untuk Python 2.4-2.5, jangan ragu untuk mengirimkan jawaban untuk bahasa lain! Dan jika Anda mau, jangan ragu untuk memposting cara lain yang lebih bagus (seperti menggunakan operator shift bitwise) juga.

Edmundito
sumber
5
Apakah Anda menjalankan benchmark? Itu hanya sekitar selusin baris kode. Apa yang Anda pelajari dari menjalankan benchmark? [Petunjuk: melakukan itu akan lebih cepat daripada memposting pertanyaan di sini.]
S.Lott
4
Pertanyaan bagus, yang menghasilkan beberapa jawaban / diskusi yang cukup menarik. Terima kasih :)
stealthcopter
22
Sekalipun dia telah mempelajari jawabannya dengan melakukan benchmarking, itu masih merupakan pertanyaan yang berguna dan telah menghasilkan beberapa jawaban yang menarik dan bermanfaat. Juga saya berharap orang-orang akan tetap pada intinya dan menahan diri dari menulis jawaban dan komentar ke jawaban yang menawarkan saran yang tidak relevan tentang apakah perlu atau tidak membuat pengoptimalan yang dimaksud. Mengapa tidak berasumsi bahwa OP mengajukan pertanyaan seperti yang tertulis alih-alih mengasumsikan bahwa dia 'benar-benar' menginginkan saran dalam skala yang lebih besar untuk menulis ulang.
Kevin Whitefoot
1
Pembagian jauh lebih lambat, daripada perkalian. Tetapi beberapa pemenuhan / VM cerdas mengubah pembagian menjadi perkalian, sehingga pengujian Anda akan memiliki hasil yang sama (kedua pengujian menguji perkalian).
Ivan Kuckir
4
Sedikit keluar dari topik, tapi saya hanya ingin mengatakan seberapa setuju saya dengan @KevinWitefoot. Tidak ada yang lebih membuat frustrasi daripada membaca dari khotbah daripada jawaban teknis untuk pertanyaan teknis. Terima kasih Kevin atas komentar Anda!
Jean-François

Jawaban:

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

perkalian 33% lebih cepat

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

=> tidak ada perbedaan nyata

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

=> hanya 5% lebih cepat

kesimpulan: dengan Python, lebih cepat menggandakan daripada membagi, tetapi saat Anda mendekati CPU menggunakan VM atau JIT yang lebih canggih, keuntungannya menghilang. Sangat mungkin bahwa VM Python di masa depan akan membuatnya tidak relevan

Javier
sumber
Terima kasih atas tip dalam menggunakan perintah waktu untuk benchmarking!
Edmundito
2
Kesimpulan Anda salah. Ini menjadi lebih relevan karena JIT / VM menjadi lebih baik. Pembagian menjadi lebih lambat dibandingkan dengan overhead VM yang lebih rendah. Ingatlah bahwa penyusun umumnya tidak dapat banyak mengoptimalkan floating point untuk menjamin presisi.
rasmus
7
@rasmus: Saat JIT menjadi lebih baik, JIT menjadi lebih mungkin untuk menggunakan instruksi perkalian CPU meskipun Anda meminta pembagian.
Ben Voigt
68

Selalu gunakan apa pun yang paling jelas. Hal lain yang Anda lakukan adalah mencoba mengakali kompilator. Jika kompilernya cerdas, ia akan melakukan yang terbaik untuk mengoptimalkan hasil, tetapi tidak ada yang bisa membuat orang berikutnya tidak membenci Anda atas solusi pengalihan bit Anda yang buruk (omong-omong, saya suka manipulasi bit, itu menyenangkan. Tapi menyenangkan! = Dapat dibaca )

Optimasi prematur adalah akar dari segala kejahatan. Ingatlah selalu tiga aturan pengoptimalan!

  1. Jangan optimalkan.
  2. Jika Anda seorang ahli, lihat aturan # 1
  3. Jika Anda adalah seorang ahli dan dapat membenarkan kebutuhan tersebut, gunakan prosedur berikut:

    • Kode itu tidak dioptimalkan
    • menentukan seberapa cepat "Cukup cepat" - Catat persyaratan / cerita pengguna mana yang memerlukan metrik tersebut.
    • Tulis tes kecepatan
    • Uji kode yang ada - Jika cukup cepat, Anda sudah selesai.
    • Recode itu dioptimalkan
    • Uji kode yang dioptimalkan. JIKA tidak memenuhi metrik, buang dan simpan yang asli.
    • Jika memenuhi ujian, simpan kode asli sebagai komentar

Selain itu, melakukan hal-hal seperti menghapus loop dalam saat tidak diperlukan atau memilih daftar tertaut di atas array untuk jenis penyisipan bukanlah pengoptimalan, hanya pemrograman.

Bill K
sumber
7
itu bukan kutipan lengkap Knuth; lihat en.wikipedia.org/wiki/…
Jason S
Tidak, ada sekitar 40 kutipan berbeda tentang masalah ini dari berbagai sumber. Saya mengumpulkan beberapa.
Bill K
Kalimat terakhir Anda membuat tidak jelas kapan harus menerapkan aturan # 1 dan # 2, meninggalkan kita kembali ke tempat kita mulai: Kita perlu memutuskan pengoptimalan mana yang bermanfaat dan mana yang tidak. Berpura-pura jawabannya sudah jelas bukanlah jawaban.
Matt
2
Benar-benar membingungkanmu? Selalu terapkan aturan 1 dan 2 kecuali Anda benar-benar tidak memenuhi spesifikasi klien dan sangat paham dengan keseluruhan sistem termasuk bahasa dan karakteristik cache dari CPU. Pada titik itu, HANYA ikuti prosedur di 3, jangan hanya berpikir "Hei, jika saya menyimpan variabel ini secara lokal daripada memanggil getter, semuanya mungkin akan lebih cepat. Pertama-tama buktikan bahwa ini tidak cukup cepat kemudian uji setiap pengoptimalan secara terpisah dan membuang yang tidak membantu Dokumen yang banyak sepanjang jalan
Bill K
49

Saya pikir ini menjadi sangat rewel sehingga Anda akan lebih baik melakukan apa pun yang membuat kode lebih mudah dibaca. Kecuali jika Anda melakukan operasi ribuan, jika tidak jutaan, kali, saya ragu ada orang yang akan melihat perbedaannya.

Jika Anda benar-benar harus membuat pilihan, pembandingan adalah satu-satunya cara. Temukan fungsi apa yang memberi Anda masalah, kemudian cari tahu di fungsi mana masalah tersebut terjadi, dan perbaiki bagian tersebut. Namun, saya masih ragu bahwa satu operasi matematika (bahkan yang diulang berkali-kali) akan menjadi penyebab kemacetan.

Thomas Owens
sumber
1
Ketika saya biasa membuat prosesor radar, satu operasi memang membuat perbedaan. Namun kami mengoptimalkan kode mesin secara manual untuk mencapai kinerja waktu nyata. Untuk yang lainnya, saya memilih yang sederhana dan jelas.
S. Lott
Saya rasa untuk beberapa hal, Anda mungkin peduli dengan satu operasi. Tetapi saya berharap bahwa dalam 99% aplikasi di luar sana, itu tidak masalah.
Thomas Owens
27
Terutama karena OP sedang mencari jawaban dengan Python. Saya ragu apa pun yang membutuhkan efisiensi sebesar itu akan ditulis dengan Python.
Ed S.
4
Pembagian mungkin adalah operasi yang paling mahal dalam rutinitas persimpangan segitiga, yang merupakan dasar bagi kebanyakan pelacak sinar. Jika Anda menyimpan timbal balik dan mengalikan alih-alih membagi, Anda akan mengalami percepatan berkali-kali.
solinent
@solinent - ya speedup tapi saya ragu "berkali-kali" - pembagian dan perkalian floating-point tidak boleh berbeda lebih dari sekitar 4: 1, kecuali prosesor yang dimaksud benar-benar dioptimalkan untuk perkalian dan bukan pembagian.
Jason S
39

Perkalian lebih cepat, pembagian lebih akurat. Anda akan kehilangan beberapa presisi jika angka Anda bukan pangkat 2:

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

Bahkan jika Anda membiarkan penyusun menghitung konstanta terbalik hingga presisi sempurna, jawabannya masih bisa berbeda.

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

Masalah kecepatan hanya mungkin menjadi masalah dalam bahasa C / C ++ atau JIT, dan bahkan hanya jika operasi berada dalam loop yang mengalami hambatan.

Mark Ransom
sumber
Pembagian akurat jika Anda membagi dengan bilangan bulat.
alas tiang
7
Pembagian floating-point dengan penyebut> pembilang harus memasukkan nilai yang tidak berarti dalam bit orde rendah; pembagian biasanya mengurangi akurasi.
S. Lott
8
@ S. Lott: Tidak, itu tidak benar. Semua implementasi floating point yang sesuai dengan IEEE-754 harus membulatkan hasil dari setiap operasi dengan sempurna (yaitu ke nomor floating point terdekat) sehubungan dengan mode pembulatan saat ini. Mengalikan dengan kebalikan selalu akan menghasilkan lebih banyak kesalahan, setidaknya karena satu pembulatan lagi harus terjadi.
Elektro
1
Saya tahu jawaban ini sudah lebih dari 8 tahun, tetapi menyesatkan; Anda dapat melakukan pembagian tanpa kehilangan presisi yang signifikan: y = x * (1.0/3.0);dan kompiler biasanya akan menghitung 1/3 pada waktu kompilasi. Ya, 1/3 tidak dapat direpresentasikan secara sempurna di IEEE-754, tetapi ketika Anda melakukan aritmatika floating-point, Anda tetap kehilangan presisi , baik saat Anda melakukan perkalian atau pembagian, karena bit orde rendah dibulatkan. Jika Anda tahu bahwa komputasi Anda begitu sensitif terhadap kesalahan pembulatan, Anda juga harus tahu cara terbaik menangani masalah ini.
Jason S
1
@JasonS Saya baru saja meninggalkan program yang berjalan semalaman, mulai dari 1.0 dan menghitung hingga 1 ULP; Saya membandingkan hasil perkalian dengan (1.0/3.0)dengan membagi dengan 3.0. Saya mendapatkan hingga 1,0000036666774155, dan dalam ruang itu 7,3% hasilnya berbeda. Saya menganggap mereka hanya berbeda 1 bit, tetapi karena aritmatika IEEE dijamin membulatkan ke hasil terdekat yang benar, saya mendukung pernyataan saya bahwa pembagian lebih akurat. Apakah perbedaannya signifikan terserah Anda.
Markus Tebusan
25

Jika Anda ingin mengoptimalkan kode Anda tetapi masih jelas, coba ini:

y = x * (1.0 / 2.0);

Kompilator harus dapat melakukan pembagian pada waktu kompilasi, sehingga Anda mendapatkan kelipatan pada saat run-time. Saya berharap ketepatannya sama seperti dalam y = x / 2.0kasus ini.

Di mana hal ini mungkin menjadi masalah, LOT ada di prosesor tertanam di mana emulasi floating-point diperlukan untuk menghitung aritmatika floating-point.

Jason S
sumber
12
Terserah Anda (dan siapa pun yang memberi ini) - ini adalah praktik standar di dunia tertanam dan insinyur perangkat lunak di bidang itu merasa jelas.
Jason S
4
+1 untuk menjadi satu-satunya di sini yang menyadari bahwa kompiler tidak dapat mengoptimalkan operasi floating point sesuka mereka. Mereka bahkan tidak dapat mengubah urutan operan dalam perkalian untuk menjamin ketepatan (kecuali jika menggunakan mode santai).
rasmus
1
OMG, setidaknya ada 6 programmer yang menganggap matematika dasar itu tidak jelas. AFAIK, perkalian IEEE 754 bersifat komutatif (tetapi non-asosiatif).
maaartinus
13
Mungkin Anda melewatkan intinya. Ini tidak ada hubungannya dengan ketepatan aljabar. Dalam dunia yang ideal, Anda seharusnya hanya dapat membagi dua:, y = x / 2.0;tetapi di dunia nyata, Anda mungkin harus membujuk kompiler untuk melakukan perkalian yang lebih murah. Mungkin kurang jelas mengapa y = x * (1.0 / 2.0);lebih baik, dan y = x * 0.5;sebaliknya akan lebih jelas untuk dinyatakan . Tapi ubah 2.0menjadi a 7.0dan saya lebih suka melihat y = x * (1.0 / 7.0);daripada y = x * 0.142857142857;.
Jason S
3
Ini benar-benar menjelaskan mengapa metode Anda lebih terbaca (dan tepat).
Juan Martinez
21

Hanya akan menambahkan sesuatu untuk opsi "bahasa lain".
C: Karena ini hanya latihan akademis yang benar - benar tidak ada bedanya, saya pikir saya akan memberikan kontribusi yang berbeda.

Saya menyusun untuk berkumpul tanpa optimasi dan melihat hasilnya.
Kode:

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;

}

dikompilasi dengan gcc tdiv.c -O1 -o tdiv.s -S

pembagian dengan 2:

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

dan perkalian dengan 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)

Namun, ketika saya mengubahnya intmenjadi doubles (yang mungkin akan dilakukan python), saya mendapatkan ini:

divisi:

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

perkalian:

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

Saya belum membandingkan kode ini, tetapi hanya dengan memeriksa kode Anda dapat melihat bahwa menggunakan bilangan bulat, pembagian dengan 2 lebih pendek daripada perkalian dengan 2. Menggunakan penggandaan, perkalian lebih pendek karena kompilator menggunakan opcode titik mengambang prosesor, yang mana mungkin berjalan lebih cepat (tapi sebenarnya saya tidak tahu) daripada tidak menggunakannya untuk operasi yang sama. Jadi pada akhirnya jawaban ini telah menunjukkan bahwa kinerja perkalian dengan 0,5 vs. pembagian dengan 2 bergantung pada implementasi bahasa dan platform tempat menjalankannya. Pada akhirnya perbedaan tersebut dapat diabaikan dan merupakan sesuatu yang sebenarnya tidak pernah Anda khawatirkan, kecuali dalam hal keterbacaan.

Sebagai catatan tambahan, Anda dapat melihat bahwa di program saya main()kembali a + b. Ketika saya mengambil kata kunci yang mudah menguap, Anda tidak akan pernah menebak seperti apa rakitan itu (tidak termasuk pengaturan program):

## 5/2

## 5*0.5
## done

movl    $5, %eax
leave
ret

itu melakukan baik pembagian, perkalian, DAN penambahan dalam satu instruksi! Jelas Anda tidak perlu khawatir tentang ini jika pengoptimal adalah jenis yang terhormat.

Maaf atas jawaban yang terlalu panjang.

Carson Myers
sumber
1
Ini bukan "instruksi tunggal". Itu baru saja terlipat terus-menerus.
kvanberendonck
5
@kvanberendonck Tentu saja itu satu instruksi. Hitung mereka: movl $5, %eax Nama pengoptimalan tidak penting atau bahkan relevan. Anda hanya ingin merendahkan jawaban berusia empat tahun.
Carson Myers
2
Sifat pengoptimalan masih penting untuk dipahami, karena ini peka konteks: Ini hanya berlaku jika Anda menambahkan / mengalikan / membagi / dll. konstanta waktu kompilasi, di mana kompilator bisa melakukan semua perhitungan matematika terlebih dahulu dan memindahkan jawaban akhir ke dalam register pada waktu proses. Pembagian jauh lebih lambat daripada perkalian dalam kasus umum (pembagi runtime), tapi saya kira mengalikan dengan resiprokal hanya membantu jika Anda membagi dengan penyebut yang sama lebih dari sekali. Anda mungkin tahu semua itu, tetapi programmer yang lebih baru mungkin membutuhkannya, jadi ... untuk berjaga-jaga.
Mike S
10

Pertama, kecuali Anda bekerja di C atau ASSEMBLY, Anda mungkin menggunakan bahasa tingkat yang lebih tinggi di mana memori macet dan overhead panggilan umum akan benar-benar mengecilkan perbedaan antara mengalikan dan membagi ke titik yang tidak relevan. Jadi, pilih saja yang terbaca lebih baik dalam kasus itu.

Jika Anda berbicara dari tingkat yang sangat tinggi, itu tidak akan lebih lambat untuk apa pun yang kemungkinan besar Anda gunakan. Anda akan melihat di jawaban lain, orang perlu melakukan satu juta perkalian / bagi hanya untuk mengukur beberapa perbedaan sub-milidetik di antara keduanya.

Jika Anda masih penasaran, dari sudut pandang pengoptimalan tingkat rendah:

Divide cenderung memiliki pipa yang jauh lebih panjang daripada multiply. Ini berarti membutuhkan waktu lebih lama untuk mendapatkan hasilnya, tetapi jika Anda dapat membuat prosesor sibuk dengan tugas-tugas yang tidak bergantung, maka itu tidak akan menghabiskan biaya lebih dari kelipatan.

Berapa lama perbedaan pipeline sepenuhnya bergantung pada perangkat keras. Perangkat keras terakhir yang saya gunakan adalah sekitar 9 siklus untuk perkalian FPU dan 50 siklus untuk pembagian FPU. Kedengarannya banyak, tetapi kemudian Anda akan kehilangan 1000 siklus karena kehilangan memori, sehingga dapat menempatkan segala sesuatunya dalam perspektif.

Analoginya adalah memasukkan pai ke dalam microwave saat Anda menonton acara TV. Total waktu yang Anda perlukan dari acara TV adalah berapa lama Anda harus memasukkannya ke dalam microwave, dan mengeluarkannya dari microwave. Sisa waktu Anda masih menonton acara TV. Jadi jika pai membutuhkan waktu 10 menit untuk dimasak, bukan 1 menit, itu tidak benar-benar menghabiskan waktu menonton TV Anda lagi.

Dalam praktiknya, jika Anda ingin mencapai tingkat kepedulian tentang perbedaan antara Multiply dan Divide, Anda perlu memahami pipeline, cache, branch stall, prediksi out-of-order, dan dependensi pipeline. Jika ini tidak terdengar seperti tujuan Anda dengan pertanyaan ini, maka jawaban yang benar adalah mengabaikan perbedaan di antara keduanya.

Bertahun-tahun yang lalu sangatlah penting untuk menghindari pembagian dan selalu menggunakan perkalian, tetapi saat itu hit ingatan kurang relevan, dan pembagian jauh lebih buruk. Hari-hari ini saya menilai keterbacaan lebih tinggi, tetapi jika tidak ada perbedaan keterbacaan, saya pikir itu adalah kebiasaan yang baik untuk memilih penggandaan.

James Podesta
sumber
7

Tulis mana saja yang lebih jelas menyatakan maksud Anda.

Setelah program Anda berfungsi, cari tahu apa yang lambat, dan buat lebih cepat.

Jangan lakukan sebaliknya.

Jay Bazuzi
sumber
6

Lakukan apapun yang Anda butuhkan. Pikirkan pembaca Anda terlebih dahulu, jangan khawatir tentang kinerja sampai Anda yakin Anda memiliki masalah kinerja.

Biarkan kompilator melakukan kinerja untuk Anda.

buti-oxa
sumber
5

Jika Anda bekerja dengan integer atau tipe non floating point jangan lupa operator bitshifting Anda: << >>

    int y = 10;
    y = y >> 1;
    Console.WriteLine("value halved: " + y);
    y = y << 1;
    Console.WriteLine("now value doubled: " + y);
sbeskur.dll
sumber
7
pengoptimalan ini secara otomatis dilakukan di belakang layar dalam kompiler modern manapun.
Dustin Getz
Adakah yang menguji jika memeriksa (menggunakan bit ops) jika operand (?) Memiliki versi yang dapat digeser untuk menggunakannya? function mul (a, b) {if (b is 2) return a << 1; if (b adalah 4) mengembalikan a << 2; // ... etc return a * b; } Dugaan saya adalah IF sangat mahal sehingga kurang efisien.
Christopher Lightfoot
Itu tidak tercetak mendekati apa yang saya bayangkan; Lupakan.
Christopher Lightfoot
Untuk operasi const, kompilator normal harus melakukan pekerjaan itu; tapi di sini kami menggunakan python jadi saya tidak yakin apakah cukup pintar untuk mengetahuinya? (Harus).
Christopher Lightfoot
Pintasan yang bagus, hanya saja tidak jelas apa yang sebenarnya terjadi. Kebanyakan programmer bahkan tidak mengenali operator bitshift.
Blazemonger
4

Sebenarnya ada alasan bagus bahwa sebagai aturan umum perkalian akan lebih cepat daripada pembagian. Pembagian floating point dalam perangkat keras dilakukan dengan algoritma shift dan pengurangan bersyarat ("pembagian panjang" dengan bilangan biner) atau - lebih mungkin hari ini - dengan iterasi seperti algoritma Goldschmidt . Pergeseran dan pengurangan membutuhkan setidaknya satu siklus per bit presisi (iterasi hampir tidak mungkin untuk diparalelkan seperti halnya pergeseran-dan-tambah perkalian), dan algoritma iteratif melakukan setidaknya satu perkalian per iterasi. Dalam kedua kasus tersebut, kemungkinan besar pembagian akan mengambil lebih banyak siklus. Tentu saja ini tidak memperhitungkan kebiasaan dalam kompiler, pergerakan data, atau presisi. Namun, pada umumnya, jika Anda membuat kode loop dalam di bagian program yang sensitif terhadap waktu,0.5 * xatau 1.0/2.0 * xbukan x / 2.0merupakan hal yang wajar untuk dilakukan. Pedantry dari "kode apa yang paling jelas" benar-benar benar, tetapi ketiganya sangat dekat dalam keterbacaan sehingga penyebutan dalam kasus ini hanya sekedar omong kosong.

Gen
sumber
3

Saya selalu belajar bahwa perkalian lebih efisien.

Toon Krijthe
sumber
"efisien" adalah kata yang salah. Memang benar bahwa kebanyakan prosesor mengalikan lebih cepat daripada yang mereka bagi. Namun, dengan arsitektur jaringan pipa modern, program Anda mungkin tidak melihat perbedaan. Seperti yang dikatakan banyak orang lain, Anda benar-benar jauh lebih baik hanya melakukan apa yang paling baik dibaca oleh manusia.
TED
3

Perkalian biasanya lebih cepat - pasti tidak pernah lebih lambat. Namun, jika bukan kecepatan kritis, tulis mana saja yang paling jelas.

Dan Hewett
sumber
2

Pembagian floating-point (umumnya) sangat lambat, jadi meskipun perkalian floating-point juga relatif lambat, mungkin lebih cepat daripada pembagian floating-point.

Tapi saya lebih cenderung menjawab "itu tidak terlalu penting", kecuali pembuatan profil telah menunjukkan bahwa pembagian sedikit hambatan vs. perkalian. Saya menduga, bahwa pilihan perkalian vs. pembagian tidak akan berdampak besar pada kinerja aplikasi Anda.

mipadi
sumber
2

Ini menjadi lebih dari sebuah pertanyaan ketika Anda memprogram dalam perakitan atau mungkin C. Saya pikir dengan sebagian besar bahasa modern pengoptimalan seperti ini dilakukan untuk saya.

Seamus
sumber
2

Berhati-hatilah dengan "perkalian menebak biasanya lebih baik jadi saya mencoba untuk tetap menggunakannya saat saya membuat kode,"

Dalam konteks pertanyaan khusus ini, lebih baik di sini berarti "lebih cepat". Yang tidak terlalu berguna.

Memikirkan kecepatan bisa menjadi kesalahan serius. Ada implikasi kesalahan yang sangat besar dalam bentuk perhitungan aljabar tertentu.

Lihat aritmatika Floating Point dengan analisis kesalahan . Lihat Masalah Dasar dalam Aritmatika Titik Mengambang dan Analisis Kesalahan .

Meskipun beberapa nilai floating-point tepat, sebagian besar nilai floating-point merupakan perkiraan; mereka adalah beberapa nilai ideal ditambah beberapa kesalahan. Setiap operasi berlaku untuk nilai ideal dan nilai kesalahan.

Masalah terbesar datang dari mencoba memanipulasi dua angka yang hampir sama. Bit paling kanan (bit error) mendominasi hasil.

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

Dalam contoh ini, Anda dapat melihat bahwa saat nilai semakin kecil, perbedaan antara angka yang hampir sama menghasilkan hasil bukan nol dengan jawaban yang benar adalah nol.

S. Lott
sumber
1

Saya pernah membaca bahwa perkalian lebih efisien dalam C / C ++; Tidak tahu tentang bahasa yang ditafsirkan - perbedaannya mungkin dapat diabaikan karena semua overhead lainnya.

Kecuali jika itu menjadi masalah, tetap dengan apa yang lebih mudah dipelihara / dibaca - Saya benci ketika orang memberi tahu saya ini tetapi itu sangat benar.

Christopher Lightfoot
sumber
1

Saya menyarankan perkalian secara umum, karena Anda tidak perlu menghabiskan siklus untuk memastikan pembagi Anda bukan 0. Ini tidak berlaku, tentu saja, jika pembagi Anda adalah konstanta.

Steve
sumber
1

Android Java, diprofilkan di 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;
    }
}

Hasil?

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

Pembagian sekitar 20% lebih cepat dari perkalian (!)

PiotrK
sumber
1
Agar realistis, Anda harus menguji a = i*0.5, bukan a *= 0.5. Begitulah cara kebanyakan programmer menggunakan operasi.
Blazemonger
1

Seperti postingan # 24 (perkalian lebih cepat) dan # 30 - tetapi terkadang keduanya sama mudahnya untuk dipahami:

1*1e-6F;

1/1e6F;

~ Saya menemukan keduanya sama mudahnya untuk dibaca, dan harus mengulanginya miliaran kali. Jadi sangat berguna untuk mengetahui bahwa perkalian biasanya lebih cepat.

Chris
sumber
1

Ada perbedaan, tetapi bergantung pada kompiler. Awalnya pada vs2003 (c ++) saya tidak mendapatkan perbedaan yang signifikan untuk tipe ganda (64 bit floating point). Namun menjalankan tes lagi pada vs2010, saya mendeteksi perbedaan besar, hingga faktor 4 lebih cepat untuk perkalian. Melacak ini, tampaknya vs2003 dan vs2010 menghasilkan kode fpu yang berbeda.

Pada Pentium 4, 2,8 GHz, vs 2003:

  • Perkalian: 8.09
  • Divisi: 7.97

Pada Xeon W3530, vs2003:

  • Perkalian: 4.68
  • Divisi: 4.64

Pada Xeon W3530, vs2010:

  • Perkalian: 5.33
  • Divisi: 21.05

Tampaknya pada vs2003, pembagian dalam satu lingkaran (sehingga pembagi digunakan beberapa kali) diterjemahkan menjadi perkalian dengan invers. Pada vs2010, pengoptimalan ini tidak diterapkan lagi (saya kira karena ada hasil yang sedikit berbeda antara kedua metode). Perhatikan juga bahwa cpu melakukan pembagian lebih cepat segera setelah pembilang Anda 0,0. Saya tidak tahu persis algoritma yang tertanam dalam chip, tapi mungkin itu tergantung pada angka.

Edit 18-03-2013: observasi untuk vs2010

gast128
sumber
Saya ingin tahu apakah ada alasan mengapa kompilator tidak dapat mengganti misalnya n/10.0dengan ekspresi formulir (n * c1 + n * c2)? Saya berharap bahwa pada kebanyakan prosesor, pembagian akan memakan waktu lebih lama dari dua perkalian dan pembagian, dan saya percaya bahwa pembagian dengan konstanta apa pun dapat menghasilkan hasil yang benar dalam semua kasus menggunakan rumus yang ditunjukkan.
supercat
1

Inilah jawaban konyol yang menyenangkan:

x / 2.0 adalah tidak setara dengan x * 0,5

Katakanlah Anda menulis metode ini pada 22 Okt 2008.

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

Sekarang, 10 tahun kemudian Anda mengetahui bahwa Anda dapat mengoptimalkan potongan kode ini. Metode ini direferensikan dalam ratusan rumus di seluruh aplikasi Anda. Jadi Anda mengubahnya, dan mengalami peningkatan kinerja 5% yang luar biasa.

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

Apakah keputusan yang tepat untuk mengubah kode? Dalam matematika, kedua ekspresi tersebut memang setara. Dalam ilmu komputer, hal itu tidak selalu benar. Silakan baca Meminimalkan efek masalah akurasi untuk lebih jelasnya. Jika nilai yang Anda hitung - di beberapa titik - dibandingkan dengan nilai lain, Anda akan mengubah hasil kasus tepi. Misalnya:

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

Intinya adalah; setelah Anda puas dengan salah satu dari keduanya, maka patuhi itu!

l33t
sumber
1
Tidak suka? Bagaimana dengan komentar yang menjelaskan pemikiran Anda? Jawaban ini pasti 100% relevan.
l33t
Dalam ilmu komputer, mengalikan / membagi nilai floating point dengan pangkat 2 adalah lossless, kecuali jika nilainya menjadi tidak normal atau meluap.
Soonts
Karena floating point bukan lossless pada saat pembagian, tidak masalah jika pernyataan Anda benar. Padahal saya akan sangat terkejut jika itu terjadi.
l33t
1
"Floating point tidak lossless pada saat pembagian" hanya ketika Anda membangun dengan compiler kuno yang mengeluarkan kode x87 yang tidak digunakan lagi. Pada perangkat keras modern, hanya memiliki variabel float / double yang tidak memiliki kerugian, baik 32 atau 64 bit IEEE 754: en.wikipedia.org/wiki/IEEE_754 Karena cara kerja IEEE 754, ketika Anda membagi dengan 2 atau mengalikan dengan 0,5, Anda mengurangi eksponen dengan 1, sisa bit (tanda + mantissa) tidak berubah. Dan angka 2dan keduanya 0.5dapat direpresentasikan dalam IEEE 754 dengan tepat, tanpa kehilangan presisi (tidak seperti misalnya 0.4atau 0.1, mereka tidak bisa).
Soonts
0

Nah, jika kita berasumsi bahwa biaya operasi tambah / sublacak 1, kalikan biaya 5, dan bagi biaya sekitar 20.

matma
sumber
Dari mana Anda mendapatkan nomor-nomor ini? pengalaman? firasat? artikel di internet? bagaimana mereka akan berubah untuk tipe data yang berbeda?
kroiz
0

Setelah diskusi yang panjang dan menarik, inilah pendapat saya tentang ini: Tidak ada jawaban akhir untuk pertanyaan ini. Seperti yang ditunjukkan beberapa orang, itu tergantung pada keduanya, perangkat keras (cf piotrk dan gast128 ) dan kompiler (cf tes @Javier ). Jika kecepatan tidak penting, jika aplikasi Anda tidak perlu memproses data dalam jumlah besar secara real-time, Anda dapat memilih kejelasan menggunakan pembagian sedangkan jika kecepatan pemrosesan atau beban prosesor menjadi masalah, perkalian mungkin yang paling aman. Terakhir, kecuali Anda tahu persis pada platform apa aplikasi Anda akan diterapkan, tolok ukur tidak ada artinya. Dan untuk kejelasan kode, satu komentar sudah cukup!

Jean-François
sumber
-3

Secara teknis tidak ada yang namanya pembagian, yang ada hanyalah perkalian dengan elemen invers. Misalnya Anda tidak pernah membagi dengan 2, Anda justru mengalikannya dengan 0,5.

'Pembagian' - mari kita menipu diri sendiri bahwa itu ada sebentar - selalu lebih sulit dari perkalian itu karena untuk 'membagi' xdengan ysatu terlebih dahulu perlu menghitung nilainya y^{-1}sedemikian rupa y*y^{-1} = 1dan kemudian melakukan perkalian x*y^{-1}. Jika sudah tahu y^{-1}maka tidak menghitungnya dari ypasti ada optimasi.

satnhak
sumber
3
Yang sama sekali mengabaikan realitas kedua perintah yang ada di silikon.
NPSF3000
@ NPSF3000 - Saya tidak mengikuti. Di bawah asumsi bahwa kedua operasi itu ada, itu hanya menegaskan bahwa operasi pembagian secara implisit melibatkan penghitungan invers perkalian dan perkalian, yang akan selalu lebih sulit daripada hanya melakukan perkalian tunggal. Silikon adalah detail implementasi.
satnhak
@ BTy. Jika kedua perintah ada di silikon, dan kedua perintah mengambil jumlah siklus yang sama [seperti yang diharapkan] daripada seberapa kompleks instruksi yang sama sekali tidak relevan dari kinerja POV.
NPSF3000
@ NPSF3000 - tetapi keduanya tidak mengambil jumlah siklus yang sama karena perkalian lebih cepat.
satnhak