машинний код x86-16 (BubbleSort int8_t), 20 19 байт
x86-64 / 32 код машини (JumpDownSort) 21 19 байт
Журнал змін:
Дякуємо @ ped7g за lodsb
/ cmp [si],al
ідею та додає це разом із збільшенням / скиданням вказівника, на яке я дивився. Не потребуючи al
/ ah
дозволяє нам використовувати майже той самий код для більших цілих чисел.
Новий (але пов’язаний з цим) алгоритм, безліч змін реалізації: Bubbly SelectionSort дозволяє меншу реалізацію x86-64 для байтів або dwords; беззбитковість на x86-16 (байти чи слова). Також уникає помилок на розмір = 1, який має мій BubbleSort. Дивись нижче.
Виявляється, мій Bubbly Selection Sort зі свопами кожного разу, коли ви знайдете новий хв - це вже відомий алгоритм, JumpDown Sort. Він згадується у сортуванні бульбашок: археологічний алгоритмічний аналіз (тобто як сортування бульбашок стало популярним, незважаючи на смоктання).
Сортує 8-бітні цілі числа на місці . (Непідписаний - той самий розмір коду, просто змініть jge
на a jae
). Дублікати - це не проблема. Ми обміняємось, використовуючи 16-бітове поворот на 8 (з призначенням пам'яті).
Bubble Sort відстійний для продуктивності , але я читав, що це один з найменших для впровадження машинного коду. Це здається особливо актуальним, коли існують спеціальні хитрощі для заміни сусідніх елементів. Це майже єдина його перевага, але іноді (в реальному житті вбудованих систем) достатньо переваги, щоб використовувати його для дуже коротких списків.
Я пропустив дострокове припинення без будь-яких свопів . Я використав "оптимізований" цикл 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.
push / pop cx
навколо внутрішнього циклу означає, що він працює з cx
= external_cx до 0.
Зауважте, що rol r/m16, imm8
це не інструкція 8086, вона була додана пізніше (186 або 286), але це не намагається бути 8086 кодом, лише 16-бітним x86. Якщо SSE4.1 phminposuw
допоможе, я би використовував його.
32-розрядна версія цього (все ще працює на 8-бітових цілих числах, але з 32-бітовими покажчиками / лічильниками) становить 20 байт (префікс розміру операнду включений rol word [esi-1], 8
)
Помилка: size = 1 трактується як розмір = 65536, тому що ніщо не заважає нам увійти до зовнішнього do / while з cx = 0. (Ви зазвичай використовуєте jcxz
для цього.) Але, на щастя, 19-байтовий JumpDown Sort має 19 байт і не має такої проблеми.
Оригінальна версія 20-байт x86-16 (без ідеї Ped7g). Пропущено для економії місця, перегляньте історію редагування для нього з описом.
Продуктивність
Частково перекриваюче зберігання / перезавантаження (у повороті до місця призначення пам'яті) спричиняє затримку переадресації магазину на сучасних процесорах x86 (крім Atom порядку). Коли високе значення кидається вгору, ця додаткова затримка є частиною ланцюга залежностей, перенесених циклом. Зберігати / перезавантажувати смоктання в першу чергу (як, наприклад, 5 циклів затримки переадресації магазину на Haswell), але стійла переадресації приводять його приблизно до 13 циклів. Виконання поза замовлення матиме труднощі приховати це.
Дивіться також: Переповнення стека: сортування міхурів для сортування рядків для версії цієї програми з аналогічною реалізацією, але з достроковою програмою, коли заміни не потрібні. Він використовує xchg al, ah
/ mov [si], ax
для заміни, що на 1 байт довше і викликає зупинку часткового реєстру на деяких процесорах. (Але це все ж може бути краще, ніж обертання пам’яті, яке потрібно знову завантажити). У моєму коментарі є кілька пропозицій ...
x86-64 / x86-32 Сортувати JumpDown, 19 байт (сортування int32_t)
Викликається з C, використовуючи умовний виклик системи V x86-64 як V
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(значення повернення = max (масив [])).
Це https://en.wikipedia.org/wiki/Selection_sort , але замість того, щоб запам'ятати положення елемента min, поміняйте поточний кандидат у масив . Після того, як ви знайшли хв (неортирований_регіон), збережіть його до кінця відсортованої області, як звичайний Сортування вибору. Це зростає відсортовану область по одному. (У коді rsi
вказує на один минулий кінець відсортованої області; lodsd
просуває його та mov [rsi-4], eax
зберігає хв назад у нього.)
Назва Сортувати вниз по сорту використовується в Bubble Sort: Археологічний алгоритмічний аналіз . Я думаю, мій сорт - це справді стрибок вгору, тому що високі елементи стрибають вгору, залишаючи знизу відсортовану, а не кінець.
Цей дизайн обміну призводить до того, що несортована частина масиву закінчується в основному зворотно відсортованим порядком, що призводить до пізніх змін. (Тому що ви починаєте з великого кандидата і продовжуєте бачити все нижчих та нижчих кандидатів, тому ви продовжуєте міняти місцями.) Я назвав це "бульбашково", хоча він переміщує елементи в іншому напрямку. Спосіб переміщення елементів також трохи схожий на сортування назад. Щоб переглянути його в дії, використовуйте GDB display (int[12])buf
, встановіть точку перерви у внутрішній loop
інструкції та використовуйте c
(продовжуйте). Натисніть клавішу return, щоб повторити. (Команда "display" дає GDB друкувати весь стан масиву кожного разу, коли ми потрапляємо на точку зламу).
xchg
У mem є неявний lock
префікс, який робить це надзвичайно повільним. Можливо, приблизно на порядок повільніше, ніж ефективний своп / зберігання; xchg m,r
- це одна пропускна здатність 23c на Skylake, але завантаження / зберігання / mov з tmp reg для ефективної заміни (reg, mem) може зміщувати один елемент на такт. Це може бути гіршим співвідношенням у процесорі AMD, де loop
інструкція швидка, і не буде вузьким місцем внутрішнього циклу, але пропуски гілок все ще будуть великим вузьким місцем, оскільки заміни є звичайними (і стають більш поширеними, коли несортірованная область стає меншою ).
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
. Цей же машинний код працює в 32-бітному режимі для 8/32-бітних елементів. 16-бітний режим для 8/16-бітних елементів повинен бути відтворений із зміненими зміщеннями (а 16-бітні режими адреси використовують інше кодування). Але все ж 19 байт для всіх.
Він уникає початкового dec ecx
шляхом порівняння з елементом, який він щойно завантажив, перш ніж рухатися далі. Під час останньої ітерації зовнішньої петлі він завантажує останній елемент, перевіряє, чи не менше він себе, то робиться. Це дозволяє йому працювати з size = 1, де мій BubbleSort виходить з ладу (трактує його як розмір = 65536).
Я протестував цю версію (в GDB) за допомогою цього абонента: Спробуйте в Інтернеті! . Ви можете запустити це на TIO, але звичайно без налагодження чи друку. І все-таки те, _start
що викликає, завершує роботу з вихідним статусом = найбільшим елементом = 99, тож ви можете бачити, що це працює.
[7 2 4 1] -> [4 2 3 1]
. Також, чи може список CSV міститись у дужках? Також конкретний формат введення дуже підходить для деяких мов, а для інших поганий. Це робить синтаксичний аналіз великою частиною для одних подань та непотрібним для інших.