Código de máquina x86-16 (BubbleSort int8_t), 20 19 bytes
Código de máquina x86-64 / 32 (JumpDownSort) 21 19 bytes
Registro de cambios:
Gracias a @ ped7g por el lodsb
/cmp [si],al
idea , y poner eso junto con un incremento / reinicio del puntero que había estado mirando. No necesitar al
/ ah
nos permite usar casi el mismo código para enteros más grandes.
Nuevo algoritmo (pero relacionado), muchos cambios de implementación: Bubbly SelectionSort permite una implementación x86-64 más pequeña para bytes o palabras clave; punto de equilibrio en x86-16 (bytes o palabras). También evita el error en size = 1 que tiene mi BubbleSort. Vea abajo.
Resulta que mi selección de selección burbujeante con intercambios cada vez que encuentras un nuevo min ya es un algoritmo conocido, JumpDown Sort. Se menciona en Bubble Sort: An Archaeological Algorithmic Analysis (es decir, cómo Bubble Sort se hizo popular a pesar de la succión).
Ordena enteros con signo de 8 bits en el lugar . (Sin signo es el mismo tamaño de código, simplemente cambie el jge
ajae
). Los duplicados no son un problema. Intercambiamos usando una rotación de 16 bits por 8 (con un destino de memoria).
Bubble Sort es una mierda para el rendimiento , pero he leído que es uno de los más pequeños para implementar en código máquina. Esto parece especialmente cierto cuando hay trucos especiales para intercambiar elementos adyacentes. Esta es prácticamente su única ventaja, pero a veces (en sistemas integrados de la vida real) es suficiente ventaja para usarla en listas muy cortas.
Omití la terminación anticipada de permutas . Utilicé el bucle BubbleSort "optimizado" de Wikipedia que evita mirar los últimos n − 1
elementos cuando se ejecuta por n
enésima vez, por lo que el contador del bucle externo es el límite superior del bucle interno.
Listado NASM ( nasm -l /dev/stdout
) o fuente simple
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 de cx
alrededor del bucle interno significa que se ejecuta con cx
= external_cx hasta 0.
Tenga en cuenta que rol r/m16, imm8
no es una instrucción 8086, se agregó más tarde (186 o 286), pero esto no está tratando de ser un código 8086, solo x86 de 16 bits. Si SSE4.1phminposuw
ayudara, lo usaría.
Una versión de 32 bits de esto (todavía opera en enteros de 8 bits pero con punteros / contadores de 32 bits) es de 20 bytes (prefijo de tamaño de operando en rol word [esi-1], 8
)
Error: size = 1 se trata como size = 65536, porque nada nos impide ingresar al do / while externo con cx = 0. (Normalmente lo usaría jcxz
para eso). Pero afortunadamente, el JumpDown Sort de 19 bytes tiene 19 bytes y no tiene ese problema.
Versión original x86-16 de 20 bytes (sin la idea de Ped7g). Omitido para ahorrar espacio, vea el historial de edición con una descripción.
Actuación
El almacenamiento / recarga parcialmente superpuestos (en rotación de destino de memoria) provoca un bloqueo de reenvío de almacenamiento en CPU modernas x86 (excepto Atom en orden). Cuando un valor alto está burbujeando hacia arriba, esta latencia adicional es parte de una cadena de dependencia transportada en bucle. Almacenar / recargar apesta en primer lugar (como latencia de reenvío de 5 ciclos en Haswell), pero una pérdida de reenvío lo lleva a más de 13 ciclos. La ejecución fuera de orden tendrá problemas para ocultar esto.
Ver también: Desbordamiento de pila: clasificación de burbujas para ordenar cadenas para una versión de esto con una implementación similar, pero con una salida anticipada cuando no se necesitan intercambios. Utiliza xchg al, ah
/ mov [si], ax
para el intercambio, que es 1 byte más largo y provoca un bloqueo de registro parcial en algunas CPU. (Pero aún puede ser mejor que la memoria dst rotar, que necesita cargar el valor nuevamente). Mi comentario allí tiene algunas sugerencias ...
Ordenación JumpDown x86-64 / x86-32, 19 bytes (ordena int32_t)
Se puede llamar desde C usando la convención de llamadas del sistema V x86-64 como
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(valor de retorno = max (array [])).
Esto es https://en.wikipedia.org/wiki/Selection_sort , pero en lugar de recordar la posición del elemento min, intercambie el candidato actual en la matriz . Una vez que haya encontrado el min (unsorted_region), guárdelo hasta el final de la región ordenada, como Ordenar por selección normal. Esto aumenta la región ordenada por uno. (En el código, rsi
apunta a una pasada el final de la región ordenada; lodsd
avanza ymov [rsi-4], eax
almacena el min de nuevo en él).
El nombre Jump Down Sort se usa en Bubble Sort: An Archaeological Algorithmic Analysis . Supongo que mi tipo es realmente un tipo Jump Up, porque los elementos altos saltan hacia arriba, dejando el fondo ordenado, no el final.
Este diseño de intercambio lleva a que la parte no ordenada de la matriz termine en su mayoría en orden inverso, lo que lleva a muchos intercambios más adelante. (Debido a que comienza con un candidato grande, y sigue viendo candidatos cada vez más bajos, por lo que sigue intercambiando). Lo llamé "burbujeante" a pesar de que mueve elementos en la otra dirección. La forma en que mueve los elementos también es un poco como una clasificación de inserción hacia atrás. Para verlo en acción, use GDB's display (int[12])buf
, establezca un punto de interrupción en la loop
instrucción interna y usec
(continuar). Presione volver para repetir. (El comando "display" hace que GDB imprima todo el estado de la matriz cada vez que alcanzamos el punto de interrupción).
xchg
with mem tiene un lock
prefijo implícito que hace que esto sea más lento. Probablemente un orden de magnitud más lento que un intercambio eficiente de carga / almacenamiento; xchg m,r
es uno por rendimiento de 23c en Skylake, pero cargar / almacenar / mover con un tmp reg para un intercambio eficiente (reg, mem) puede cambiar un elemento por reloj. Podría ser una relación peor en una CPU AMD donde elloop
instrucción es rápida y no obstaculizaría tanto el bucle interno, pero las fallas de ramificación seguirán siendo un gran cuello de botella porque los intercambios son comunes (y se vuelven más comunes a medida que la región no clasificada se vuelve más pequeña) )
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
El tamaño del código mismo para int8_t
: uso lodsb
/ scasb
, AL
y el cambio de la[rsi/rdi-4]
a-1
. El mismo código de máquina funciona en modo de 32 bits para elementos de 8/32 bits. El modo de 16 bits para elementos de 8/16 bits debe reconstruirse con los desplazamientos modificados (y los modos de direccionamiento de 16 bits usan una codificación diferente). Pero aún así 19 bytes para todos.
Evita la inicial dec ecx
al comparar con el elemento que acaba de cargar antes de continuar. En la última iteración del bucle externo, carga el último elemento, comprueba si es menor que sí mismo, y luego está listo. Esto le permite trabajar con size = 1, donde mi BubbleSort falla (lo trata como size = 65536).
Probé esta versión (en GDB) usando esta llamada: ¡ Pruébelo en línea! . Puede ejecutarlo en TIO, pero por supuesto no hay depurador ni impresión. Aún así, el _start
que lo llama sale con estado de salida = elemento más grande = 99, por lo que puede ver que funciona.
[7 2 4 1] -> [4 2 3 1]
. Además, ¿puede la lista CSV estar entre paréntesis? Además, el formato de entrada específico es muy adecuado para algunos idiomas y malo para otros. Esto hace que el análisis de entrada sea una gran parte para algunos envíos e innecesario para otros.