x86-16 makine kodu (BubbleSort int8_t), 20 19 bayt
x86-64 / 32 makine kodu (JumpDownSort) 21 19 bayt
Değişiklikler:
lodsb
/ cmp [si],al
Fikri için @ ped7g'ye teşekkür ederim ve bunu, bakmakta olduğum bir işaretçi artırma / sıfırlama ile bir araya getirme . İhtiyaç duymamak al
/ah
daha büyük tamsayılar için neredeyse aynı kodu kullanmamıza izin verir.
Yeni (ancak ilişkili) algoritma, birçok uygulama değişikliği: Bubbly SelectionSort, bayt veya şifre için daha küçük bir x86-64 uygulaması sağlar; x86-16'daki break-even (bayt veya sözcük). Ayrıca, BubbleSort'un sahip olduğu size = 1 büyüklüğündeki hatayı da engeller. Aşağıya bakınız.
Bubbly Selection Sort'un, her yeni bir min bulduğunuzda swaplarla değiştirildiğini, JumpDown Sort'un zaten bilinen bir algoritma olduğu ortaya çıktı. Bubble Sort: Bir Arkeolojik Algoritmik Analiz'de bahsedilmiştir (örn. Bubble Sort emmeye rağmen nasıl popüler olmuştur).
Yerinde 8 bit işaretli tam sayıları sıralar . (İmzasız aynı kod boyutunda, sadece jge
birjae
). Kopyalar sorun değil. 16 bitlik bir döndürme kullanarak 8'e kadar (bellek hedefiyle) değiştiririz.
Bubble Sort performans için berbat , ancak bunun makine kodunda uygulanması en küçüklerinden biri olduğunu okudum. Bu, özellikle bitişik elemanları değiştirmek için özel numaralar olduğunda doğrudur. Bu hemen hemen tek avantajı, ancak bazen (gerçek yaşamdaki gömülü sistemlerde) bu onu çok kısa listelerde kullanmak için yeterli bir avantaj.
Erken feshi hiçbir takas üzerinde ihmal ettim . Wikipedia'nın "en iyi duruma getirilmiş" BubbleSort döngüsünü kullandım;n − 1
n
, bu süre -inci kez çalışırken öğelere , bu yüzden dış döngü sayacı iç döngü için üst sınırdır.
NASM listesi ( nasm -l /dev/stdout
) veya düz kaynak
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
İç döngü etrafında itme / pop ile çalışır anlamına gelircx
= 0'a kadar outer_cx .
Bunun rol r/m16, imm8
bir 8086 talimatı olmadığını unutmayın, daha sonra eklendi (186 veya 286), ancak bu 8086 kodu, sadece 16 bit x86 olmaya çalışıyor. Eğer SSE4.1 isephminposuw
yardım , kullanırdım.
Bunun 32 bit sürümü (hala 8 bit tam sayılarda çalışıyor, ancak 32 bit işaretçiler / sayıcılar ile çalışıyor) 20 bayttır (operand boyutu öneki açık rol word [esi-1], 8
)
Hata: size = 1 size = 65536 olarak kabul edildi, çünkü hiçbir şey bizi dış do / girerken cx = 0 ile engellemiyor. (Normalde jcxz
bunun için kullanırsınız.) Fakat neyse ki 19 bayt JumpDown Sıralaması 19 bayttır ve bu sorunu yaşamaz.
Orijinal x86-16 20 byte versiyonu (Ped7g'in fikri olmadan). Yer kazanmak için ihmal edildi, bir açıklama içeren düzenleme geçmişine bakın .
performans
Kısmen üst üste binen mağaza / yeniden yükleme (bellek hedefi döndürme modunda), modern x86 CPU'larda (sıralı Atom hariç) bir mağaza iletme duraklamasına neden olur. Yüksek bir değer yukarı doğru kabarcıklanırken, bu gecikme süresi döngü taşıma bağımlılık zincirinin bir parçasıdır. İlk etapta emmek saklayın / yeniden yükleyin (Haswell'deki 5 devirli mağaza ileri gecikme süresi gibi), ancak bir ileri alma kabini 13 tur gibi daha fazla sayı getirir. Sıra dışı yürütme bunu gizlemekte zorlanacaktır.
Ayrıca bakınız: Yığın Taşması: benzer bir uygulamayla, ancak herhangi bir takas gerekmediğinde erken bir zaman için bunun bir sürümü için dizgiyi sıralamak için kabarcık sıralaması . 1 bayt daha uzun olan ve bazı CPU'larda kısmi kayıt durmasına neden olan, takas için xchg al, ah
/ kullanır mov [si], ax
. (Ancak yine de değeri tekrar yüklemesi gereken bellek-dönme işleminden daha iyi olabilir). Benim yorumumun bazı önerileri var ...
x86-64 / x86-32 JumpDown Sıralama, 19 bayt (int32_t sıralar)
X86-64 System V arama kuralını kullanarak C'den çağrılabilir
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(dönüş değeri = maks (dizi [])).
Bu https://en.wikipedia.org/wiki/Selection_sort'tur , fakat min elemanının pozisyonunu hatırlamak yerine, mevcut adayı diziye değiştirirsiniz . Min'i (sıralanmamış_ bölge) bulduktan sonra, normal Seçim Sıralaması gibi, sıralanmış bölgenin sonuna kaydedin. Bu, sıralanan bölgeyi birer birer büyütür. (Kodda, rsi
sıralanan bölgenin sonundan bir tanesine işaret eder; lodsd
onu ilerletir ve mov [rsi-4], eax
min'i tekrar içine kaydeder.)
Jump Down Sort adı Bubble Sort: Bir Arkeolojik Algoritmik Analizde kullanılır . Sanırım benim sıralamam gerçekten bir Atlama Sıralaması, çünkü yüksek elemanlar yukarı doğru atlıyor, altını sıralı değil bırakarak.
Bu değişim tasarımı, dizinin sıralanmamış kısmına, çoğunlukla tersine sıralı bir şekilde, daha sonradan çok fazla takasla sonuçlanmasına neden olur. (Çünkü büyük bir adayla başlarsınız, daha düşük ve daha düşük adaylar görürsünüz, böylece değişmeye devam edersiniz.) Elemanları diğer yöne hareket etmesine rağmen “kabarcıklı” olarak adlandırdım. Elemanları hareket ettirme şekli de geriye doğru yerleştirme sıralaması gibidir. Hareket halindeyken izlemek için, GDB'leri kullanın display (int[12])buf
, iç loop
talimatlarda bir kesme noktası ayarlayın ve kullanın c
(devam). Tekrarlamak için return tuşuna basın. ("Display" komutu, kesme noktasına her bastığımızda tüm dizi durumunu yazdırması için GDB'yi alır).
xchg
mem ile lock
bu ekstra yavaşlık yaratan bir önek vardır. Muhtemelen verimli bir yük / depo takasından daha yavaş bir büyüklük sırası; xchg m,r
Skylake üzerindeki her 23c verimden bir tanesidir, ancak etkin bir değişim (reg, mem) için bir tmp reg ile yükle / kaydet / mov, saat başına bir elementi değiştirebilir. loop
Talimatın hızlı olduğu ve iç döngüyü çok fazla sıkıntıya sokmayacağı bir AMD işlemcide daha kötü bir oran olabilir , ancak dallanmalar büyük bir darboğaz olacaktır, çünkü takaslar yaygındır (ve sıralanmamış bölge küçüldükçe daha yaygın hale gelir. ).
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
İçin aynı kod boyutu int8_t
: kullanım lodsb
/ scasb
, AL
ve değiştirmek [rsi/rdi-4]
için-1
. Aynı makine kodu, 8/32-bit öğeler için 32-bit modunda çalışır. 8/16 bitlik elemanlar için 16 bitlik modun, ofsetlerin değiştirilmesiyle yeniden oluşturulması gerekir (ve 16 bitlik adresleme modları farklı bir kodlama kullanır). Ama yine de herkes için 19 bayt.
Devam dec ecx
etmeden önce yeni yüklediği öğeyle karşılaştırarak başlangıçtan kaçınır . Dış döngünün son yinelemesinde, son elemanı yükler, kendisinden küçük olup olmadığını kontrol eder, sonra yapılır. Bu, BubbleSort'umun başarısız olduğu size = 1 ile çalışmasına izin verir (bunu size = 65536 olarak kabul eder).
Bu arayanı kullanarak bu sürümü (GDB cinsinden) test ettim: Çevrimiçi deneyin! . Bunu TIO'da çalıştırabilirsiniz, ancak elbette hata ayıklayıcı veya yazdırma özelliği yoktur. Yine de, _start
bu çağrılar exit-status = biggest element = 99 ile çıkar, böylece çalıştığını görebilirsiniz.
[7 2 4 1] -> [4 2 3 1]
. Ayrıca, CSV listesi parantez içinde olabilir mi? Ayrıca, belirli giriş formatı bazı diller için çok uygundur ve diğerleri için kötüdür. Bu da girdilerin bazı gönderimler için büyük bir kısmını, diğerleri için gereksiz olmasını sağlar.