Katakanlah kita memiliki satu byte:
0110110
Menerapkan satu bithift kiri memberi kita:
1101100
Nol paling kiri digeser keluar dari byte, dan nol baru ditambahkan ke ujung kanan byte.
Bit tidak terguling; mereka dibuang. Itu berarti jika Anda meninggalkan shift 1101100 dan kemudian shift kanan, Anda tidak akan mendapatkan hasil yang sama kembali.
Pergeseran ditinggalkan oleh N setara dengan mengalikan dengan 2 N .
Menggeser kanan oleh N adalah (jika Anda menggunakan komplemen yang ) adalah sama dengan membagi dengan 2 N dan pembulatan ke nol.
Bitshifting dapat digunakan untuk perkalian dan pembagian yang sangat cepat, asalkan Anda bekerja dengan kekuatan 2. Hampir semua rutinitas grafis tingkat rendah menggunakan bitshifting.
Misalnya, pada masa lalu, kami menggunakan mode 13j (320x200 256 warna) untuk game. Dalam Mode 13h, memori video diletakkan berurutan per piksel. Itu berarti menghitung lokasi untuk piksel, Anda akan menggunakan matematika berikut:
memoryOffset = (row * 320) + column
Sekarang, pada zaman itu, kecepatan sangat penting, jadi kami akan menggunakan bithift untuk melakukan operasi ini.
Namun, 320 bukan kekuatan dua, jadi untuk mengatasi ini kita harus mencari tahu apa kekuatan dua yang ditambahkan bersama membuat 320:
(row * 320) = (row * 256) + (row * 64)
Sekarang kita bisa mengubahnya menjadi shift kiri:
(row * 320) = (row << 8) + (row << 6)
Untuk hasil akhir dari:
memoryOffset = ((row << 8) + (row << 6)) + column
Sekarang kita mendapatkan offset yang sama seperti sebelumnya, kecuali alih-alih operasi perkalian yang mahal, kita menggunakan dua bithifts ... di x86 itu akan menjadi sesuatu seperti ini (catatan, sudah selamanya sejak saya melakukan perakitan (catatan editor: dikoreksi beberapa kesalahan dan menambahkan contoh 32-bit)):
mov ax, 320; 2 cycles
mul word [row]; 22 CPU Cycles
mov di,ax; 2 cycles
add di, [column]; 2 cycles
; di = [row]*320 + [column]
; 16-bit addressing mode limitations:
; [di] is a valid addressing mode, but [ax] isn't, otherwise we could skip the last mov
Total: 28 siklus pada CPU kuno apa pun yang memiliki timing ini.
Vrs
mov ax, [row]; 2 cycles
mov di, ax; 2
shl ax, 6; 2
shl di, 8; 2
add di, ax; 2 (320 = 256+64)
add di, [column]; 2
; di = [row]*(256+64) + [column]
12 siklus pada CPU kuno yang sama.
Ya, kami akan bekerja keras untuk mengurangi 16 siklus CPU.
Dalam mode 32 atau 64-bit, kedua versi menjadi jauh lebih pendek dan lebih cepat. CPU eksekusi modern out-of-order seperti Intel Skylake (lihat http://agner.org/optimize/ ) memiliki perkalian perangkat keras yang sangat cepat (latensi rendah dan throughput tinggi), sehingga keuntungannya jauh lebih kecil. AMD Bulldozer-family sedikit lebih lambat, terutama untuk 64-bit. Pada CPU Intel, dan AMD Ryzen, dua shift latensi sedikit lebih rendah tetapi lebih banyak instruksi daripada multiply (yang dapat menyebabkan throughput lebih rendah):
imul edi, [row], 320 ; 3 cycle latency from [row] being ready
add edi, [column] ; 1 cycle latency (from [column] and edi being ready).
; edi = [row]*(256+64) + [column], in 4 cycles from [row] being ready.
vs.
mov edi, [row]
shl edi, 6 ; row*64. 1 cycle latency
lea edi, [edi + edi*4] ; row*(64 + 64*4). 1 cycle latency
add edi, [column] ; 1 cycle latency from edi and [column] both being ready
; edi = [row]*(256+64) + [column], in 3 cycles from [row] being ready.
Compiler akan melakukan ini untuk Anda: Lihat bagaimana GCC, Dentang, dan Microsoft Visual C ++ semua menggunakan shift + lea saat mengoptimalkanreturn 320*row + col;
.
Hal yang paling menarik untuk dicatat di sini adalah bahwa x86 memiliki instruksi shift-and-add ( LEA
) yang dapat melakukan shift kiri kecil dan menambahkan pada saat bersamaan, dengan kinerja sebagai add
instruksi. ARM bahkan lebih kuat: satu operan dari instruksi apa pun dapat digeser ke kiri atau kanan secara gratis. Jadi penskalaan oleh konstanta waktu-kompilasi yang dikenal sebagai kekuatan-2 bisa lebih efisien daripada perkalian.
OK, kembali ke zaman modern ... sesuatu yang lebih berguna sekarang adalah menggunakan bitshifting untuk menyimpan dua nilai 8-bit dalam integer 16-bit. Misalnya, dalam C #:
// Byte1: 11110000
// Byte2: 00001111
Int16 value = ((byte)(Byte1 >> 8) | Byte2));
// value = 000011111110000;
Dalam C ++, kompiler harus melakukan ini untuk Anda jika Anda menggunakan anggota struct
dengan dua 8-bit, tetapi dalam praktiknya mereka tidak selalu.