codice macchina x86-16 (BubbleSort int8_t), 20 19 byte
codice macchina x86-64 / 32 (JumpDownSort) 21 19 byte
changelog:
Grazie a @ ped7g per l' idea lodsb
/ cmp [si],al
e mettendo insieme un incremento / reset del puntatore che stavo guardando. Non necessario al
/ah
serve ci permette di usare quasi lo stesso codice per numeri interi più grandi.
Nuovo algoritmo (ma correlato), molte modifiche all'implementazione: Bubbly SelectionSort consente un'implementazione x86-64 più piccola per byte o password; break-even su x86-16 (byte o parole). Evita anche il bug su size = 1 del mio BubbleSort. Vedi sotto.
Si scopre che il mio ordinamento di selezione frizzante con swap ogni volta che trovi un nuovo min è già un algoritmo noto, JumpDown Sort. È menzionato in Bubble Sort: An Archaeological Algorithmic Analysis (ovvero come ha fatto Bubble Sort a diventare popolare nonostante il succhiare).
Ordina sul posto gli interi con segno a 8 bit . (Unsigned ha le stesse dimensioni del codice, basta cambiare jge
in a jae
). I duplicati non sono un problema. Scambiamo usando una rotazione di 16 bit di 8 (con una destinazione di memoria).
Bubble Sort fa schifo per le prestazioni , ma ho letto che è uno dei più piccoli da implementare nel codice macchina. Questo sembra particolarmente vero quando ci sono trucchi speciali per scambiare elementi adiacenti. Questo è praticamente il suo unico vantaggio, ma a volte (nei sistemi embedded nella vita reale) è abbastanza vantaggio per usarlo per elenchi molto brevi.
Ho omesso la risoluzione anticipata senza swap . Ho usato il ciclo BubbleSort "ottimizzato" di Wikipedia che evita di guardare gli ultimi n − 1
elementi quando corro pern
nona volta, quindi il contatore del loop esterno è il limite superiore per il loop interno.
Elenco NASM ( nasm -l /dev/stdout
) o semplice fonte
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
attorno al ciclo interno significa che funziona con cx
= outer_cx fino a 0.
Nota che rol r/m16, imm8
non è un'istruzione 8086, è stata aggiunta in seguito (186 o 286), ma questo non sta cercando di essere codice 8086, solo x86 a 16 bit. Se SSE4.1 phminposuw
fosse d'aiuto, lo userei.
Una versione a 32 bit di questo (ancora funzionante su numeri interi a 8 bit ma con puntatori / contatori a 32 bit) è di 20 byte (prefisso di dimensione dell'operando su rol word [esi-1], 8
)
Bug: size = 1 viene trattato come size = 65536, perché nulla ci impedisce di entrare nel do esterno / mentre con cx = 0. (Normalmente lo jcxz
useresti per quello.) Ma per fortuna il JumpDown Sort a 19 byte è di 19 byte e non ha questo problema.
Versione originale x86-16 20 byte (senza l'idea di Ped7g). Omesso per risparmiare spazio, vedere la cronologia delle modifiche con una descrizione.
Prestazione
Store / ricaricare parzialmente sovrapposti (ruotando la destinazione della memoria) provoca uno stallo di inoltro del negozio su moderne CPU x86 (tranne Atom in ordine). Quando un valore elevato gorgoglia verso l'alto, questa latenza aggiuntiva fa parte di una catena di dipendenze trasportata da loop. Conservare / ricaricare fa schifo in primo luogo (come la latenza di inoltro del magazzino a 5 cicli su Haswell), ma una stalla di inoltro lo porta a più di 13 cicli. L'esecuzione fuori ordine avrà difficoltà a nasconderlo.
Vedi anche: Stack Overflow: ordinamento a bolle per l'ordinamento della stringa per una versione di questo con un'implementazione simile, ma con un inizio anticipato quando non sono necessari scambi. Utilizza xchg al, ah
/ mov [si], ax
per lo scambio, che è più lungo di 1 byte e provoca uno stallo del registro parziale su alcune CPU. (Ma potrebbe comunque essere meglio di memory-dst rotate, che deve caricare nuovamente il valore). Il mio commento ha alcuni suggerimenti ...
x86-64 / x86-32 Ordinamento JumpDown, 19 byte (ordina int32_t)
Richiamabile da C utilizzando la convenzione di chiamata System V x86-64 come
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(valore di ritorno = max (array [])).
Questo è https://en.wikipedia.org/wiki/Selection_sort , ma invece di ricordare la posizione dell'elemento min, scambiare il candidato corrente nella matrice . Dopo aver trovato il minimo (unsorted_region), memorizzalo alla fine della regione ordinata, come il normale ordinamento per selezione. Questo aumenta la regione ordinata di uno. (Nel codice, rsi
punta a uno oltre la fine della regione ordinata; lo fa lodsd
avanzare e mov [rsi-4], eax
memorizza il min al suo interno.)
Il nome Jump Down Sort è utilizzato in Bubble Sort: An Archaeological Algorithmic Analysis . Immagino che il mio tipo sia davvero un tipo Jump Up, perché gli elementi alti saltano verso l'alto, lasciando il fondo ordinato, non la fine.
Questo design di scambio porta alla parte non ordinata dell'array che finisce in un ordine per lo più in ordine inverso, portando a molti swap in seguito. (Perché inizi con un grande candidato e continui a vedere candidati sempre più bassi, quindi continui a scambiarti.) L'ho chiamato "frizzante" anche se sposta gli elementi nella direzione opposta. Il modo in cui sposta gli elementi è anche un po 'come una sorta di inserzione all'indietro. Per vederlo in azione, usa GDB display (int[12])buf
, imposta un punto di interruzione sull'istruzione interna loop
e usa c
(continua). Premi Invio per ripetere. (Il comando "display" consente a GDB di stampare l'intero stato dell'array ogni volta che raggiungiamo il punto di interruzione).
xchg
con mem ha un lock
prefisso implicito che rende questo extra lento. Probabilmente circa un ordine di grandezza più lento di uno scambio carico / magazzino efficiente; xchg m,r
è uno per 23c throughput su Skylake, ma caricare / memorizzare / mov con un tmp reg per uno scambio efficiente (reg, mem) può spostare un elemento per clock. Potrebbe essere un rapporto peggiore su una CPU AMD in cui le loop
istruzioni sono veloci e non rallenterebbero troppo il ciclo interno, ma i fallimenti del ramo saranno comunque un grosso collo di bottiglia perché gli swap sono comuni (e diventano più comuni quando la regione non ordinata si riduce ).
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
Dimensioni del codice stesso per int8_t
: utilizzo lodsb
/ scasb
, AL
e modificare l' [rsi/rdi-4]
a-1
. Lo stesso codice macchina funziona in modalità 32 bit per elementi a 8/32 bit. La modalità a 16 bit per gli elementi a 8/16 bit deve essere ricostruita con gli offset modificati (e le modalità di indirizzamento a 16 bit utilizzano una codifica diversa). Ma ancora 19 byte per tutti.
Evita l'inizializzazione dec ecx
confrontandola con l'elemento appena caricato prima di proseguire. Nell'ultima iterazione del ciclo esterno, carica l'ultimo elemento, controlla se è inferiore a se stesso, quindi viene eseguito. Questo gli permette di lavorare con size = 1, dove il mio BubbleSort fallisce (lo tratta come size = 65536).
Ho provato questa versione (in GDB) usando questo chiamante: provalo online! . Puoi eseguirlo su TIO, ma ovviamente nessun debugger o stampa. Tuttavia, quello _start
che lo chiama esce con exit-status = più grande elemento = 99, quindi puoi vedere che funziona.
[7 2 4 1] -> [4 2 3 1]
. Inoltre, l'elenco CSV può essere racchiuso tra parentesi? Inoltre, il formato di input specifico è molto adatto per alcune lingue e male per altre. Ciò rende l'analisi dell'analisi una parte importante di alcuni invii e non necessaria per altri.