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
Changelog:
Agradeço a @ ped7g pela ideia lodsb
/ cmp [si],al
e a juntar isso com um incremento / redefinição de ponteiro que eu estava olhando. Não precisamos al
/ ah
vamos usar quase o mesmo código para números inteiros maiores.
Novo algoritmo (mas relacionado), muitas alterações na implementação: O Bubbly SelectionSort permite uma implementação menor do x86-64 para bytes ou dwords; ponto de equilíbrio em x86-16 (bytes ou palavras). Também evita o erro no tamanho = 1 que meu BubbleSort possui. Ver abaixo.
Acontece que meu Bubbly Selection Sort com swaps toda vez que você encontra um novo min já é um algoritmo conhecido, JumpDown Sort. É mencionado em Bubble Sort: Uma Análise Algorítmica Arqueológica (ou seja, como o Bubble Sort se tornou popular apesar da sucção).
Classifica inteiros assinados de 8 bits no local . (Não assinado é do mesmo tamanho de código, basta alterar jge
para a jae
). Duplicatas não são um problema. Trocamos usando uma rotação de 16 bits por 8 (com um destino de memória).
O Bubble Sort é péssimo para desempenho , mas eu li que é um dos menores implementados em código de máquina. Isso parece especialmente verdadeiro quando há truques especiais para trocar elementos adjacentes. Essa é praticamente sua única vantagem, mas às vezes (em sistemas embarcados na vida real) é vantagem suficiente para usá-lo em listas muito curtas.
Omiti a rescisão antecipada sem trocas . Eu usei o loop BubbleSort "otimizado" da Wikipedia, que evita olhar os últimos n − 1
itens durante a execução pela n
quinta vez, de modo que o contador de loop externo é o limite superior do loop interno.
Listagem NASM ( nasm -l /dev/stdout
) ou fonte simples
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 cx
ao redor do loop interno significa que ele roda com cx
= outer_cx até 0.
Observe que rol r/m16, imm8
não é uma instrução 8086, foi adicionada posteriormente (186 ou 286), mas isso não está tentando ser o código 8086, apenas x86 de 16 bits. Se o SSE4.1 phminposuw
ajudasse, eu o usaria.
Uma versão de 32 bits disso (ainda operando em números inteiros de 8 bits, mas com ponteiros / contadores de 32 bits) tem 20 bytes (prefixo do tamanho do operando ativado rol word [esi-1], 8
)
Bug: size = 1 é tratado como size = 65536, porque nada nos impede de entrar no fazer externo / enquanto com cx = 0. (Você normalmente usaria jcxz
para isso.) Mas, felizmente, a Classificação JumpDown de 19 bytes tem 19 bytes e não tem esse problema.
Versão original de 20 bytes x86-16 (sem a idéia de Ped7g). Omitido para economizar espaço, consulte o histórico de edições com uma descrição.
atuação
O armazenamento / recarregamento parcialmente sobreposto (na rotação do destino da memória) causa uma interrupção do encaminhamento de loja nas CPUs x86 modernas (exceto Atom em ordem). Quando um valor alto está subindo, essa latência extra faz parte de uma cadeia de dependência transportada por loop. Armazenar / recarregar é uma porcaria em primeiro lugar (como latência de encaminhamento de loja de 5 ciclos em Haswell), mas uma paralisação de encaminhamento leva a mais de 13 ciclos. A execução fora de ordem terá problemas para ocultar isso.
Consulte também: Estouro de pilha: classificação de bolha para classificar sequência de caracteres para uma versão disso com uma implementação semelhante, mas com uma saída antecipada quando não são necessários swaps. Ele usa xchg al, ah
/ mov [si], ax
para troca, que é 1 byte mais longo e causa uma parada parcial do registro em algumas CPUs. (Mas ainda pode ser melhor que a rotação da memória-dst, que precisa carregar o valor novamente). Meu comentário tem algumas sugestões ...
Classificação JumpDown x86-64 / x86-32, 19 bytes (classifica int32_t)
É possível chamar de C usando a convenção de chamada do System V x86-64 como
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(valor de retorno = max (matriz [])).
Isso é https://en.wikipedia.org/wiki/Selection_sort , mas em vez de lembrar a posição do elemento min, troque o candidato atual na matriz . Depois de encontrar o mínimo (região não classificada), armazene-o no final da região classificada, como Classificação normal da seleção. Isso aumenta a região classificada em um. (No código, rsi
aponta para um além do final da região classificada; lodsd
avança e mov [rsi-4], eax
armazena o min novamente nela.)
O nome Jump Down Sort é usado em Bubble Sort: An Aritological Algorithmic Analysis . Acho que minha classificação é realmente uma classificação Jump Up, porque os elementos altos saltam para cima, deixando a parte inferior classificada, não o fim.
Esse design de troca faz com que a parte não classificada da matriz acabe na ordem mais inversa, levando a muitos swaps posteriormente. (Como você começa com um candidato grande e continua vendo candidatos cada vez mais baixos, continua trocando). Eu o chamei de "borbulhante", mesmo que ele mova os elementos na outra direção. A maneira como ele move os elementos também é um pouco como um tipo de inserção para trás. Para assisti-lo em ação, use GDBs display (int[12])buf
, defina um ponto de interrupção na loop
instrução interna e use c
(continuar). Pressione retornar para repetir. (O comando "display" faz com que o GDB imprima todo o estado da matriz toda vez que atingimos o ponto de interrupção).
xchg
com mem tem um lock
prefixo implícito que torna isso mais lento. Provavelmente uma ordem de magnitude mais lenta que uma troca eficiente de carga / loja; xchg m,r
é um por taxa de transferência de 23c no Skylake, mas carregar / armazenar / mover com um registro tmp para uma troca eficiente (reg, mem) pode mudar um elemento por relógio. Pode ser uma proporção pior em uma CPU AMD em que a loop
instrução é rápida e não prejudica tanto o loop interno, mas as falhas de ramificação ainda serão um grande gargalo porque os swaps são comuns (e se tornam mais comuns à medida que a região não classificada fica menor) )
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
O tamanho do código mesmo para int8_t
: uso lodsb
/ scasb
, AL
e alterar o [rsi/rdi-4]
que -1
. O mesmo código de máquina funciona no modo de 32 bits para elementos de 8/32 bits. O modo de 16 bits para elementos de 8/16 bits precisa ser reconstruído com as compensações alteradas (e os modos de endereçamento de 16 bits usam uma codificação diferente). Mas ainda 19 bytes para todos.
Evita a inicial dec ecx
comparando com o elemento que acabou de carregar antes de prosseguir. Na última iteração do loop externo, ele carrega o último elemento, verifica se é menor que ele próprio e, em seguida, está pronto. Isso permite que ele funcione com tamanho = 1, onde meu BubbleSort falha (trata-o como tamanho = 65536).
Testei esta versão (no GDB) usando o chamador deste: Experimente online! . Você pode executá-lo no TIO, mas é claro que não há depurador nem impressão. Ainda assim, o _start
que o chama sai com exit-status = maior elemento = 99, para que você possa ver que funciona.
[7 2 4 1] -> [4 2 3 1]
. Além disso, a lista CSV pode estar entre colchetes? Além disso, o formato de entrada específico é muito adequado para alguns idiomas e ruim para outros. Isso torna a análise de entrada uma grande parte para alguns envios e desnecessária para outros.