Mari kita lihat dua program C kecil yang melakukan sedikit pergeseran dan pembagian.
#include <stdlib.h>
int main(int argc, char* argv[]) {
int i = atoi(argv[0]);
int b = i << 2;
}
#include <stdlib.h>
int main(int argc, char* argv[]) {
int i = atoi(argv[0]);
int d = i / 4;
}
Ini kemudian masing-masing dikompilasi gcc -S
untuk melihat apa yang akan menjadi majelis yang sebenarnya.
Dengan versi pergeseran bit, dari panggilan ke atoi
untuk kembali:
callq _atoi
movl $0, %ecx
movl %eax, -20(%rbp)
movl -20(%rbp), %eax
shll $2, %eax
movl %eax, -24(%rbp)
movl %ecx, %eax
addq $32, %rsp
popq %rbp
ret
Sementara versi bagi:
callq _atoi
movl $0, %ecx
movl $4, %edx
movl %eax, -20(%rbp)
movl -20(%rbp), %eax
movl %edx, -28(%rbp) ## 4-byte Spill
cltd
movl -28(%rbp), %r8d ## 4-byte Reload
idivl %r8d
movl %eax, -24(%rbp)
movl %ecx, %eax
addq $32, %rsp
popq %rbp
ret
Hanya dengan melihat ini ada beberapa instruksi lebih banyak dalam versi membagi dibandingkan dengan pergeseran bit.
Kuncinya adalah apa yang mereka lakukan?
Dalam versi bit shift, instruksi kuncinya adalah shll $2, %eax
shift kiri yang logis - ada pembagiannya, dan yang lainnya hanya memindahkan nilai.
Dalam versi membagi, Anda dapat melihat idivl %r8d
- tetapi tepat di atas itu adalah cltd
(konversi panjang menjadi dua kali lipat) dan beberapa logika tambahan di sekitar tumpahan dan muat ulang. Pekerjaan tambahan ini, mengetahui bahwa kita berurusan dengan matematika daripada bit sering diperlukan untuk menghindari berbagai kesalahan yang dapat terjadi dengan hanya melakukan sedikit matematika.
Mari kita lakukan beberapa perkalian cepat:
#include <stdlib.h>
int main(int argc, char* argv[]) {
int i = atoi(argv[0]);
int b = i >> 2;
}
#include <stdlib.h>
int main(int argc, char* argv[]) {
int i = atoi(argv[0]);
int d = i * 4;
}
Daripada melewati semua ini, ada satu baris yang berbeda:
$ diff mult.s bit.s
24c24
> shll $ 2,% eax
---
<sarl $ 2,% eax
Di sini kompiler dapat mengidentifikasi bahwa matematika dapat dilakukan dengan pergeseran, namun alih-alih perubahan logis itu melakukan perubahan aritmatika. Perbedaan antara ini akan jelas jika kita menjalankan ini - sarl
mempertahankan tanda. Sehingga -2 * 4 = -8
sementara shll
itu tidak.
Mari kita lihat ini dalam skrip perl cepat:
#!/usr/bin/perl
$foo = 4;
print $foo << 2, "\n";
print $foo * 4, "\n";
$foo = -4;
print $foo << 2, "\n";
print $foo * 4, "\n";
Keluaran:
16
16
18446744073709551600
-16
Um ... -4 << 2
adalah 18446744073709551600
yang tidak persis seperti yang Anda harapkan saat berhadapan dengan perkalian dan pembagian. Benar, tapi itu bukan perkalian bilangan bulat.
Dan dengan demikian waspada terhadap optimasi prematur. Biarkan kompiler mengoptimalkan untuk Anda - ia tahu apa yang sebenarnya Anda coba lakukan dan kemungkinan akan melakukan pekerjaan yang lebih baik, dengan lebih sedikit bug.