kode mesin x86-16 (BubbleSort int8_t), 20 19 byte
kode mesin x86-64 / 32 (JumpDownSort) 21 19 byte
Changelog:
Terima kasih kepada @ ped7g untuk ide lodsb
/ cmp [si],al
, dan menempatkan itu bersama dengan peningkatan / reset pointer yang telah saya lihat. Tidak perlu al
/ah
mari kita gunakan kode yang hampir sama untuk bilangan bulat yang lebih besar.
Algoritma baru (tetapi terkait), banyak perubahan implementasi: Bubbly SelectionSort memungkinkan implementasi x86-64 yang lebih kecil untuk byte atau kata-kata; impas pada x86-16 (byte atau kata-kata). Juga hindari bug pada ukuran = 1 yang dimiliki BubbleSort saya. Lihat di bawah.
Ternyata Sortasi Bubbly Seleksi saya dengan swap setiap kali Anda menemukan min baru sudah merupakan algoritma yang dikenal, JumpDown Sort. Disebutkan dalam Bubble Sort: An Archaeological Algorithmic Analysis (yaitu bagaimana Bubble Sort menjadi populer meskipun mengisap).
Mengurutkan bilangan bulat bertanda 8-bit di tempat . (Unsigned adalah ukuran kode yang sama, cukup ubah jge
ke a jae
). Duplikat bukan masalah. Kami bertukar menggunakan 16-bit rotate oleh 8 (dengan tujuan memori).
Bubble Sort menyebalkan untuk kinerja , tetapi saya telah membaca bahwa itu salah satu yang terkecil untuk diterapkan dalam kode mesin. Ini tampaknya benar terutama ketika ada trik khusus untuk bertukar elemen yang berdekatan. Ini adalah satu-satunya keuntungan, tetapi kadang-kadang (dalam sistem embedded kehidupan nyata) itu cukup keuntungan untuk menggunakannya untuk daftar yang sangat singkat.
Saya menghilangkan penghentian awal tanpa swap . Saya menggunakan loop BubbleSort Wikipedia "yang dioptimalkan" yang menghindari melihat n − 1
item terakhir ketika berjalan untuk yang n
ke-kali, sehingga penghitung lingkaran luar adalah batas atas untuk loop dalam.
Daftar NASM ( nasm -l /dev/stdout
), atau sumber sederhana
2 address 16-bit bubblesort16_v2:
3 machine ;; inputs: pointer in ds:si, size in in cx
4 code ;; requires: DF=0 (cld)
5 bytes ;; clobbers: al, cx=0
6
7 00000000 49 dec cx ; cx = max valid index. (Inner loop stops 1 before cx, because it loads i and i+1).
8 .outer: ; do{
9 00000001 51 push cx ; cx = inner loop counter = i=max_unsorted_idx
10 .inner: ; do{
11 00000002 AC lodsb ; al = *p++
12 00000003 3804 cmp [si],al ; compare with *p (new one)
13 00000005 7D04 jge .noswap
14 00000007 C144FF08 rol word [si-1], 8 ; swap
15 .noswap:
16 0000000B E2F5 loop .inner ; } while(i < size);
17 0000000D 59 pop cx ; cx = outer loop counter
18 0000000E 29CE sub si,cx ; reset pointer to start of array
19 00000010 E2EF loop .outer ; } while(--size);
20 00000012 C3 ret
22 00000013 size = 0x13 = 19 bytes.
push / pop di cx
sekitar loop dalam berarti berjalan dengan cx
= outer_cx ke 0.
Perhatikan bahwa rol r/m16, imm8
ini bukan instruksi 8086, itu ditambahkan kemudian (186 atau 286), tetapi ini tidak mencoba menjadi kode 8086, hanya x86 16-bit. Jika SSE4.1 phminposuw
akan membantu, saya akan menggunakannya.
Versi 32-bit ini (masih beroperasi pada bilangan bulat 8-bit tetapi dengan pointer / penghitung 32-bit) adalah 20 byte (awalan ukuran operan aktif rol word [esi-1], 8
)
Bug: size = 1 diperlakukan sebagai size = 65536, karena tidak ada yang menghentikan kita memasuki bagian luar do / while dengan cx = 0. (Anda biasanya menggunakan jcxz
untuk itu.) Tapi untungnya JumpDown Sort 19-byte adalah 19 byte dan tidak memiliki masalah itu.
Versi asli x86-16 20 byte (tanpa ide Ped7g). Dihapus untuk menghemat ruang, lihat riwayat edit untuknya dengan deskripsi.
Performa
Penyimpanan / pengisian ulang yang tumpang tindih sebagian (dalam rotasi tujuan-memori) menyebabkan kios penerusan toko pada CPU x86 modern (kecuali Atom yang dipesan). Ketika nilai tinggi menggelembung ke atas, latensi tambahan ini adalah bagian dari rantai ketergantungan yang digerakkan oleh loop. Simpan / muat ulang sucks di tempat pertama (seperti latensi 5-store-forwarding di Haswell), tetapi warung penerusan membuatnya lebih seperti 13 siklus. Eksekusi out-of-order akan mengalami kesulitan menyembunyikan ini.
Lihat juga: Stack Overflow: bubble sort untuk menyortir string untuk versi ini dengan implementasi yang sama, tetapi dengan awal ketika tidak diperlukan swap. Menggunakan xchg al, ah
/ mov [si], ax
untuk bertukar, yang 1 byte lebih panjang dan menyebabkan kios parsial-register pada beberapa CPU. (Tapi mungkin masih lebih baik daripada memori-dst rotate, yang perlu memuat nilainya lagi). Komentar saya ada beberapa saran ...
Sortir JumpDown x86-64 / x86-32, 19 byte (macam int32_t)
Dipanggil dari C menggunakan konvensi pemanggilan Sistem V x86-64 sebagai
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(nilai balik = maks (array [])).
Ini adalah https://en.wikipedia.org/wiki/Selection_sort , tetapi alih-alih mengingat posisi elemen min, tukar kandidat saat ini ke dalam array . Setelah Anda menemukan min (unsorted_region), simpan ke akhir wilayah yang disortir, seperti Urut Pilihan normal. Ini menumbuhkan wilayah yang diurutkan berdasarkan satu. (Dalam kode, rsi
arahkan ke satu melewati akhir wilayah yang disortir; lodsd
memajukannya danmov [rsi-4], eax
menyimpan min kembali ke dalamnya.)
Nama Jump Down Sort digunakan dalam Bubble Sort: An Archaeological Algorithmic Analysis . Saya kira jenis saya benar-benar semacam Jump Up, karena elemen-elemen tinggi melompat ke atas, meninggalkan bagian bawah diurutkan, bukan akhir.
Desain pertukaran ini mengarah ke bagian array yang tidak disortir dan berakhir dalam urutan sebagian besar terbalik, yang mengarah ke banyak swap di kemudian hari. (Karena Anda mulai dengan kandidat besar, dan terus melihat kandidat yang lebih rendah dan lebih rendah, sehingga Anda terus bertukar.) Saya menyebutnya "ceria" meskipun itu memindahkan elemen ke arah yang lain. Cara elemen bergerak juga sedikit seperti penyisipan mundur jenis. Untuk melihatnya beraksi, gunakan GDB display (int[12])buf
, atur breakpoint pada loop
instruksi bagian dalam , dan gunakan c
(lanjutkan). Tekan kembali untuk mengulangi. (Perintah "display" membuat GDB untuk mencetak seluruh status array setiap kali kita menekan breakpoint).
xchg
dengan mem memiliki lock
awalan implisit yang membuat ini sangat lambat. Mungkin tentang urutan besarnya lebih lambat daripada pertukaran beban / toko yang efisien; xchg m,r
adalah satu per 23c throughput pada Skylake, tetapi memuat / menyimpan / mov dengan reg tmp untuk swap (reg, mem) yang efisien dapat menggeser satu elemen per jam. Ini mungkin rasio yang lebih buruk pada CPU AMD di mana loop
instruksi cepat dan tidak akan bottleneck loop dalam sebanyak, tetapi cabang merindukan masih akan menjadi hambatan besar karena swap adalah umum (dan menjadi lebih umum ketika daerah yang tidak disortir menjadi lebih kecil) ).
2 Address ;; hybrib Bubble Selection sort
3 machine bubblyselectionsort_int32: ;; working, 19 bytes. Same size for int32 or int8
4 code ;; input: pointer in rsi, count in rcx
5 bytes ;; returns: eax = max
6
7 ;dec ecx ; we avoid this by doing edi=esi *before* lodsb, so we do redundant compares
8 ; This lets us (re)enter the inner loop even for 1 element remaining.
9 .outer:
10 ; rsi pointing at the element that will receive min([rsi]..[rsi+rcx])
11 00000000 56 push rsi
12 00000001 5F pop rdi
13 ;mov edi, esi ; rdi = min-search pointer
14 00000002 AD lodsd
16 00000003 51 push rcx ; rcx = inner counter
17 .inner: ; do {
18 ; rdi points at next element to check
19 ; eax = candidate min
20 00000004 AF scasd ; cmp eax, [rdi++]
21 00000005 7E03 jle .notmin
22 00000007 8747FC xchg [rdi-4], eax ; exchange with new min.
23 .notmin:
24 0000000A E2F8 loop .inner ; } while(--inner);
26 ; swap min-position with sorted position
27 ; eax = min. If it's not [rsi-4], then [rsi-4] was exchanged into the array somewhere
28 0000000C 8946FC mov [rsi-4], eax
29 0000000F 59 pop rcx ; rcx = outer loop counter = unsorted elements left
30 00000010 E2EE loop .outer ; } while(--unsorted);
32 00000012 C3 ret
34 00000013 13 .size: db $ - bubblyselectionsort_int32
0x13 = 19 bytes long
Ukuran kode yang sama untuk int8_t
: penggunaan lodsb
/ scasb
, AL
, dan mengubah [rsi/rdi-4]
ke-1
. Kode mesin yang sama berfungsi dalam mode 32-bit untuk elemen 8/32-bit. Mode 16-bit untuk elemen 8/16-bit perlu dibangun kembali dengan perubahan offset (dan mode pengalamatan 16-bit menggunakan pengkodean yang berbeda). Tapi masih 19 byte untuk semua.
Ini menghindari awal dec ecx
dengan membandingkan dengan elemen yang baru saja dimuat sebelum pindah. Pada iterasi terakhir dari loop luar, ia memuat elemen terakhir, memeriksa apakah itu kurang dari itu sendiri, kemudian dilakukan. Ini memungkinkannya bekerja dengan ukuran = 1, di mana BubbleSort saya gagal (memperlakukannya sebagai ukuran = 65536).
Saya menguji versi ini (dalam GDB) menggunakan penelepon ini: Coba online! . Anda dapat menjalankannya di TIO, tetapi tentu saja tidak ada debugger atau pencetakan. Namun, _start
panggilan yang keluar dengan status-keluar = elemen terbesar = 99, sehingga Anda dapat melihatnya berfungsi.
[7 2 4 1] -> [4 2 3 1]
. Juga, dapatkah daftar CSV berada di dalam kurung? Juga, format input spesifik sangat cocok untuk beberapa bahasa, dan buruk untuk yang lain. Ini membuat input mem-parsing bagian besar untuk beberapa pengiriman, dan tidak perlu untuk yang lain.