x86-16マシンコード(BubbleSort int8_t)、20 19バイト
x86-64 / 32マシンコード(JumpDownSort)21 19バイト
変更ログ:
lodsb
/ pedegの@ ped7gに感謝しcmp [si],al
、それを私が見ていたポインターのインクリメント/リセットと組み合わせます。必要なしal
/ah
ため、より大きな整数にほぼ同じコードを使用できます。
新しい(ただし関連する)アルゴリズム、多くの実装の変更:Bubbly SelectionSortは、バイトまたはdwordの小さなx86-64実装を許可します。x86-16の損益分岐点(バイトまたはワード)。また、BubbleSortが持っているsize = 1のバグを回避します。下記参照。
新しい最小値を見つけるたびに、スワップを使用したBubbly Selection Sortが既知のアルゴリズムであるJumpDown Sortであることがわかりました。「バブルソート:考古学的アルゴリズム分析」で説明されています(つまり、バブルソートは吸ったにもかかわらず、どのように普及したのか)。
8ビットの符号付き整数をインプレースでソートします。(符号なしだけ変更、同じコードの大きさjge
にjae
)。重複は問題ではありません。16ビットの8回転(メモリ宛先を使用)を使用してスワップします。
バブルソートはパフォーマンスを低下させるさせますが、マシンコードに実装する最小の1つであることを読みました。これは、隣接する要素を交換するための特別なトリックがある場合に特に当てはまるようです。これはほとんど唯一の利点ですが、(実際の組み込みシステムでは)非常に短いリストに使用するのに十分な場合もあります。
スワップなしの早期終了を省略しました。ウィキペディアの「最適化された」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
= outer_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として扱われます。これは、cx = 0で外側のdo / whileに入ることを妨げるものがないためです。(通常jcxz
はそのために使用します。)しかし、幸いなことに、19バイトのJumpDown Sortは19バイトであり、その問題はありません。
オリジナルのx86-16 20バイトバージョン(Ped7gのアイデアなし)。スペースを節約するために省略されています。説明付きの編集履歴を参照してください。
性能
部分的に重複するストア/リロード(メモリ宛先ローテーションで)により、最新のx86 CPU(ストア内のAtomを除く)でストア転送が停止します。高い値が上向きにバブリングしている場合、この余分なレイテンシは、ループで伝達される依存関係チェーンの一部です。ストア/リロードはそもそも吸い込みますが(Haswellでの5サイクルのストア転送レイテンシーなど)、転送ストールは13サイクル以上になります。順不同の実行では、これを隠すのが困難になります。
関連項目:Stack Overflow:同様の実装で、スワップが不要なアーリーアウトのこのバージョンの文字列をソートするためのバブルソート。スワップにxchg al, ah
/ mov [si], ax
を使用します。これは1バイト長く、一部のCPUで部分レジスタストールを引き起こします。(ただし、値を再度ロードする必要があるmemory-dst rotateよりも優れている場合があります)。私のコメントにはいくつかの提案があります...
x86-64 / x86-32ジャンプダウンソート、19バイト(int32_tをソート)
x86-64 System V呼び出し規約を使用してCから呼び出し可能
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)を見つけたら、通常の選択ソートのように、ソートされた領域の最後に格納します。これにより、ソートされた領域が1つ増えます。(コードでrsi
は、ソートされた領域の末尾の1つを指します。lodsd
それを進めmov [rsi-4], eax
、最小値を格納します。)
ジャンプソートという名前は、バブルソート:考古学的アルゴリズム分析で使用されます。私のソートは本当にジャンプアップソートだと思います。なぜなら、高い要素は上にジャンプし、最後ではなく下をソートしたままになるからです。
この交換設計により、配列の未ソート部分がほとんど逆ソートの順序になり、後に多くのスワップが発生します。(大きな候補から始めて、下位の候補を見続けるため、スワップを続けます。)要素を他の方向に動かしても、「バブル」と呼んでいます。要素を移動する方法も、後方挿入ソートのようなものです。それを実際に見るには、GDBを使用display (int[12])buf
し、内部loop
命令にブレークポイントを設定して、c
(続行)。Returnキーを押して繰り返します。(「display」コマンドは、ブレークポイントにヒットするたびにGDBに配列全体の状態を出力させます)。
xchg
memには暗黙のlock
接頭辞があり、これにより余分な時間がかかります。おそらく、効率的なロード/ストアスワップよりも1桁遅くなります。xchg m,r
Skylakeの23cスループットごとに1つですが、効率的なswap(reg、mem)のtmp regでload / store / movはクロックごとに1つの要素をシフトできます。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 = 65536として扱う)size = 1で動作することができます。
this callerを使用してこのバージョンを(GDBで)テストしました: オンラインで試してみてください!。これをTIOで実行できますが、もちろんデバッガーや印刷はできません。それでも、それ_start
を呼び出すものはexit-status = maximum element = 99で終了するため、動作することがわかります。
[7 2 4 1] -> [4 2 3 1]
。また、CSVリストを括弧内に含めることはできますか?また、特定の入力形式は一部の言語に非常に適しており、他の言語には不適切です。これにより、入力の解析は一部のサブミッションでは大きな部分を占め、他のサブミッションでは不要になります。