Saya pikir akan lebih membantu bagi si penanya untuk memiliki jawaban yang lebih berbeda, karena saya melihat beberapa asumsi yang tidak diteliti dalam pertanyaan dan dalam beberapa jawaban atau komentar.
Runtime relatif yang dihasilkan dari pergeseran dan perkalian tidak ada hubungannya dengan C. Ketika saya mengatakan C, saya tidak bermaksud contoh dari implementasi spesifik, seperti itu atau itu versi GCC, tetapi bahasa. Saya tidak bermaksud mengambil iklan absurdum ini, tetapi menggunakan contoh ekstrem untuk ilustrasi: Anda dapat menerapkan kompiler C yang sepenuhnya memenuhi standar dan penggandaan membutuhkan waktu satu jam, sementara menggeser membutuhkan milidetik - atau sebaliknya. Saya tidak mengetahui adanya pembatasan kinerja seperti itu di C atau C ++.
Anda mungkin tidak peduli dengan teknis ini dalam berargumentasi. Niat Anda mungkin hanya untuk menguji kinerja relatif dari melakukan shift versus multiplikasi dan Anda memilih C, karena itu umumnya dianggap sebagai bahasa pemrograman tingkat rendah, sehingga orang dapat mengharapkan kode sumbernya untuk menerjemahkan ke dalam instruksi yang sesuai lebih langsung. Pertanyaan-pertanyaan semacam itu sangat umum dan saya pikir jawaban yang baik harus menunjukkan bahwa bahkan dalam C kode sumber Anda tidak diterjemahkan ke dalam instruksi secara langsung seperti yang Anda pikirkan dalam contoh yang diberikan. Saya telah memberi Anda beberapa hasil kompilasi di bawah ini.
Di sinilah komentar yang mempertanyakan kegunaan pengganti kesetaraan ini dalam perangkat lunak dunia nyata masuk. Anda dapat melihat beberapa di komentar untuk pertanyaan Anda, seperti yang dari Eric Lippert. Ini sejalan dengan reaksi yang biasanya Anda dapatkan dari insinyur yang lebih berpengalaman dalam menanggapi optimasi tersebut. Jika Anda menggunakan pergeseran biner dalam kode produksi sebagai alat selimut untuk mengalikan dan membagi, orang kemungkinan besar akan merasa ngeri pada kode Anda dan memiliki beberapa tingkat reaksi emosional ("Saya telah mendengar klaim tidak masuk akal yang dibuat tentang JavaScript demi Tuhan.") Untuk itu mungkin tidak masuk akal bagi programmer pemula, kecuali mereka lebih memahami alasan untuk reaksi tersebut.
Alasan-alasan tersebut terutama merupakan kombinasi dari penurunan keterbacaan dan kesia-siaan dari optimasi tersebut, karena Anda mungkin sudah mengetahui dengan membandingkan kinerja relatif mereka. Namun, saya tidak berpikir bahwa orang akan memiliki reaksi yang kuat jika penggantian shift untuk multiplikasi adalah satu-satunya contoh optimasi tersebut. Pertanyaan seperti milik Anda sering kali muncul dalam berbagai bentuk dan dalam berbagai konteks. Saya pikir apa yang benar-benar bereaksi oleh insinyur senior dengan sangat kuat, setidaknya saya miliki pada saat itu, adalah bahwa ada potensi bahaya yang jauh lebih luas ketika orang-orang menggunakan optimisasi mikro semacam itu secara bebas di seluruh basis kode. Jika Anda bekerja di perusahaan seperti Microsoft pada basis kode besar, Anda akan menghabiskan banyak waktu membaca kode sumber insinyur lain, atau berusaha menemukan kode tertentu di dalamnya. Bahkan mungkin kode Anda sendiri yang akan Anda coba masuk akal dalam beberapa tahun ke depan, terutama pada beberapa waktu yang paling tidak tepat, seperti ketika Anda harus memperbaiki pemadaman produksi setelah panggilan yang Anda terima pada pager tugas pada hari Jumat malam, akan keluar untuk bersenang-senang dengan teman-teman ... Jika Anda menghabiskan banyak waktu untuk membaca kode, Anda akan menghargai itu karena dapat dibaca sebagai mungkin. Bayangkan membaca novel favorit Anda, tetapi penerbit telah memutuskan untuk merilis edisi baru di mana mereka menggunakan abbrv. semua ovr th plc bcs thnk itu svs spc. Itu mirip dengan reaksi insinyur lain mungkin harus kode Anda, jika Anda menaburkannya dengan optimasi seperti itu. Seperti yang telah ditunjukkan oleh jawaban lain, lebih baik jelaskan apa yang Anda maksudkan,
Bahkan dalam lingkungan itu, Anda mungkin menemukan diri Anda memecahkan pertanyaan wawancara di mana Anda diharapkan mengetahui hal ini atau kesetaraan lainnya. Mengetahui mereka tidak buruk dan seorang insinyur yang baik akan menyadari efek aritmatika dari pergeseran biner. Perhatikan bahwa saya tidak mengatakan bahwa ini membuat insinyur yang baik, tetapi insinyur yang baik akan tahu, menurut pendapat saya. Secara khusus, Anda mungkin masih menemukan beberapa manajer, biasanya menjelang akhir wawancara Anda, yang akan menyeringai lebar pada Anda dalam mengantisipasi kegembiraan untuk mengungkapkan "trik" rekayasa pintar ini kepada Anda dalam pertanyaan koding dan membuktikan bahwa dia , juga, dulu atau merupakan salah satu insinyur yang cerdas dan bukan "hanya" seorang manajer. Dalam situasi itu, cobalah untuk terlihat terkesan dan berterima kasih padanya untuk wawancara yang mencerahkan.
Mengapa Anda tidak melihat perbedaan kecepatan dalam C? Jawaban yang paling mungkin adalah keduanya menghasilkan kode perakitan yang sama:
int shift(int i) { return i << 2; }
int multiply(int i) { return i * 2; }
Dapat dikompilasi menjadi keduanya
shift(int):
lea eax, [0+rdi*4]
ret
Pada GCC tanpa optimasi, yaitu menggunakan flag "-O0", Anda mungkin mendapatkan ini:
shift(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
sal eax, 2
pop rbp
ret
multiply(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, eax
pop rbp
ret
Seperti yang Anda lihat, meneruskan "-O0" ke GCC tidak berarti bahwa tidak akan terlalu pintar tentang jenis kode apa yang dihasilkannya. Secara khusus, perhatikan bahwa bahkan dalam kasus ini kompiler menghindari penggunaan instruksi penggandaan. Anda dapat mengulangi percobaan yang sama dengan menggeser dengan angka lain dan bahkan mengalikan dengan angka yang bukan kekuatan dua. Kemungkinannya adalah bahwa pada platform Anda, Anda akan melihat kombinasi shift dan penambahan, tetapi tidak ada multiplikasi. Sepertinya sedikit kebetulan bagi kompiler untuk menghindari penggunaan perkalian dalam semua kasus jika perkalian dan pergeseran benar-benar memiliki biaya yang sama, bukan? Tetapi saya tidak bermaksud memberikan anggapan untuk bukti, jadi mari kita beralih.
Anda dapat menjalankan kembali pengujian dengan kode di atas dan melihat apakah Anda melihat perbedaan kecepatan sekarang. Meskipun demikian, Anda tidak menguji shift versus multiply, seperti yang dapat Anda lihat dengan tidak adanya multiplikasi, tetapi kode yang dihasilkan dengan serangkaian flag oleh GCC untuk operasi C shift dan dikalikan dalam contoh tertentu . Jadi, dalam tes lain Anda dapat mengedit kode perakitan dengan tangan dan alih-alih menggunakan instruksi "imul" dalam kode untuk metode "multiply".
Jika Anda ingin mengalahkan beberapa kecerdasan dari kompiler, Anda bisa mendefinisikan metode shift dan multiply yang lebih umum dan akan berakhir dengan sesuatu seperti ini:
int shift(int i, int j) { return i << j; }
int multiply(int i, int j) { return i * j; }
Yang dapat menghasilkan kode perakitan berikut:
shift(int, int):
mov eax, edi
mov ecx, esi
sal eax, cl
ret
multiply(int, int):
mov eax, edi
imul eax, esi
ret
Di sini kita akhirnya memiliki, bahkan pada tingkat optimisasi tertinggi dari GCC 4.9, ekspresi dalam instruksi perakitan yang mungkin Anda harapkan ketika Anda awalnya memulai tes Anda. Saya pikir itu sendiri dapat menjadi pelajaran penting dalam optimasi kinerja. Kita dapat melihat perbedaan yang dibuat untuk mengganti variabel untuk konstanta konkret dalam kode kita, dalam hal kecerdasan yang dapat diterapkan oleh kompiler. Optimalisasi mikro seperti penggantian shift-multiply adalah beberapa optimasi tingkat sangat rendah yang biasanya mudah dilakukan oleh kompiler. Optimalisasi lain yang jauh lebih berdampak pada kinerja memerlukan pemahaman maksud kodeyang sering tidak dapat diakses oleh kompiler atau hanya dapat ditebak oleh beberapa heuristik. Di situlah Anda sebagai insinyur perangkat lunak datang dan tentu saja biasanya tidak melibatkan penggantian perkalian dengan shift. Ini melibatkan faktor-faktor seperti menghindari panggilan berlebihan ke layanan yang menghasilkan I / O dan dapat memblokir suatu proses. Jika Anda pergi ke hard disk Anda atau, semoga saja, ke basis data jauh untuk beberapa data tambahan yang bisa Anda peroleh dari apa yang sudah Anda miliki dalam memori, waktu yang Anda habiskan lebih lama daripada pelaksanaan sejuta instruksi. Sekarang, saya pikir kami telah menyimpang agak jauh dari pertanyaan awal Anda, tetapi saya pikir menunjukkan hal ini kepada penanya, terutama jika kita mengira seseorang yang baru mulai memahami terjemahan dan pelaksanaan kode,
Jadi, mana yang lebih cepat? Saya pikir ini adalah pendekatan yang baik yang Anda pilih untuk benar-benar menguji perbedaan kinerja. Secara umum, mudah untuk dikejutkan oleh kinerja runtime dari beberapa perubahan kode. Ada banyak teknik yang digunakan prosesor modern dan interaksi antara perangkat lunak juga bisa rumit. Bahkan jika Anda harus mendapatkan hasil kinerja yang menguntungkan untuk perubahan tertentu dalam satu situasi, saya pikir berbahaya untuk menyimpulkan bahwa jenis perubahan ini akan selalu menghasilkan manfaat kinerja. Saya pikir itu berbahaya untuk menjalankan tes seperti itu sekali, katakan, "Oke, sekarang saya tahu mana yang lebih cepat!" dan kemudian tanpa pandang bulu menerapkan optimasi yang sama ke kode produksi tanpa mengulangi pengukuran Anda.
Lalu bagaimana jika shift lebih cepat dari pada multiplikasi? Tentu saja ada indikasi mengapa itu benar. GCC, seperti yang Anda lihat di atas, tampaknya berpikir (bahkan tanpa optimasi) bahwa menghindari perkalian langsung yang mendukung instruksi lain adalah ide yang bagus. The Intel 64 dan IA-32 manual Arsitektur Optimasi Reference akan memberikan gambaran tentang biaya relatif instruksi CPU. Sumber lain, yang lebih fokus pada latensi dan throughput pengajaran, adalah http://www.agner.org/optimize/instruction_tables.pdf. Perhatikan bahwa mereka bukan prediktor runtime absolut yang baik, tetapi kinerja instruksi relatif satu sama lain. Dalam loop yang ketat, saat tes Anda disimulasikan, metrik "throughput" harus paling relevan. Ini adalah jumlah siklus yang biasanya diikat oleh unit eksekusi ketika menjalankan instruksi yang diberikan.
Jadi bagaimana jika shift TIDAK lebih cepat dari perkalian? Seperti yang saya katakan di atas, arsitektur modern bisa sangat kompleks dan hal-hal seperti prediksi cabang, caching, pipelining, dan unit eksekusi paralel dapat mempersulit untuk memprediksi kinerja relatif dari dua bagian kode yang setara secara logis pada suatu waktu. Saya benar-benar ingin menekankan hal ini, karena di sinilah saya tidak senang dengan sebagian besar jawaban untuk pertanyaan-pertanyaan seperti ini dan dengan sekumpulan orang langsung mengatakan bahwa tidak benar (lagi) bahwa bergeser lebih cepat daripada perkalian.
Tidak, sejauh yang saya tahu kami tidak menemukan saus rekayasa rahasia pada tahun 1970-an atau kapan pun untuk tiba-tiba membatalkan selisih biaya unit penggandaan dan sedikit shifter. Perkalian umum, dalam hal gerbang logis, dan tentu saja dalam hal operasi logis, masih lebih kompleks daripada pergeseran dengan shifter barel di banyak skenario, di banyak arsitektur. Bagaimana ini diterjemahkan ke dalam runtime keseluruhan pada komputer desktop mungkin agak buram. Saya tidak tahu pasti bagaimana mereka diimplementasikan dalam prosesor tertentu, tetapi di sini adalah penjelasan dari suatu perkalian: Apakah perkalian bilangan bulat benar-benar kecepatan yang sama seperti penambahan pada CPU modern
Sementara di sini ada penjelasan tentang Barrel Shifter . Dokumen-dokumen yang saya rujuk pada paragraf sebelumnya memberikan pandangan lain tentang biaya operasi relatif, dengan proksi instruksi CPU. Para insinyur di Intel tampaknya sering mendapatkan pertanyaan serupa: siklus clock forum pengembang zona intel untuk multiplikasi bilangan bulat dan penambahan prosesor core 2 duo
Ya, dalam sebagian besar skenario kehidupan nyata, dan hampir pasti dalam JavaScript, upaya untuk mengeksploitasi kesetaraan ini demi kinerja mungkin merupakan usaha yang sia-sia. Namun, bahkan jika kami memaksakan penggunaan instruksi perkalian dan kemudian tidak melihat perbedaan dalam run-time, itu lebih disebabkan oleh sifat metrik biaya yang kami gunakan, tepatnya, dan bukan karena tidak ada perbedaan biaya. Runtime end-to-end adalah satu metrik dan jika itu satu-satunya yang kami pedulikan, semuanya baik-baik saja. Tetapi itu tidak berarti bahwa semua perbedaan biaya antara multiplikasi dan pergeseran hilang begitu saja. Dan saya pikir tentu bukan ide yang baik untuk menyampaikan ide itu kepada penanya, baik secara implisit atau tidak, yang jelas-jelas baru mulai mendapatkan ide tentang faktor-faktor yang terlibat dalam run-time dan biaya kode modern. Rekayasa selalu tentang pertukaran. Pertanyaan dan penjelasan tentang apa pengorbanan prosesor modern telah dibuat untuk menunjukkan waktu eksekusi yang kita sebagai pengguna akhirnya melihat dapat menghasilkan jawaban yang lebih berbeda. Dan saya pikir jawaban yang lebih terdiferensiasi daripada "ini tidak benar lagi" dijamin jika kita ingin melihat lebih sedikit insinyur memeriksa kode yang dioptimalkan secara mikro menghapus keterbacaan, karena dibutuhkan pemahaman yang lebih umum tentang sifat "optimisasi" tersebut untuk lihat beragamnya inkarnasi yang beragam daripada sekadar menyebut beberapa contoh spesifik sebagai ketinggalan zaman.