x86-16 머신 코드 (BubbleSort int8_t), 20 19 바이트
x86-64 / 32 머신 코드 (JumpDownSort) 21 19 바이트
변경 로그:
lodsb
/ cmp [si],al
아이디어에 대한 @ ped7g 덕분에 포인터 증가 / 재설정과 함께 사용했습니다. 필요하지 않음 al
/ ah
더 큰 정수에 거의 동일한 코드를 사용할 수 있습니다.
새로운 (그러나 관련) 알고리즘, 많은 구현 변경 사항 : Bubbly SelectionSort를 사용하면 바이트 또는 dword에 대해 더 작은 x86-64 구현이 가능합니다. x86-16에서 손익분기 점 (바이트 또는 단어). 또한 내 BubbleSort에있는 size = 1의 버그를 피하십시오. 아래를 참조하십시오.
새로운 min을 찾을 때마다 스왑이있는 Bubbly Selection Sort는 이미 알려진 알고리즘 인 JumpDown Sort입니다. Bubble Sort : Archaeological Algorithmic Analysis ( 거품 정렬 : 고고학 알고리즘 분석)에 언급되어 있습니다.
8 비트 부호있는 정수를 제자리에 정렬합니다 . (서명되지 않은 코드 크기는 동일 jge
합니다.jae
. . 중복은 문제가되지 않습니다. 16 비트 회전을 사용하여 8 (메모리 대상 포함)로 교체합니다.
Bubble Sort 는 성능을 저하 시키지만 기계 코드에서 구현하는 것이 가장 작은 것 중 하나라는 것을 읽었습니다. 인접한 요소를 바꾸는 특별한 트릭이있을 때 특히 그렇습니다. 이것은 거의 유일한 장점이지만 때로는 (실제 임베디드 시스템에서) 매우 짧은 목록에 사용하기에 충분한 이점입니다.
나는 스왑없이 초기 종료를 생략했다 . 나는 Wikipedia의 "최적화 된"BubbleSort 루프 n − 1
를 사용했다.n
번째로 않으므로 외부 루프 카운터가 내부 루프의 상한입니다.
NASM 목록 ( nasm -l /dev/stdout
) 또는 일반 소스
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.
cx
내부 루프 주위의 push / pop은 cx
= outer_cx 와 함께 0까지 실행됨을 의미합니다 .
참고 rol r/m16, imm8
8086 지시되지 않고, 그것은 나중에 (186 또는 286)를 첨가하고 있지만, 이것은 코드 8086, 단지 16 비트 86 되려고 아니다. SSE4.1 인 경우phminposuw
이 도움이 사용하겠습니다.
32 비트 버전 (여전히 8 비트 정수에서 작동하지만 32 비트 포인터 / 카운터로 작동)은 20 바이트 (피연산자 크기 접두어 rol word [esi-1], 8
)
버그 : size = 1은 size = 65536으로 취급됩니다. cx = 0으로 외부 do / while에 들어가는 것을 막을 수있는 것은 없습니다. (일반적으로 사용 jcxz
합니다.) 그러나 다행히 19 바이트 JumpDown Sort는 19 바이트이며 그 문제는 없습니다.
원본 x86-16 20 바이트 버전 (Ped7g 아이디어 제외). 공간 절약을 위해 생략 된 설명 은 편집 기록 을 참조하십시오 .
공연
부분적으로 겹치는 저장소 / 다시로드 (메모리 대상 회전)는 최신 x86 CPU (주문 Atom 제외)에서 저장소 전달을 중단시킵니다. 높은 값이 위쪽으로 버블 링되는 경우이 추가 대기 시간은 루프로 전달되는 종속성 체인의 일부입니다. 저장 / 재로드는 첫 번째 위치 (Haswell의 5주기 저장 전달 대기 시간)에서 빨라지지만 전달 중단은 최대 13주기와 비슷합니다. 비 순차적 실행은 이것을 숨기는 데 어려움이 있습니다.
참조 : 종류의 문자열을 정렬 거품 : 스택 오버플로를 비슷한 구현이의 버전하지만, 초기 아웃로 더 스왑이 필요 없을 때. 스왑을 위해 xchg al, ah
/ mov [si], ax
를 사용하는데 , 이는 1 바이트 더 길며 일부 CPU에서 부분 레지스터 스톨이 발생합니다. (그러나 여전히 메모리 -dst 회전보다 낫습니다.이 경우 값을 다시로드해야합니다.) 내 의견에는 몇 가지 제안이 있습니다 ...
x86-64 / x86-32 JumpDown 정렬, 19 바이트 (정렬 int32_t)
x86-64 시스템 V 호출 규칙을 사용하여 C에서 호출 가능
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(return value = max (array [])).
이것은 https://en.wikipedia.org/wiki/Selection_sort 이지만 min 요소의 위치를 기억하는 대신 현재 후보를 배열로 바꾸십시오 . min (unsorted_region)을 찾으면 일반 선택 정렬과 같이 정렬 된 영역의 끝에 저장하십시오. 이것은 정렬 된 영역을 하나씩 증가시킵니다. (코드에서, rsi
정렬 된 영역의 끝을지나 하나를 가리키고 , lodsd
이를 진행 mov [rsi-4], eax
시키고 분을 다시 저장합니다.)
Jump Down Sort라는 이름은 Bubble Sort : Archaeological Algorithmic Analysis에서 사용 됩니다. 높은 요소는 위쪽으로 점프하고 아래쪽이 아닌 끝이 정렬되어 있기 때문에 내 정렬이 실제로 점프 업 정렬이라고 생각합니다.
이 교환 설계로 인해 어레이의 정렬되지 않은 부분이 대부분 역순으로 정렬되어 나중에 스왑이 많이 발생합니다. (큰 후보로 시작하고 낮은 후보와 낮은 후보를 계속보고 있기 때문에 계속 스와핑을 계속합니다.) 요소를 다른 방향으로 옮기더라도 "거품"이라고했습니다. 요소를 움직이는 방식도 거꾸로 삽입 정렬과 비슷합니다. 실제로 작동하려면 GDB를 사용 display (int[12])buf
하고 내부 loop
명령 에 중단 점을 설정 한 후 c
(계속)을 사용하십시오. return을 눌러 반복하십시오. "display"명령은 GDB가 중단 점에 도달 할 때마다 전체 배열 상태를 인쇄하도록합니다.
xchg
mem을 사용하면 암시 적 lock
접두사가있어 속도가 느려집니다. 아마도 효율적인로드 / 스토어 스왑보다 약 10 배 정도 느립니다. xchg m,r
Skylake에서 처리량은 23c 당 하나이지만 효율적인 스왑 (reg, mem)을위한 tmp reg를 사용하여로드 / 저장 / mov는 클럭 당 하나의 요소를 이동할 수 있습니다. loop
명령이 빠르고 내부 루프를 병목 현상이 발생하지 않는 AMD CPU의 경우 더 나쁜 비율 일 수 있지만 스왑이 일반적이기 때문에 분기 누락이 여전히 큰 병목 현상이 발생합니다 (분류되지 않은 영역이 작을수록 더 일반적입니다) ).
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
에 대한 동일한 코드 크기 int8_t
: 사용 lodsb
/ scasb
, AL
및 변경 [rsi/rdi-4]
에-1
. 동일한 기계 코드는 8/32 비트 요소에 대해 32 비트 모드에서 작동합니다. 8/16 비트 요소의 16 비트 모드는 오프셋을 변경 한 상태에서 다시 빌드해야합니다 (16 비트 주소 지정 모드는 다른 인코딩을 사용함). 그러나 여전히 19 바이트입니다.
dec ecx
이동하기 전에 방금로드 한 요소와 비교 하여 초기 를 피합니다 . 외부 루프의 마지막 반복에서 마지막 요소를로드하고 자체보다 작은 지 확인한 다음 수행됩니다. 이렇게하면 BubbleSort가 실패하는 size = 1로 작동 할 수 있습니다 (크기 = 65536으로 처리).
이 호출자를 사용하여이 버전 (GDB)을 테스트 했습니다. 온라인으로 사용해보십시오! . 이것을 TIO에서 실행할 수 있지만 물론 디버거 또는 인쇄는 없습니다. 여전히 _start
호출은 exit-status = 가장 큰 요소 = 99로 종료되므로 작동하는 것을 볼 수 있습니다.
[7 2 4 1] -> [4 2 3 1]
. 또한 CSV 목록을 괄호 안에 넣을 수 있습니까? 또한 특정 입력 형식은 일부 언어에 매우 적합하고 다른 언어에는 적합하지 않습니다. 이로 인해 입력 구문 분석이 일부 제출의 경우 큰 부분이되고 다른 경우에는 필요하지 않습니다.