машинный код x86-16 (BubbleSort int8_t), 20 19 байт
машинный код x86-64 / 32 (JumpDownSort) 21 19 байт
Changelog:
Спасибо @ ped7g за идею lodsb
/ cmp [si],al
и соединили это с увеличением / сбросом указателя, на который я смотрел. Не нужно al
/ ah
позволяет нам использовать почти тот же код для больших целых чисел.
Новый (но связанный) алгоритм, много изменений реализации: Bubbly SelectionSort допускает меньшую реализацию x86-64 для байтов или слов; безубыточность на x86-16 (байты или слова). Также избегает ошибки на size = 1, которая есть у моего BubbleSort. Увидеть ниже.
Оказывается, что моя Bubbly Selection Sort с перестановками каждый раз, когда вы находите новый мин, уже является известным алгоритмом JumpDown Sort. Это упоминается в Bubble Sort: Археологический Алгоритмический Анализ (то есть, как Bubble Sort стал популярным, несмотря на сосание).
Сортирует 8-битные целые числа со знаком на месте . (Без знака имеет тот же размер кода, просто изменить jge
к 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.
Пуш / поп 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 рассматривается как size = 65536, потому что ничто не мешает нам войти во внешнюю do / while с cx = 0. (Вы обычно используете jcxz
для этого.) Но, к счастью, 19-байтовая сортировка JumpDown занимает 19 байтов и не имеет этой проблемы.
Оригинальная 20-байтовая версия x86-16 (без идеи Ped7g). Упущено для экономии места, см. Историю редактирования для него с описанием.
Спектакль
Частично перекрывающееся сохранение / перезагрузка (при повороте места назначения памяти) приводит к остановке пересылки хранилища на современных процессорах x86 (кроме Atom в порядке). Когда высокое значение пузырится вверх, эта дополнительная задержка является частью цепочки зависимостей, переносимых циклом. Хранить / перезагружать в первую очередь отстой (например, 5-тактная задержка пересылки при хранении в Haswell), но остановка пересылки доводит ее до 13 циклов. Внеочередное исполнение будет трудно скрыть это.
См. Также: Переполнение стека: пузырьковая сортировка для сортировки строки для версии с аналогичной реализацией, но с ранним выходом, когда не требуется перестановок. Он использует xchg al, ah
/ mov [si], ax
для перестановки, которая на 1 байт длиннее и вызывает остановку частичного регистра на некоторых процессорах. (Но это может все же быть лучше, чем memory-dst rotate, который должен снова загрузить значение). Мой комментарий там есть некоторые предложения ...
x86-64 / x86-32 JumpDown Sort, 19 байт (сортирует int32_t)
Вызывается из C с использованием соглашения о вызовах System V x86-64 как
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(возвращаемое значение = max (array [])).
Это https://en.wikipedia.org/wiki/Selection_sort , но вместо запоминания позиции элемента min, поменяйте местами текущий кандидат в массив . Как только вы нашли min (unsorted_region), сохраните его в конце отсортированной области, как в обычной сортировке выбора. Это увеличивает отсортированный регион на единицу. (В коде rsi
указывает на один конец конца отсортированного региона; lodsd
он перемещается вперед и mov [rsi-4], eax
сохраняет мин. Обратно в него.)
Название «Прыжок вниз» используется в Bubble Sort: археологический алгоритмический анализ . Я полагаю, что мой вид - это тип Jump Up, потому что высокие элементы прыгают вверх, оставляя отсортированным нижний, а не конец.
Такая схема обмена приводит к тому, что несортированная часть массива заканчивается в основном в обратном порядке, что впоследствии приводит к множеству перестановок. (Потому что вы начинаете с большого кандидата и продолжаете видеть все более и более низкого кандидата, поэтому вы продолжаете менять местами.) Я назвал его «пузырьковым», даже если он перемещает элементы в другом направлении. То, как он перемещает элементы, также немного похоже на обратную вставку-сортировку. Чтобы посмотреть его в действии, используйте GDB display (int[12])buf
, установите точку останова на внутренней loop
инструкции и используйте c
(продолжить). Нажмите возврат, чтобы повторить. (Команда «display» заставляет GDB печатать состояние целого массива каждый раз, когда мы достигаем точки останова).
xchg
У mem есть неявный lock
префикс, который делает это очень медленным. Вероятно, примерно на порядок медленнее, чем эффективный обмен загрузкой / хранением; xchg m,r
это одна пропускная способность на 23c на Skylake, но загрузка / сохранение / перемещение с регистром tmp для эффективного обмена (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 не работает (обрабатывает его как size = 65536).
Я проверил эту версию (в GDB) с помощью этого абонента: попробуйте онлайн! , Вы можете запустить его на TIO, но, конечно, нет отладчика или печати. Тем не менее, тот, _start
который вызывает его, завершается с exit-status = наибольшим элементом = 99, так что вы можете видеть, что он работает.
[7 2 4 1] -> [4 2 3 1]
, Кроме того, список CSV может быть в скобках? Кроме того, определенный формат ввода очень подходит для некоторых языков и плохо подходит для других. Это делает анализ входных данных большой частью для некоторых представлений и ненужным для других.