En hızlı sabit uzunluklu 6 int dizi


401

Başka bir Stack Overflow sorusuna cevap verdim ( bu soru ) İlginç bir alt soruna rastladım. 6 tamsayı dizisini sıralamanın en hızlı yolu nedir?

Soru çok düşük olduğundan:

  • kütüphanelerin kullanılabilir olduğunu (ve çağrının kendisinin maliyetinin olduğunu) varsayamayız, sadece düz C
  • ( çok yüksek bir maliyeti olan) talimat boru hattının boşaltılmasını önlemek için muhtemelen dalları, sıçramaları ve diğer her türlü kontrol akışının kesilmesini en aza indirmeliyiz ( &&veya sıra noktalarının arkasında gizli olanlar gibi ||).
  • oda sınırlıdır ve kayıtları ve bellek kullanımını en aza indirmek bir konudur, ideal olarak yer sıralaması muhtemelen en iyisidir.

Gerçekten bu soru, hedefin kaynak uzunluğunu en aza indirmek değil, yürütme süresini en aza indirmek olduğu bir tür Golf. Ben Michael Zrash ve onun devamı tarafından Kod Optimizasyonu Zen kitabının başlığında kullanılan 'Zening' kodu diyorum .

Neden ilginç olduğuna gelince, birkaç katman var:

  • örnek basit ve anlaşılması ve ölçülmesi kolaydır, fazla C becerisi yoktur
  • sorun için iyi bir algoritma seçiminin etkilerini değil, aynı zamanda derleyicinin ve temel donanımın etkilerini de gösterir.

İşte benim referans (naif, optimize edilmemiş) uygulaması ve test setim.

#include <stdio.h>

static __inline__ int sort6(int * d){

    char j, i, imin;
    int tmp;
    for (j = 0 ; j < 5 ; j++){
        imin = j;
        for (i = j + 1; i < 6 ; i++){
            if (d[i] < d[imin]){
                imin = i;
            }
        }
        tmp = d[j];
        d[j] = d[imin];
        d[imin] = tmp;
    }
}

static __inline__ unsigned long long rdtsc(void)
{
  unsigned long long int x;
     __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
     return x;
}

int main(int argc, char ** argv){
    int i;
    int d[6][5] = {
        {1, 2, 3, 4, 5, 6},
        {6, 5, 4, 3, 2, 1},
        {100, 2, 300, 4, 500, 6},
        {100, 2, 3, 4, 500, 6},
        {1, 200, 3, 4, 5, 600},
        {1, 1, 2, 1, 2, 1}
    };

    unsigned long long cycles = rdtsc();
    for (i = 0; i < 6 ; i++){
        sort6(d[i]);
        /*
         * printf("d%d : %d %d %d %d %d %d\n", i,
         *  d[i][0], d[i][6], d[i][7],
         *  d[i][8], d[i][9], d[i][10]);
        */
    }
    cycles = rdtsc() - cycles;
    printf("Time is %d\n", (unsigned)cycles);
}

Ham sonuçlar

Varyant sayısı büyük hale geliyor, ben bulunabilir bir test paketi hepsini topladı burada . Kullanılan gerçek testler, Kevin Stock sayesinde yukarıda gösterilenlerden biraz daha az saftır. Kendi ortamınızda derleyebilir ve yürütebilirsiniz. Farklı hedef mimariler / derleyiciler üzerindeki davranışlarla oldukça ilgileniyorum. (Tamam çocuklar, cevaplara koyun, yeni bir sonuç kümesinin tüm katılımcılarını + 1'leyeceğim).

Bir yıl önce Daniel Stutzbach'a (golf için) cevap verdim, çünkü o sırada en hızlı çözümün kaynağıydı (sıralama ağları).

Linux 64 bit, gcc 4.6.1 64 bit, Intel Core 2 Duo E8400, -O2

  • Qsort kütüphane işlevine doğrudan çağrı: 689.38
  • Saf uygulama (ekleme türü): 285.70
  • Ekleme Sıralaması (Daniel Stutzbach): 142.12
  • Ekleme Sıralama Açıldı: 125.47
  • Sıralama Düzeni: 102.26
  • Kayıtlı Rank Order: 58.03
  • Sıralama Ağları (Daniel Stutzbach): 111.68
  • Sıralama Ağları (Paul R): 66.36
  • Hızlı Değiştirme ile Ağları Sıralama 12: 58.86
  • Sıralama Ağları 12 yeniden sıralandı Takas: 53.74
  • Sıralama Ağları 12 yeniden sıralandı Basit Değiştirme: 31.54
  • Hızlı swap ile yeniden sıralanan Sıralama Ağı: 31.54
  • Hızlı değiştirilebilen V2 ile yeniden sıralanan Sıralama Ağı: 33.63
  • Eğik Kabarcık Sıralaması (Paolo Bonzini): 48.85
  • Unrolled Ekleme Sıralaması (Paolo Bonzini): 75.30

Linux 64 bit, gcc 4.6.1 64 bit, Intel Core 2 Duo E8400, -O1

  • Qsort kütüphane fonksiyonuna doğrudan çağrı: 705.93
  • Saf uygulama (ekleme türü): 135.60
  • Ekleme Sıralaması (Daniel Stutzbach): 142.11
  • Ekleme Sıralama Açıldı: 126.75
  • Sıralama Düzeni: 46.42
  • Kayıtlı Sıralama Düzeni: 43.58
  • Sıralama Ağları (Daniel Stutzbach): 115.57
  • Sıralama Ağları (Paul R): 64.44
  • Hızlı Değiştirme ile Ağları Sıralama 12: 61.98
  • Sıralama Ağları 12 yeniden sıralandı Takas: 54.67
  • Sıralama Ağları 12 yeniden sıralandı Basit Değiştirme: 31.54
  • Hızlı swap ile yeniden sıralanan Sıralama Ağı: 31.24
  • Hızlı değiştirilebilir V2 ile yeniden sıralanan Sıralama Ağı: 33.07
  • Eğik Kabarcık Sıralaması (Paolo Bonzini): 45.79
  • Unrolled Ekleme Sıralaması (Paolo Bonzini): 80.15

Hem -O1 hem de -O2 sonuçlarını dahil ettim çünkü şaşırtıcı bir şekilde birkaç program için O2, O1'den daha az verimlidir. Acaba bu etkinin belirli bir optimizasyonu var mı?

Önerilen çözümlerle ilgili yorumlar

Ekleme Sıralaması (Daniel Stutzbach)

Beklendiği gibi dalları en aza indirmek gerçekten iyi bir fikirdir.

Sıralama Ağları (Daniel Stutzbach)

Yerleştirme sıralamasından daha iyidir. Ana etkinin dış döngüden kaçınmamasını merak ettim. Kontrol etmek için unrolled ekleme sıralama ile denedim ve gerçekten kabaca aynı rakamlar (kod burada ).

Sıralama Ağları (Paul R)

Şu ana kadar en iyisi. Test etmek için kullandığım gerçek kod burada . Neden diğer sıralama ağ uygulaması neredeyse iki kat daha hızlı olduğunu bilmiyorum. Parametre geçiyor mu? Hızlı maksimum?

Hızlı Değiştirme ile Ağları Sıralama 12 SWAP

Daniel Stutzbach'ın önerdiği gibi, 12 takas sıralama ağını şubesiz hızlı takas ile birleştirdim (kod burada ). Gerçekten daha hızlı, şimdiye kadar en iyi küçük bir marjla (kabaca% 5) 1 daha az takas kullanılarak beklenebileceği gibi.

Ayrıca, dalsız takasın PPC mimarisinde kullanıldığında basit olandan çok (4 kat) daha az etkili olduğunu fark etmek de ilginçtir.

Calling Library qsort

Başka bir referans noktası vermek için ben de sadece kütüphane qsort (kod burada ) çağırmak için önerilen denedim . Beklendiği gibi çok daha yavaş: 10 ila 30 kat daha yavaş ... yeni test paketiyle belirginleştiği için, asıl sorun ilk çağrıdan sonra kütüphanenin ilk yükü gibi görünüyor ve diğerleriyle çok zayıf karşılaştırmıyor sürümü. Linux'umda sadece 3 ila 20 kat daha yavaş. Başkaları tarafından testler için kullanılan bazı mimarilerde bile daha hızlı görünüyor (kütüphane qsort daha karmaşık bir API kullandığından, bunun için gerçekten şaşırdım).

Rütbe sırası

Rex Kerr tamamen farklı bir yöntem önerdi: dizinin her bir öğesi için doğrudan son konumunu hesaplar. Hesaplama sırası sırasının dal gerektirmediği için bu etkilidir. Bu yöntemin dezavantajı, dizinin bellek miktarının üç katını almasıdır (dizinin bir kopyası ve sıralama düzenlerini depolamak için değişkenler). Performans sonuçları çok şaşırtıcı (ve ilginç). 32 bit işletim sistemi ve Intel Core2 Quad E8300 ile referans mimarimde, döngü sayısı 1000'in biraz altındaydı (dallanma takaslı sıralama ağları gibi). Ancak 64 bitlik kutumda (Intel Core2 Duo) derlendiğinde ve yürütüldüğünde çok daha iyi performans gösterdi: şimdiye kadarki en hızlı oldu. Sonunda gerçek nedeni buldum. 32 bitlik kutum gcc 4.4.1 ve 64 bitlik kutum gcc 4.4 kullanıyorum.

güncelleme :

Yukarıdaki yayınlanmış şekillerin gösterdiği gibi, bu etki hala gcc'nin daha sonraki sürümleri ile artmıştır ve Rank Order diğer alternatiflerden iki kat daha hızlı hale gelmiştir.

Yeniden Sıralanmış Takas ile Ağları 12 Sıralama

Gcc 4.4.3 ile Rex Kerr teklifinin inanılmaz etkinliği beni meraklandırdı: 3 kat daha fazla bellek kullanımı olan bir program şubesiz sıralama ağlarından daha hızlı olabilir mi? Benim hipotezim, yazdıktan sonra okunan türden daha az bağımlılığa sahip olması ve x86'nın süperskalar talimat zamanlayıcısının daha iyi kullanılmasına izin vermesiydi. Bu bana bir fikir verdi: yazma bağımlılıklarından sonra okumayı en aza indirmek için swapları yeniden sıralayın. Daha basit bir ifadeyle: bunu SWAP(1, 2); SWAP(0, 2);yaptığınızda, ikisini de ortak bir bellek hücresine erişebildiğinden, ilk takasın bitmesini beklemeniz gerekir. Bunu yaptığınızda SWAP(1, 2); SWAP(4, 5);işlemci her ikisini de paralel olarak yürütebilir. Denedim ve beklendiği gibi çalışıyor, sıralama ağları yaklaşık% 10 daha hızlı çalışıyor.

Basit Değiştirme ile Ağları 12 Sıralama

Orijinal görevinden bir yıl sonra Steinar H. Gunderson, derleyiciyi alt etmeye ve takas kodunu basit tutmaya çalışmamamızı önerdi. Ortaya çıkan kod yaklaşık% 40 daha hızlı olduğu için gerçekten iyi bir fikir! Ayrıca, yine de bazı döngüleri yedekleyebilen x86 satır içi montaj kodu kullanılarak elle optimize edilmiş bir takas önerdi. En şaşırtıcı olanı (programcının psikolojisi üzerine ciltler diyor), bir yıl önce hiçbirinin bu takas versiyonunu denememiş olması. Test etmek için kullandığım kod burada . Diğerleri C hızlı takas yazmanın başka yollarını önerdiler, ancak iyi bir derleyiciye sahip basit performansla aynı performansları veriyorlar.

"En iyi" kod şu şekildedir:

static inline void sort6_sorting_network_simple_swap(int * d){
#define min(x, y) (x<y?x:y)
#define max(x, y) (x<y?y:x) 
#define SWAP(x,y) { const int a = min(d[x], d[y]); \
                    const int b = max(d[x], d[y]); \
                    d[x] = a; d[y] = b; }
    SWAP(1, 2);
    SWAP(4, 5);
    SWAP(0, 2);
    SWAP(3, 5);
    SWAP(0, 1);
    SWAP(3, 4);
    SWAP(1, 4);
    SWAP(0, 3);
    SWAP(2, 5);
    SWAP(1, 3);
    SWAP(2, 4);
    SWAP(2, 3);
#undef SWAP
#undef min
#undef max
}

Test setimize inanırsak (ve evet, oldukça zayıftır, sadece faydası kısa, basit ve neyi ölçtüğümüzü anlamak kolaydır), sonuçta ortaya çıkan kodun bir döngü için ortalama döngü sayısı 40 çevrimin altındadır ( 6 test yapılır). Bu, her bir swap'ı ortalama 4 döngüde koydu. Buna inanılmaz hızlı diyorum. Başka iyileştirme mümkün mü?


2
Ints üzerinde bazı kısıtlamalarınız var mı? Örneğin, herhangi bir 2 x, y için x-yve x+ytaşma veya taşmaya neden olmayacağını varsayabilir miyiz ?
Matthieu M.Mayıs

3
12 takas sıralama ağımı Paul'ün dalsız takas işleviyle birleştirmeyi denemelisiniz. Çözümü, tüm parametreleri bir diziye tek bir işaretçi yerine yığın üzerinde ayrı öğeler olarak geçirir. Bu da bir fark yaratabilir.
Daniel Stutzbach

2
64 bit üzerinde rdtsc doğru uygulanmasının, __asm__ volatile (".byte 0x0f, 0x31; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");GCC tek bir 64 bit kayıtta beklerken rdtsc'nin EDX: EAX cevabını koyması nedeniyle olduğunu unutmayın. -O3'te derleyerek hatayı görebilirsiniz. Ayrıca daha hızlı bir SWAP hakkında Paul R'ye yaptığım yoruma bakın.
Paolo Bonzini

3
@Tyler: Şube olmadan montaj seviyesinde nasıl uygularsınız?
Loren Pechtel

4
@Loren: sırasıyla büyük veya küçük olmasına bağlı olarak CMP EAX, EBX; SBB EAX, EAX0 veya 0xFFFFFFFF değerini koyacaktır . "ödünç ile çıkarma", karşılığı ("taşıma ile ekleme"); Durumunuz başvurmak bit olan taşıma bit. Sonra tekrar, bunu hatırlıyorum ve & Pentium 4 vs. üzerine üretilen iş korkunç gecikme vardı ve ve iki kez hala Core işlemciler üzerinde olarak yavaş. 80386'dan beri koşullu mağaza ve koşullu taşıma talimatları da var, ancak bunlar da yavaş. EAXEAXEBXSBBADCADCSBBADDSUBSETccCMOVcc
j_random_hacker

Yanıtlar:


162

Herhangi bir optimizasyon için test etmek, test etmek, test etmek her zaman en iyisidir. En azından ağları ve ekleme türlerini sıralamayı denerdim. Bahse girseydim, paramı geçmiş deneyime dayanarak ekleme türüne koyardım.

Girdi verileri hakkında bir şey biliyor musunuz? Bazı algoritmalar belirli veri türleriyle daha iyi performans gösterir. Örneğin, ekleme sıralaması sıralı veya neredeyse sıralanmış verilerde daha iyi performans gösterir, bu nedenle neredeyse sıralanmış verilerin ortalamanın üstünde bir olasılık varsa daha iyi bir seçim olacaktır.

Gönderdiğiniz algoritma ekleme türüne benzer, ancak daha fazla karşılaştırma pahasına takas sayısını en aza indirdiğiniz anlaşılıyor. Bununla birlikte, karşılaştırmalar takaslardan çok daha pahalıdır, çünkü dallar talimat boru hattının durmasına neden olabilir.

İşte bir ekleme sıralama uygulaması:

static __inline__ int sort6(int *d){
        int i, j;
        for (i = 1; i < 6; i++) {
                int tmp = d[i];
                for (j = i; j >= 1 && tmp < d[j-1]; j--)
                        d[j] = d[j-1];
                d[j] = tmp;
        }
}

İşte bu şekilde bir sıralama ağı oluşturacağım. İlk olarak, uygun uzunlukta bir ağ için minimum bir SWAP makroları kümesi oluşturmak için bu siteyi kullanın. Bunu bir işlevde sarmak bana şunu verir:

static __inline__ int sort6(int * d){
#define SWAP(x,y) if (d[y] < d[x]) { int tmp = d[x]; d[x] = d[y]; d[y] = tmp; }
    SWAP(1, 2);
    SWAP(0, 2);
    SWAP(0, 1);
    SWAP(4, 5);
    SWAP(3, 5);
    SWAP(3, 4);
    SWAP(0, 3);
    SWAP(1, 4);
    SWAP(2, 5);
    SWAP(2, 4);
    SWAP(1, 3);
    SWAP(2, 3);
#undef SWAP
}

9
+1: güzel, yukarıdaki el kodlu ve ampirik olarak türetilmiş ağımdaki 13 yerine 12 borsa ile yaptınız. Sizin için ağlar oluşturan siteye bağlantı için yapabilseydim, şimdi başka bir +1 verirdim.
Paul R

9
Bu, isteklerin çoğunun küçük boyutlu diziler olmasını bekliyorsanız, genel amaçlı bir sıralama işlevi için harika bir fikirdir. Bu yordamı kullanarak, en iyileştirmek istediğiniz durumlar için bir switch deyimi kullanın; varsayılan durumun bir kütüphane sıralama işlevi kullanmasına izin verin.
Mark Ransom

5
@ Mark İyi bir kütüphane sıralama işlevi zaten küçük diziler için hızlı bir yola sahip olacaktır. Birçok modern kütüphane, tekrarlayan InsertionSort'a geçiş yapan özyinelemeli bir QuickSort veya MergeSort kullanır n < SMALL_CONSTANT.
Daniel Stutzbach

3
@Mark Bir C kütüphanesi sıralama işlevi, karşılaştırma işlemini bir işlev bekçisi aracılığıyla belirtmenizi gerektirir. Her karşılaştırma için bir işlev çağırmanın yükü çok büyüktür. Genellikle, bu hala gitmek için en temiz yoldur, çünkü bu programda nadiren kritik bir yoldur. Ancak, kritik yol buysa, tamsayıları ve tam olarak 6 tanesini sıraladığımızı biliyorsanız gerçekten çok daha hızlı sıralama yapabiliriz. :)
Daniel Stutzbach

7
@tgwh: XOR takası neredeyse her zaman kötü bir fikirdir.
Paul R

63

Sıralama ağlarını kullanan bir uygulama :

inline void Sort2(int *p0, int *p1)
{
    const int temp = min(*p0, *p1);
    *p1 = max(*p0, *p1);
    *p0 = temp;
}

inline void Sort3(int *p0, int *p1, int *p2)
{
    Sort2(p0, p1);
    Sort2(p1, p2);
    Sort2(p0, p1);
}

inline void Sort4(int *p0, int *p1, int *p2, int *p3)
{
    Sort2(p0, p1);
    Sort2(p2, p3);
    Sort2(p0, p2);  
    Sort2(p1, p3);  
    Sort2(p1, p2);  
}

inline void Sort6(int *p0, int *p1, int *p2, int *p3, int *p4, int *p5)
{
    Sort3(p0, p1, p2);
    Sort3(p3, p4, p5);
    Sort2(p0, p3);  
    Sort2(p2, p5);  
    Sort4(p1, p2, p3, p4);  
}

Bunun için gerçekten çok verimli dalsız minve maxuygulamalara ihtiyacınız var , çünkü bu kodun etkili bir şekilde kaynaması - bir dizi işlem minve maxişlem (toplamda 13 adet). Bunu okuyucu için bir egzersiz olarak bırakıyorum.

Bu uygulamanın kendini vektörleştirmeye (örneğin, SIMD - çoğu SIMD ISA'sının vektör min / maks komutları vardır) ve ayrıca GPU uygulamalarına (örneğin CUDA - dalsız olması, çözgü sapması vb. İle ilgili herhangi bir sorun bulunmadığını) unutmayın.

Ayrıca bkz: Çok küçük listeyi sıralamak için hızlı algoritma uygulaması


1
Min / maks için bazı bit saldırıları için: graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
Rubys

1
@ Paul: gerçek CUDA kullanım bağlamında, kesinlikle en iyi cevaptır. Golf x64 bağlamında da (ve ne kadar) olup olmadığını kontrol edeceğim ve sonucu yayınlayacağım.
kriss

1
Sort3(a+b+c)-(min+max)merkezi sayı olduğunu fark ederseniz (çoğu mimaride, yine de) daha hızlı olurdu .
Rex Kerr

1
@Rex: Anlıyorum - iyi görünüyor. AltiVec ve SSE gibi SIMD mimarileri için aynı sayıda komut döngüsü olacaktır (maks ve min, toplama / çıkarma gibi tek döngü talimatlarıdır), ancak normal bir skaler CPU için yönteminiz daha iyi görünür.
Paul R

2
Ben koşullu hareket talimatları ile GCC optimize min izin verirseniz ben% 33 hızlanma olsun: #define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }. Burada?: D [y] için kullanmıyorum çünkü biraz daha kötü performans veriyor, ama neredeyse gürültüde.
Paolo Bonzini

45

Bunlar tamsayılar ve karşılaştırmalar hızlı olduğundan, neden her birinin sıralama düzenini doğrudan hesaplamıyorsunuz:

inline void sort6(int *d) {
  int e[6];
  memcpy(e,d,6*sizeof(int));
  int o0 = (d[0]>d[1])+(d[0]>d[2])+(d[0]>d[3])+(d[0]>d[4])+(d[0]>d[5]);
  int o1 = (d[1]>=d[0])+(d[1]>d[2])+(d[1]>d[3])+(d[1]>d[4])+(d[1]>d[5]);
  int o2 = (d[2]>=d[0])+(d[2]>=d[1])+(d[2]>d[3])+(d[2]>d[4])+(d[2]>d[5]);
  int o3 = (d[3]>=d[0])+(d[3]>=d[1])+(d[3]>=d[2])+(d[3]>d[4])+(d[3]>d[5]);
  int o4 = (d[4]>=d[0])+(d[4]>=d[1])+(d[4]>=d[2])+(d[4]>=d[3])+(d[4]>d[5]);
  int o5 = 15-(o0+o1+o2+o3+o4);
  d[o0]=e[0]; d[o1]=e[1]; d[o2]=e[2]; d[o3]=e[3]; d[o4]=e[4]; d[o5]=e[5];
}

@Rex: gcc -O1 ile 1000 döngünün altında, oldukça hızlı ama sıralama ağından daha yavaş. Kodu geliştirmek için bir fikrin var mı? Belki dizi kopyasını önleyebilirsek ...
kriss

@kriss: -O2 ile benim için sıralama ağından daha hızlı. -O2'nin iyi olmamasının bir nedeni var mı, yoksa -O2'de sizin için daha yavaş mı? Belki de makine mimarisinde bir fark var mı?
Rex Kerr

1
@Rex: üzgünüm, ilk bakışta> vs> = desenini kaçırdım. Her durumda çalışır.
kriss

3
@ kriss: Aha. Bu tamamen şaşırtıcı değil - etrafta yüzen birçok değişken var ve kayıtlarda dikkatli bir şekilde sıralanmalı ve önbelleğe alınmalıdır.
Rex Kerr

2
@SSpoke 0+1+2+3+4+5=15Bunlardan biri eksik olduğu için, 15 eksi geri kalanının toplamı eksik olanı verir
Glenn Teitelbaum

35

Görünüşe göre partiye bir yıl geç kaldım, ama başlıyoruz ...

Gcc 4.5.2 tarafından üretilen montajı inceleyerek her takas için yüklerin ve depoların yapıldığını gözlemledim, ki bu gerçekten gerekli değil. 6 değeri yazmaçlara yüklemek, sıralamak ve tekrar belleğe kaydetmek daha iyi olur. Mağazalardaki yüklerin olabildiğince yakın olmasını emrettim, ilk önce kayıtlara ihtiyaç duyulur ve son kullanılır. Steinar H. Gunderson'un SWAP makrosunu da kullandım. Güncelleme: Paolo Bonzini'nin gcc'nin Gunderson'ınkine benzer bir şeye dönüştüğü SWAP makrosuna geçtim, ancak gcc, açık bir montaj olarak verilmediği için talimatları daha iyi sipariş edebiliyor.

Daha iyi bir sipariş olsa da, en iyi performans gösteren yeniden sıralı takas ağıyla aynı takas sırasını kullandım. Biraz daha zaman bulursam bir grup permütasyon üretip test edeceğim.

Test kodunu 4000'den fazla diziyi dikkate alacak şekilde değiştirdim ve her birini sıralamak için gereken ortalama döngü sayısını gösterdim. Bir i5-650'de ~ 65.3 döngü / sıralama (-O1 kullanarak, -O2 ve -O3 kullanan) yeniden sıralanan sıralama ağına kıyasla ~ 34.1 döngü / sıralama (-O3 kullanarak) alıyorum.

#include <stdio.h>

static inline void sort6_fast(int * d) {
#define SWAP(x,y) { int dx = x, dy = y, tmp; tmp = x = dx < dy ? dx : dy; y ^= dx ^ tmp; }
    register int x0,x1,x2,x3,x4,x5;
    x1 = d[1];
    x2 = d[2];
    SWAP(x1, x2);
    x4 = d[4];
    x5 = d[5];
    SWAP(x4, x5);
    x0 = d[0];
    SWAP(x0, x2);
    x3 = d[3];
    SWAP(x3, x5);
    SWAP(x0, x1);
    SWAP(x3, x4);
    SWAP(x1, x4);
    SWAP(x0, x3);
    d[0] = x0;
    SWAP(x2, x5);
    d[5] = x5;
    SWAP(x1, x3);
    d[1] = x1;
    SWAP(x2, x4);
    d[4] = x4;
    SWAP(x2, x3);
    d[2] = x2;
    d[3] = x3;

#undef SWAP
#undef min
#undef max
}

static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile ("rdtsc; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");
    return x;
}

void ran_fill(int n, int *a) {
    static int seed = 76521;
    while (n--) *a++ = (seed = seed *1812433253 + 12345);
}

#define NTESTS 4096
int main() {
    int i;
    int d[6*NTESTS];
    ran_fill(6*NTESTS, d);

    unsigned long long cycles = rdtsc();
    for (i = 0; i < 6*NTESTS ; i+=6) {
        sort6_fast(d+i);
    }
    cycles = rdtsc() - cycles;
    printf("Time is %.2lf\n", (double)cycles/(double)NTESTS);

    for (i = 0; i < 6*NTESTS ; i+=6) {
        if (d[i+0] > d[i+1] || d[i+1] > d[i+2] || d[i+2] > d[i+3] || d[i+3] > d[i+4] || d[i+4] > d[i+5])
            printf("d%d : %d %d %d %d %d %d\n", i,
                    d[i+0], d[i+1], d[i+2],
                    d[i+3], d[i+4], d[i+5]);
    }
    return 0;
}

Ben de test paketi değiştirdi de sıralama başına saatler rapor ve daha fazla test çalıştırmak için (cmp işlevi de tamsayı taşması işlemek için güncellendi), İşte bazı farklı mimariler sonuçları. Bir AMD işlemci üzerinde test yapmaya çalıştım ancak rdtsc, sahip olduğum X6 1100T'de güvenilir değil.

Clarkdale (i5-650)
==================
Direct call to qsort library function      635.14   575.65   581.61   577.76   521.12
Naive implementation (insertion sort)      538.30   135.36   134.89   240.62   101.23
Insertion Sort (Daniel Stutzbach)          424.48   159.85   160.76   152.01   151.92
Insertion Sort Unrolled                    339.16   125.16   125.81   129.93   123.16
Rank Order                                 184.34   106.58   54.74    93.24    94.09
Rank Order with registers                  127.45   104.65   53.79    98.05    97.95
Sorting Networks (Daniel Stutzbach)        269.77   130.56   128.15   126.70   127.30
Sorting Networks (Paul R)                  551.64   103.20   64.57    73.68    73.51
Sorting Networks 12 with Fast Swap         321.74   61.61    63.90    67.92    67.76
Sorting Networks 12 reordered Swap         318.75   60.69    65.90    70.25    70.06
Reordered Sorting Network w/ fast swap     145.91   34.17    32.66    32.22    32.18

Kentsfield (Core 2 Quad)
========================
Direct call to qsort library function      870.01   736.39   723.39   725.48   721.85
Naive implementation (insertion sort)      503.67   174.09   182.13   284.41   191.10
Insertion Sort (Daniel Stutzbach)          345.32   152.84   157.67   151.23   150.96
Insertion Sort Unrolled                    316.20   133.03   129.86   118.96   105.06
Rank Order                                 164.37   138.32   46.29    99.87    99.81
Rank Order with registers                  115.44   116.02   44.04    116.04   116.03
Sorting Networks (Daniel Stutzbach)        230.35   114.31   119.15   110.51   111.45
Sorting Networks (Paul R)                  498.94   77.24    63.98    62.17    65.67
Sorting Networks 12 with Fast Swap         315.98   59.41    58.36    60.29    55.15
Sorting Networks 12 reordered Swap         307.67   55.78    51.48    51.67    50.74
Reordered Sorting Network w/ fast swap     149.68   31.46    30.91    31.54    31.58

Sandy Bridge (i7-2600k)
=======================
Direct call to qsort library function      559.97   451.88   464.84   491.35   458.11
Naive implementation (insertion sort)      341.15   160.26   160.45   154.40   106.54
Insertion Sort (Daniel Stutzbach)          284.17   136.74   132.69   123.85   121.77
Insertion Sort Unrolled                    239.40   110.49   114.81   110.79   117.30
Rank Order                                 114.24   76.42    45.31    36.96    36.73
Rank Order with registers                  105.09   32.31    48.54    32.51    33.29
Sorting Networks (Daniel Stutzbach)        210.56   115.68   116.69   107.05   124.08
Sorting Networks (Paul R)                  364.03   66.02    61.64    45.70    44.19
Sorting Networks 12 with Fast Swap         246.97   41.36    59.03    41.66    38.98
Sorting Networks 12 reordered Swap         235.39   38.84    47.36    38.61    37.29
Reordered Sorting Network w/ fast swap     115.58   27.23    27.75    27.25    26.54

Nehalem (Xeon E5640)
====================
Direct call to qsort library function      911.62   890.88   681.80   876.03   872.89
Naive implementation (insertion sort)      457.69   236.87   127.68   388.74   175.28
Insertion Sort (Daniel Stutzbach)          317.89   279.74   147.78   247.97   245.09
Insertion Sort Unrolled                    259.63   220.60   116.55   221.66   212.93
Rank Order                                 140.62   197.04   52.10    163.66   153.63
Rank Order with registers                  84.83    96.78    50.93    109.96   54.73
Sorting Networks (Daniel Stutzbach)        214.59   220.94   118.68   120.60   116.09
Sorting Networks (Paul R)                  459.17   163.76   56.40    61.83    58.69
Sorting Networks 12 with Fast Swap         284.58   95.01    50.66    53.19    55.47
Sorting Networks 12 reordered Swap         281.20   96.72    44.15    56.38    54.57
Reordered Sorting Network w/ fast swap     128.34   50.87    26.87    27.91    28.02

Kayıt değişkenleri fikriniz Rex Kerr'in "Sıralama Düzeni" çözümüne uygulanmalıdır. Bu en hızlı olmalı ve belki de o zaman -O3optimizasyon karşı üretken olmayacaktır.
cdunn2001

1
@ cdunn2001 Sadece test ettim, gelişme görmüyorum (-O0 ve -Os'da birkaç döngü hariç). Asm bakıldığında gcc zaten kayıt kullanmak ve memcpy çağrısını ortadan kaldırmak için anlamaya başardı görünüyor.
Kevin Stock

Size test paketine basit takas sürümü eklemek ister misiniz, sanırım elle optimize montaj hızlı takas ile karşılaştırmak ilginç olabilir.
kriss

1
Kodunuz hala Gunderson'un takasını kullanıyor, benim olacak #define SWAP(x,y) { int oldx = x; x = x < y ? x : y; y ^= oldx ^ x; }.
Paolo Bonzini

@Paolo Bonzini: Evet, seninle bir test örneği eklemeyi planlıyorum, henüz vaktim olmadı. Ancak satır içi montajdan kaçınacağım.
kriss

15

Bu soruyu birkaç gün önce Google'dan tökezledim, çünkü 6 tamsayı olan bir sabit uzunluk dizisini hızlı bir şekilde sıralamaya ihtiyacım vardı. Ancak benim durumumda, tamsayılarım sadece 8 bit (32 yerine) ve sadece C'yi kullanmak için kesin bir gereksinimim yok.

Montajda karşılaştırma ve takas işlemlerini mümkün olduğunca vektörleştirmek için SSE kullanan bir ağ türünün bir varyantını uyguladım. Diziyi tamamen sıralamak altı "geçiş" gerektirir. PCMPGTB (vektörleştirilmiş karşılaştırma) sonuçlarını doğrudan bir PADDB (vectorized add) ve bazı durumlarda da PAND (bitwise AND) komutunu kullanarak PSHUFB (vectorized swap) parametrelerini karıştırmak için yeni bir mekanizma kullandım.

Bu yaklaşımın aynı zamanda gerçekten dalsız bir işlev vermesinin yan etkisi de vardı . Herhangi bir atlama talimatı yoktur.

Bu uygulamanın, şu anda sorunun en hızlı seçeneği olarak işaretlenen uygulamadan yaklaşık% 38 daha hızlı olduğu görülmektedir ("Ağları 12 Basit Değiştirmeyle Sıralama"). Bu uygulamayı, chartestim sırasında dizi öğelerini kullanacak , karşılaştırmayı adil hale getirmek için değiştirdim.

Bu yaklaşımın 16 öğeye kadar herhangi bir dizi boyutuna uygulanabileceğini unutmayın. Alternatiflere göre göreceli hız avantajının daha büyük diziler için daha da büyümesini bekliyorum.

Kod, SSSE3 bulunan x86_64 işlemciler için MASM'de yazılmıştır. İşlev, "yeni" Windows x64 çağrı kuralını kullanır. İşte burada...

PUBLIC simd_sort_6

.DATA

ALIGN 16

pass1_shuffle   OWORD   0F0E0D0C0B0A09080706040503010200h
pass1_add       OWORD   0F0E0D0C0B0A09080706050503020200h
pass2_shuffle   OWORD   0F0E0D0C0B0A09080706030405000102h
pass2_and       OWORD   00000000000000000000FE00FEFE00FEh
pass2_add       OWORD   0F0E0D0C0B0A09080706050405020102h
pass3_shuffle   OWORD   0F0E0D0C0B0A09080706020304050001h
pass3_and       OWORD   00000000000000000000FDFFFFFDFFFFh
pass3_add       OWORD   0F0E0D0C0B0A09080706050404050101h
pass4_shuffle   OWORD   0F0E0D0C0B0A09080706050100020403h
pass4_and       OWORD   0000000000000000000000FDFD00FDFDh
pass4_add       OWORD   0F0E0D0C0B0A09080706050403020403h
pass5_shuffle   OWORD   0F0E0D0C0B0A09080706050201040300h
pass5_and       OWORD 0000000000000000000000FEFEFEFE00h
pass5_add       OWORD   0F0E0D0C0B0A09080706050403040300h
pass6_shuffle   OWORD   0F0E0D0C0B0A09080706050402030100h
pass6_add       OWORD   0F0E0D0C0B0A09080706050403030100h

.CODE

simd_sort_6 PROC FRAME

    .endprolog

    ; pxor xmm4, xmm4
    ; pinsrd xmm4, dword ptr [rcx], 0
    ; pinsrb xmm4, byte ptr [rcx + 4], 4
    ; pinsrb xmm4, byte ptr [rcx + 5], 5
    ; The benchmarked 38% faster mentioned in the text was with the above slower sequence that tied up the shuffle port longer.  Same on extract
    ; avoiding pins/extrb also means we don't need SSE 4.1, but SSSE3 CPUs without SSE4.1 (e.g. Conroe/Merom) have slow pshufb.
    movd    xmm4, dword ptr [rcx]
    pinsrw  xmm4,  word ptr [rcx + 4], 2  ; word 2 = bytes 4 and 5


    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass1_shuffle]
    pcmpgtb xmm5, xmm4
    paddb xmm5, oword ptr [pass1_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass2_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass2_and]
    paddb xmm5, oword ptr [pass2_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass3_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass3_and]
    paddb xmm5, oword ptr [pass3_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass4_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass4_and]
    paddb xmm5, oword ptr [pass4_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass5_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass5_and]
    paddb xmm5, oword ptr [pass5_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass6_shuffle]
    pcmpgtb xmm5, xmm4
    paddb xmm5, oword ptr [pass6_add]
    pshufb xmm4, xmm5

    ;pextrd dword ptr [rcx], xmm4, 0    ; benchmarked with this
    ;pextrb byte ptr [rcx + 4], xmm4, 4 ; slower version
    ;pextrb byte ptr [rcx + 5], xmm4, 5
    movd   dword ptr [rcx], xmm4
    pextrw  word ptr [rcx + 4], xmm4, 2  ; x86 is little-endian, so this is the right order

    ret

simd_sort_6 ENDP

END

Bunu yürütülebilir bir nesneye derleyebilir ve C projenize bağlayabilirsiniz. Visual Studio'da bunun nasıl yapılacağıyla ilgili yönergeler için bu makaleyi okuyabilirsiniz . İşlevi C kodunuzdan çağırmak için aşağıdaki C prototipini kullanabilirsiniz:

void simd_sort_6(char *values);

Sizinkileri diğer montaj düzeyindeki tekliflerle karşılaştırmak zorlayıcı olabilir. Karşılaştırılan uygulama performansları bunları içermez. SSE kullanmak zaten kulağa hoş geliyor.
kriss

Gelecekteki araştırmaların başka bir alanı, yeni Intel AVX talimatlarının bu soruna uygulanması olacaktır. Daha büyük 256 bit vektörler 8 DWORD'ye sığacak kadar büyüktür.
Joe Crivello

1
Bunun yerine pxor / pinsrd xmm4, mem, 0, sadece kullanın movd!
Peter Cordes

14

Test kodu oldukça kötü; ilk diziyi taşar (burada derleyici uyarılarını okumazlar mı?), printf yanlış öğeleri yazdırıyor, rdtsc için .byte'yi iyi bir nedenle kullanmıyor, sadece bir çalışma var (!), kontrol eden hiçbir şey yok sonuç gerçekten doğrudur (bu yüzden yanlış bir şeye “optimize etmek” çok kolaydır), dahil edilen testler çok basittir (negatif sayı yok?) ve derleyicinin tüm işlevi sadece ölü kod olarak atmasını engelleyecek hiçbir şey yoktur.

Bununla birlikte, bitonik ağ çözümü üzerinde iyileştirmek de oldukça kolaydır; min / max / SWAP öğelerini

#define SWAP(x,y) { int tmp; asm("mov %0, %2 ; cmp %1, %0 ; cmovg %1, %0 ; cmovg %2, %1" : "=r" (d[x]), "=r" (d[y]), "=r" (tmp) : "0" (d[x]), "1" (d[y]) : "cc"); }

ve benim için yaklaşık% 65 daha hızlı çıkıyor (-O2, amd64, Core i7 ile Debian gcc 4.4.5).


Tamam, test kodu zayıf. Geliştirmek için çekinmeyin. Ve evet, montaj kodunu kullanabilirsiniz. Neden tüm yol boyunca gitmiyor ve tam x86 birleştirici kullanarak kod? Biraz daha az taşınabilir olabilir, ancak neden rahatsız edelim?
kriss

Dizi taşmasını fark ettiğiniz için teşekkürler, düzelttim. Diğer insanlar fark etmemiş olabilirler çünkü kod taşmak / yapıştırmak için bağlantıya tıklanmış, taşma olmayan.
kriss

4
Aslında toplayıcıya bile ihtiyacınız yok; tüm akıllı hileleri bırakırsanız, GCC diziyi tanıyacak ve sizin için koşullu hareketleri ekleyecektir: #define min (a, b) ((a <b)? a: b) #define max (a, b) ( (a <b)? b: a) # tanım SWAP (x, y) {int a = dak (d [x], d [y]); int b = maks (d [x], d [y]); d [x] = a; d [y] = b; } Satır içi asm varyantından belki yüzde birkaç daha yavaş ortaya çıkıyor, ancak uygun kıyaslama eksikliği göz önüne alındığında bunu söylemek zor.
Steinar H.Gunderson

3
… Ve son olarak, sayılarınız yüzüyorsa ve NaN vb. İçin endişelenmenize gerek yoksa, GCC bunu minss / maxss SSE talimatlarına dönüştürebilir, bu da ~% 25 daha hızlıdır. Moral: Zekice bitfilm hilelerini bırakın ve derleyicinin işini yapmasına izin verin. :-)
Steinar H. Gunderson

13

Gerçekten sağlanan takas makro gibi:

#define min(x, y) (y ^ ((x ^ y) & -(x < y)))
#define max(x, y) (x ^ ((x ^ y) & -(x < y)))
#define SWAP(x,y) { int tmp = min(d[x], d[y]); d[y] = max(d[x], d[y]); d[x] = tmp; }

(İyi bir derleyicinin yapabileceği bir gelişme) görüyorum:

#define SWAP(x,y) { int tmp = ((x ^ y) & -(y < x)); y ^= tmp; x ^= tmp; }

Min ve max'ın nasıl çalıştığını not eder ve ortak alt ifadeyi açıkça çekeriz. Bu, min ve maks makroları tamamen ortadan kaldırır.


Bu onları geri alır, d [y] 'nin maksimum değeri aldığını, yani x ^ (ortak alt ifade) olduğunu unutmayın.
Kevin Stock

Aynı şeyi fark ettim; Uygulamanızın (aynı için ) d[x]yerine ve burada eşitsizlik için (evet, min / max kodundan farklı ) istediğiniz doğru olmasını düşünüyorum . xyd[y] < d[x]
Tyler

Takasınızı denedim, ancak yerel optimizasyonun daha büyük düzeyde olumsuz etkileri var (sanırım bağımlılıklar getiriyor). Ve sonuç diğer takastan daha yavaştır. Ancak önerilen yeni çözüm ile görebileceğiniz gibi, gerçekten de takas optimize etmek için çok fazla performans vardı.
kriss

12

Asla derleyici tarafından üretilen montajı karşılaştırmadan ve min / maks'i optimize etmeyin. GCC'yi min.

#define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }

(Test kodunda 280'e 420 döngü). ?: İle maksimum yapmak aşağı yukarı aynıdır, gürültüde neredeyse kaybolur, ancak yukarıdakiler biraz daha hızlıdır. Bu SWAP, hem GCC hem de Clang ile daha hızlıdır.

Derleyiciler ayrıca kayıt tahsisi ve takma ad analizinde istisnai bir iş çıkarıyor, d [x] 'i etkin bir şekilde yerel değişkenlere öna doğru hareket ettiriyor ve sadece sonunda belleğe geri kopyalıyor. Aslında, tamamen yerel değişkenlerle (örneğin,d0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3], d4 = d[4], d5 = d[5] ) . Bunu yazıyorum çünkü güçlü optimizasyon varsayıyorsunuz ve derleyiciyi min / maks. :)

Bu arada, Clang ve GCC'yi denedim. Aynı optimizasyonu yaparlar, ancak zamanlama farklılıkları nedeniyle, ikisinin sonuçlarda bazı farklılıkları vardır, hangisinin daha hızlı veya daha yavaş olduğunu söyleyemez. GCC sıralama ağlarında daha hızlıdır, ikinci dereceden türlerde Clang.

Sadece bütünlük için, açılmamış kabarcık sıralama ve yerleştirme çeşitleri de mümkündür. İşte kabarcık sıralama:

SWAP(0,1); SWAP(1,2); SWAP(2,3); SWAP(3,4); SWAP(4,5);
SWAP(0,1); SWAP(1,2); SWAP(2,3); SWAP(3,4);
SWAP(0,1); SWAP(1,2); SWAP(2,3);
SWAP(0,1); SWAP(1,2);
SWAP(0,1);

ve ekleme türü şöyledir:

//#define ITER(x) { if (t < d[x]) { d[x+1] = d[x]; d[x] = t; } }
//Faster on x86, probably slower on ARM or similar:
#define ITER(x) { d[x+1] ^= t < d[x] ? d[x] ^ d[x+1] : 0; d[x] = t < d[x] ? t : d[x]; }
static inline void sort6_insertion_sort_unrolled_v2(int * d){
    int t;
    t = d[1]; ITER(0);
    t = d[2]; ITER(1); ITER(0);
    t = d[3]; ITER(2); ITER(1); ITER(0);
    t = d[4]; ITER(3); ITER(2); ITER(1); ITER(0);
    t = d[5]; ITER(4); ITER(3); ITER(2); ITER(1); ITER(0);

Bu yerleştirme sıralaması Daniel Stutzbach'ınkinden daha hızlıdır ve özellikle GPU veya tahminli bir bilgisayarda iyidir, çünkü ITER sadece 3 talimatla yapılabilir (SWAP için 4'e karşı). Örneğin, t = d[2]; ITER(1); ITER(0);ARM montajındaki çizgi:

    MOV    r6, r2
    CMP    r6, r1
    MOVLT  r2, r1
    MOVLT  r1, r6
    CMP    r6, r0
    MOVLT  r1, r0
    MOVLT  r0, r6

Altı öğe için ekleme sıralama, sıralama ağı ile rekabet edebilir (12 swap vs 15 yineleme, 4 talimatları / swap vs. 3 talimatları / yinelemeyi dengeler); kabarcık tür tabii ki daha yavaş. Ancak sıralama ağları O (n günlüğü n) olurken ekleme sıralaması O (n ^ 2) olduğundan, boyut büyüdüğünde doğru olmaz.


1
Aşağı yukarı ilgili: GCC'ye optimizasyonu doğrudan derleyiciye uygulayabilmesi için bir rapor gönderdim . Yapacağından emin değilim, ama en azından nasıl geliştiğini takip edebilirsiniz.
Morwenn

11

Test paketini tanımlayamadığım bir PPC mimari makinesine taşıdım (koda dokunmak zorunda kalmadım, sadece testin tekrarlarını artırın, sonuçları modlarla kirletmekten kaçınmak ve x86'ya özgü rdtsc'yi değiştirmek için 8 test örneği kullanın):

Qsort kütüphane işlevine doğrudan çağrı : 101

Saf uygulama (ekleme türü) : 299

Ekleme Sıralaması (Daniel Stutzbach) : 108

Ekleme Sıralama Açıldı : 51

Sıralama Ağları (Daniel Stutzbach) : 26

Sıralama Ağları (Paul R) : 85

Hızlı Değiştirme ile Ağları Sıralama 12 : 117

Sıralama Ağları 12 yeniden sıralandı Takas : 116

Sıralama Düzeni : 56


1
Gerçekten ilginç. Şubesiz takas PPC'de kötü bir fikir gibi görünüyor. Derleyici ile ilgili bir etki de olabilir. Hangisi kullanıldı?
kriss

Onun gcc derleyicisinin bir dalı - min, max mantık muhtemelen dalsız değildir - demontajı inceler ve size bildiririm, ancak derleyici x <y gibi bir şey olmadan yeterince zeki değilse, hala bir dal haline gelirse - x86 / x64 CMOV komutu bundan kaçınabilir, ancak PPC'de sabit nokta değerleri için böyle bir talimat yoktur, sadece yüzer. Bu yarınla ​​başa çıkabilirim ve size haber verebilirim - Winamp AVS kaynağında çok daha basit bir dalsız min / maks olduğunu hatırlıyorum, ancak sadece şamandıralar içindi - ama gerçekten dalsız bir yaklaşıma doğru iyi bir başlangıç ​​olabilir.
jheriko

4
Burada işaretsiz girişli PPC için maksimum bir dalsız dakika / olup: subfc r5,r4,r3; subfe r6,r6,r6; andc r6,r5,r6; add r4,r6,r4; subf r3,r6,r3. r3 / r4 girişlerdir, r5 / r6 çizik kayıtlarıdır, r3 çıkışında min ve r4 maks. Elle düzgün bir şekilde programlanabilir olmalıdır. Ben 4-talimatları min ve max dizileri başlayarak ve birleştirilebilir iki el ile bakarak GNU süper hızlandırıcı ile buldum. İmzalı girdiler için, elbette başlangıçta tüm öğelere 0x80000000 ekleyebilir ve sonunda tekrar çıkarabilir ve sonra imzalanmamış gibi çalışabilirsiniz.
Paolo Bonzini

7

Bir XOR takas, takas işlevlerinizde yararlı olabilir.

void xorSwap (int *x, int *y) {
     if (*x != *y) {
         *x ^= *y;
         *y ^= *x;
         *x ^= *y;
     }
 }

Eğer kodunuzda çok fazla farklılaşmaya neden olabilir, ancak tüm ints'larınızın benzersiz olduğuna dair bir garantiniz varsa bu kullanışlı olabilir.


1
xveya swap da eşit değerler için çalışır ... x ^ = y x'i 0 olarak ayarlar, y ^ = x
y'yi

11
O zaman değil iştir xve yaynı konuma gelin.
Ocaklar

Her neyse, sıralama ağlarıyla kullanıldığında, asla x ve y ile aynı yere işaret etmiyoruz. Dalsız takas ile aynı etkiyi elde etmek için daha büyük olan testlerden kaçınmanın bir yolu hala var. Bunu başarmak için bir fikrim var.
kriss

5

Bunu denemeyi ve bu örneklerden öğrenmeyi dört gözle bekliyorum, ancak önce 1 GB DDR RAM ile 1,5 GHz PPC Powerbook G4'ümden bazı zamanlamalar. (PPC için benzer bir rdtsc benzeri zamanlayıcıyı http://www.mcs.anl.gov/~kazutomo/rdtsc.html adresinden ödünç aldım. zamanlamalar için .) Programı birkaç kez çalıştırdım ve mutlak sonuçlar değişiyordu ama tutarlı bir şekilde en hızlı test "Ekleme Sıralaması (Daniel Stutzbach)" ve "Ekleme Sıralaması Açıldı" kısa bir süre oldu.

İşte son kez:

**Direct call to qsort library function** : 164
**Naive implementation (insertion sort)** : 138
**Insertion Sort (Daniel Stutzbach)**     : 85
**Insertion Sort Unrolled**               : 97
**Sorting Networks (Daniel Stutzbach)**   : 457
**Sorting Networks (Paul R)**             : 179
**Sorting Networks 12 with Fast Swap**    : 238
**Sorting Networks 12 reordered Swap**    : 236
**Rank Order**                            : 116

4

İşte bu iş parçacığına katkım: 6 üyeli int vektörü (valp) için benzersiz değerler içeren optimize edilmiş 1, 4 boşluk kabukları.

void shellsort (int *valp)
{      
  int c,a,*cp,*ip=valp,*ep=valp+5;

  c=*valp;    a=*(valp+4);if (c>a) {*valp=    a;*(valp+4)=c;}
  c=*(valp+1);a=*(valp+5);if (c>a) {*(valp+1)=a;*(valp+5)=c;}

  cp=ip;    
  do
  {
    c=*cp;
    a=*(cp+1);
    do
    {
      if (c<a) break;

      *cp=a;
      *(cp+1)=c;
      cp-=1;
      c=*cp;
    } while (cp>=valp);
    ip+=1;
    cp=ip;
  } while (ip<ep);
}

Çift çekirdekli Athlon M300 @ 2 Ghz (DDR2 bellek) bulunan HP dv7-3010so dizüstü bilgisayarımda 165 saat döngüsünde çalışıyor. Bu, her benzersiz dizinin zamanlamasından hesaplanan bir ortalamadır (toplamda 6! / 720). OpenWatcom 1.8 kullanarak Win32 için derlenmiştir. Döngü aslında bir ekleme sıralamasıdır ve 16 talimat / 37 bayt uzunluğundadır.

Derlemek için 64-bit ortamım yok.


Güzel. Ben daha uzun testsuite ekleyeceğim
kriss

3

Ekleme sıralaması burada oldukça rekabetçi ise, bir kabuk kabuğu denemenizi tavsiye ederim. Korkarım 6 element muhtemelen en iyileri arasında olamayacak kadar az, ama denemeye değer olabilir.

Örnek kod, test edilmemiş, ayıklanmamış, vb. Optimum bulmak için inc = 4 ve inc - = 3 dizisini ayarlamak istiyorsunuz (örneğin inc = 2, inc - = 1'i deneyin).

static __inline__ int sort6(int * d) {
    char j, i;
    int tmp;
    for (inc = 4; inc > 0; inc -= 3) {
        for (i = inc; i < 5; i++) {
            tmp = a[i];
            j = i;
            while (j >= inc && a[j - inc] > tmp) {
                a[j] = a[j - inc];
                j -= inc;
            }
            a[j] = tmp;
        }
    }
}

Bunun kazanacağını düşünmüyorum, ama eğer birisi 10 öğeyi sıralama hakkında bir soru gönderirse, kim bilir ...

Wikipedia'ya göre bu, sıralama ağlarıyla bile birleştirilebilir: Pratt, V (1979). Kabuk sıralaması ve sıralama ağları (Bilgisayar bilimlerinde üstün tezler). Çelenk. ISBN 0-824-04406-1


bazı uygulama önermek için çekinmeyin :-)
kriss

Teklif eklendi. Böceklerin tadını çıkarın.
gcp

3

Çok geç kaldığımı biliyorum, ama bazı farklı çözümleri denemekle ilgileniyordum. İlk olarak, o macunu temizledim, derledim ve bir depoya koydum. Bazılarının çıkmaması için istenmeyen çözümler çıkmaz olarak sakladım. Bunlar arasında x1> x2'nin bir kez hesaplanmasını sağlamaya çalışan ilk çözümüm de vardı. Optimizasyondan sonra, diğer basit versiyonlardan daha hızlı değildir.

Sıralama düzeni sıralamasının döngüsel bir sürümünü ekledim, çünkü bu çalışmanın kendi uygulamam 2-8 öğeyi sıralamak için olduğundan, değişken sayıda argüman olduğundan, bir döngü gereklidir. Bu yüzden sıralama ağı çözümlerini de göz ardı ettim.

Test kodu, yinelenenlerin doğru işlendiğini test etmedi, bu nedenle mevcut çözümlerin hepsi doğru olsa da, yinelenenlerin doğru bir şekilde işlendiğinden emin olmak için test koduna özel bir durum ekledim.

Sonra, tamamen AVX kayıtlarında bir ekleme türü yazdım. Makinemde diğer yerleştirme türlerine göre% 25 daha hızlı, ancak sıralama sırasına göre% 100 daha yavaş. Bunu sadece deney için yaptım ve yerleştirme türündeki dallanma nedeniyle bunun daha iyi olacağı beklentisi yoktu.

static inline void sort6_insertion_sort_avx(int* d) {
    __m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], 0, 0);
    __m256i index = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
    __m256i shlpermute = _mm256_setr_epi32(7, 0, 1, 2, 3, 4, 5, 6);
    __m256i sorted = _mm256_setr_epi32(d[0], INT_MAX, INT_MAX, INT_MAX,
            INT_MAX, INT_MAX, INT_MAX, INT_MAX);
    __m256i val, gt, permute;
    unsigned j;
     // 8 / 32 = 2^-2
#define ITER(I) \
        val = _mm256_permutevar8x32_epi32(src, _mm256_set1_epi32(I));\
        gt =  _mm256_cmpgt_epi32(sorted, val);\
        permute =  _mm256_blendv_epi8(index, shlpermute, gt);\
        j = ffs( _mm256_movemask_epi8(gt)) >> 2;\
        sorted = _mm256_blendv_epi8(_mm256_permutevar8x32_epi32(sorted, permute),\
                val, _mm256_cmpeq_epi32(index, _mm256_set1_epi32(j)))
    ITER(1);
    ITER(2);
    ITER(3);
    ITER(4);
    ITER(5);
    int x[8];
    _mm256_storeu_si256((__m256i*)x, sorted);
    d[0] = x[0]; d[1] = x[1]; d[2] = x[2]; d[3] = x[3]; d[4] = x[4]; d[5] = x[5];
#undef ITER
}

Sonra AVX kullanarak bir sıralama düzeni yazdım. Bu, diğer sıralama düzeni çözümlerinin hızıyla eşleşir, ancak daha hızlı değildir. Buradaki sorun, sadece AVX ile indeksleri hesaplayabilmem ve daha sonra bir indeks tablosu yapmam gerekiyor. Bunun nedeni, hesaplamanın kaynak tabanlı değil, hedef tabanlı olmasıdır. Bkz . Kaynak Tabanlı Endekslerden Hedef Tabanlı Endekslere Dönüştürme

static inline void sort6_rank_order_avx(int* d) {
    __m256i ror = _mm256_setr_epi32(5, 0, 1, 2, 3, 4, 6, 7);
    __m256i one = _mm256_set1_epi32(1);
    __m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], INT_MAX, INT_MAX);
    __m256i rot = src;
    __m256i index = _mm256_setzero_si256();
    __m256i gt, permute;
    __m256i shl = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 6, 6);
    __m256i dstIx = _mm256_setr_epi32(0,1,2,3,4,5,6,7);
    __m256i srcIx = dstIx;
    __m256i eq = one;
    __m256i rotIx = _mm256_setzero_si256();
#define INC(I)\
    rot = _mm256_permutevar8x32_epi32(rot, ror);\
    gt = _mm256_cmpgt_epi32(src, rot);\
    index = _mm256_add_epi32(index, _mm256_and_si256(gt, one));\
    index = _mm256_add_epi32(index, _mm256_and_si256(eq,\
                _mm256_cmpeq_epi32(src, rot)));\
    eq = _mm256_insert_epi32(eq, 0, I)
    INC(0);
    INC(1);
    INC(2);
    INC(3);
    INC(4);
    int e[6];
    e[0] = d[0]; e[1] = d[1]; e[2] = d[2]; e[3] = d[3]; e[4] = d[4]; e[5] = d[5];
    int i[8];
    _mm256_storeu_si256((__m256i*)i, index);
    d[i[0]] = e[0]; d[i[1]] = e[1]; d[i[2]] = e[2]; d[i[3]] = e[3]; d[i[4]] = e[4]; d[i[5]] = e[5];
}

Repo burada bulunabilir: https://github.com/eyepatchParrot/sort6/


1
Sen kullanabilirsiniz vmovmskpsbitscan (sağ shift etme işlemini ortadan kaldırmak (mutlu intrinsics tutmak için bir alçı ile) vektörler tamsayı üzerinde ffs) sonucu.
Peter Cordes

1
Bir cmpgtsonuca dayalı olarak 1'i maskelemek yerine çıkararak koşullu olarak 1 ekleyebilirsiniz set1(1). örneğin index = _mm256_sub_epi32(index, gt)yaparindex -= -1 or 0;
Peter Cordes

1
eq = _mm256_insert_epi32(eq, 0, I) bir öğeyi yazılı olarak derlerse (özellikle düşük 4 dışındaki öğelerde sıfırlamak için etkili bir yol değildir, çünkü vpinsrd yalnızca bir XMM hedefi ile kullanılabilir; 3'ten yüksek endekslerin taklit edilmesi gerekir). Bunun yerine, _mm256_blend_epi32( vpblendd) sıfırlanmış bir vektörle. vpblenddIntel CPU'larda bağlantı noktası 5 gerektiren bir karıştırmaya karşılık, herhangi bir bağlantı noktasında çalışan tek bir yönlendirme talimatıdır. ( agner.org/optimize ).
Peter Cordes

1
Ayrıca, rotaynı kaynaktan farklı karıştırmalı vektörler oluşturmayı ya da şerit geçişli bir karıştırmayla (3 döngü gecikme) tek bir dep zinciri yerine dönüşümlü olarak kullandığınız paralel olarak en az 2 dep zinciri çalıştırmayı düşünebilirsiniz . Bu ILP'yi tek bir çeşitte artıracaktır. 2 dep zinciri, vektör sabitlerinin sayısını makul bir sayıyla sınırlandırır, bir döndürme için yalnızca 2: 1 ve birleştirme için 2 döndürme adımı için bir.
Peter Cordes

2

Bu soru oldukça eski hale geliyor, ama aslında bugünlerde aynı sorunu çözmek zorunda kaldım: küçük dizileri sıralamak için hızlı algoritmalar. Bilgilerimi paylaşmanın iyi bir fikir olacağını düşündüm. İlk olarak sıralama ağlarını kullanmaya başlarken, 6 değerin her permütasyonunu sıralamak için yapılan toplam karşılaştırma sayısının sıralama ağlarından daha küçük ve ekleme sıralamasından daha küçük olduğu diğer algoritmaları bulmayı başardım. Takas sayısını saymadım; Ben kabaca eşdeğer (bazen biraz daha yüksek) olmasını beklenir.

Algoritma sort6algoritması kullanır sort4algoritma kullanırsort3 . İşte bazı hafif C ++ formunda uygulama (orijinal herhangi bir rastgele erişim yineleyici ve herhangi bir uygun karşılaştırma fonksiyonu ile çalışabilmesi için şablon ağırdır).

3 değeri sıralama

Aşağıdaki algoritma, kaydedilmemiş bir ekleme sıralamasıdır. İki takas (6 atama) yapılması gerektiğinde, bunun yerine 4 atama kullanır:

void sort3(int* array)
{
    if (array[1] < array[0]) {
        if (array[2] < array[0]) {
            if (array[2] < array[1]) {
                std::swap(array[0], array[2]);
            } else {
                int tmp = array[0];
                array[0] = array[1];
                array[1] = array[2];
                array[2] = tmp;
            }
        } else {
            std::swap(array[0], array[1]);
        }
    } else {
        if (array[2] < array[1]) {
            if (array[2] < array[0]) {
                int tmp = array[2];
                array[2] = array[1];
                array[1] = array[0];
                array[0] = tmp;
            } else {
                std::swap(array[1], array[2]);
            }
        }
    }
}

Biraz karmaşık görünüyor çünkü sıralama, dizinin olası her permütasyonu için az ya da çok bir şubeye sahip, 2 ~ 3 karşılaştırma ve üç değeri sıralamak için en fazla 4 atama kullanıyor.

4 değeri sıralama

Bu çağrı sort3daha sonra dizinin son öğesiyle birlikte kaydedilmemiş bir ekleme sıralaması gerçekleştirir:

void sort4(int* array)
{
    // Sort the first 3 elements
    sort3(array);

    // Insert the 4th element with insertion sort 
    if (array[3] < array[2]) {
        std::swap(array[2], array[3]);
        if (array[2] < array[1]) {
            std::swap(array[1], array[2]);
            if (array[1] < array[0]) {
                std::swap(array[0], array[1]);
            }
        }
    }
}

Bu algoritma 3 ila 6 karşılaştırma ve en fazla 5 swap gerçekleştirir. Bir ekleme sıralamasının kilidini açmak kolaydır, ancak son sıralama için başka bir algoritma kullanacağız ...

6 değeri sıralama

Bu, çift ​​eklemeli sıralama olarak adlandırdığım şeyin kaydedilmemiş bir sürümünü kullanıyor . Adı o kadar iyi değil, ama oldukça açıklayıcı, işte nasıl işliyor:

  • Dizinin ilk ve son öğeleri dışındaki her şeyi sıralayın.
  • İlkinin sondan büyükse ilkini ve dizinin öğelerini değiştirin.
  • İlk elemanı önden sıralanan sıraya, sonra arkadan son elemanı ekleyin.

Takastan sonra, ilk eleman her zaman sondan daha küçüktür, yani sıralı diziye eklenirken, en kötü duruma iki öğeyi eklemek için N'den fazla karşılaştırma olmayacaktır: örneğin, ilk eleman 3. pozisyona yerleştirildi, son eleman 4. pozisyondan daha aşağıya takılamaz.

void sort6(int* array)
{
    // Sort everything but first and last elements
    sort4(array+1);

    // Switch first and last elements if needed
    if (array[5] < array[0]) {
        std::swap(array[0], array[5]);
    }

    // Insert first element from the front
    if (array[1] < array[0]) {
        std::swap(array[0], array[1]);
        if (array[2] < array[1]) {
            std::swap(array[1], array[2]);
            if (array[3] < array[2]) {
                std::swap(array[2], array[3]);
                if (array[4] < array[3]) {
                    std::swap(array[3], array[4]);
                }
            }
        }
    }

    // Insert last element from the back
    if (array[5] < array[4]) {
        std::swap(array[4], array[5]);
        if (array[4] < array[3]) {
            std::swap(array[3], array[4]);
            if (array[3] < array[2]) {
                std::swap(array[2], array[3]);
                if (array[2] < array[1]) {
                    std::swap(array[1], array[2]);
                }
            }
        }
    }
}

6 değerin her permütasyonundaki testlerim, bu algoritmaların her zaman 6 ila 13 karşılaştırma yaptığını gösteriyor. Gerçekleştirilen takas sayısını hesaplamıyordum, ancak en kötü durumda 11'den daha yüksek olmasını beklemiyorum.

Umarım bu soru artık gerçek bir sorunu temsil etmese bile yardımcı olur :)

DÜZENLEME: sağlanan kıstasa koyduktan sonra, ilginç alternatiflerin çoğundan açıkça daha yavaştır. Açılmamış yerleştirme sıralamasından biraz daha iyi performans gösterir, ancak bu hemen hemen odur. Temel olarak, tamsayılar için en iyi sıralama değildir, ancak pahalı bir karşılaştırma işlemine sahip türler için ilginç olabilir.


Bunlar güzel. Çözülen problem uzun yıllardır, muhtemelen bir C programlama kadar eski olduğundan, sorunun şu anda yaklaşık 5 yıl olması o kadar alakalı görünmüyor.
kriss

Diğer cevapların nasıl zamanlandığına bir göz atmalısınız. Mesele şu ki, bu küçük veri kümesi sayım karşılaştırmaları, hatta karşılaştırmalar ve takaslar gerçekten bir algoritmanın ne kadar hızlı olduğunu söylemez (temel olarak 6 ints'ı sıralamak her zaman O (1) çünkü O (6 * 6) O (1)). Daha önce önerilen çözümlerin en hızlısı, büyük bir karşılaştırma (RexKerr tarafından) kullanılarak hemen her değerin konumunu bulmaktır.
kriss

@kriss Şimdi en hızlı mı? Sonuçları okuduğumdan, sıralama ağları yaklaşımı en hızlı, kötüydü. Çözümümün genel kütüphanemden geldiği ve her zaman tamsayıları karşılaştırmayacağım veya her zaman karşılaştırma için kullanamayacağım da doğrudur operator<. Karşılaştırmalar ve takasların nesnel sayımının yanı sıra, algoritmalarımı doğru bir şekilde zamanladım; Bu çözüm en hızlı genel çözümdü, ama gerçekten de RexKerr'in çözümünü kaçırdım. Deneyeceğim :)
Morwenn

RexKerr (Sipariş Sıralaması) çözümü, gcc derleyici 4.2.3'ten (ve gcc 4.9'dan itibaren ikinci en iyiden yaklaşık iki kat daha hızlı) X86 mimarisinde en hızlı hale geldi. Ancak derleyici optimizasyonuna büyük ölçüde bağımlıdır ve diğer mimarilerde doğru olmayabilir.
kriss

@kriss Bunu bilmek ilginç. Ve gerçekten daha fazla farklılık olabilir -O3. Sanırım sıralama kütüphanem için başka bir strateji benimseyeceğim: düşük sayıda karşılaştırmaya, düşük sayıda swapa veya potansiyel olarak en iyi performansa sahip olmak için üç çeşit algoritma sağlamak. En azından ne olacak okuyucu için şeffaf olacak.
Görüşleriniz

1

Sorunuzun iki kısmı olduğuna inanıyorum.

  • Birincisi, en uygun algoritmayı belirlemektir. Bu - en azından bu durumda - karşılaştırmaların ve takasların tam min, maksimum, ortalama ve standart sapmasını hesaplamanızı sağlayan her olası sıralamada (çok fazla yoktur) döngü yaparak yapılır. Bir ikinciye de sahip olun.
  • İkincisi algoritmayı optimize etmektir. Ders kitabı kod örneklerini ortalama ve yalın gerçek yaşam algoritmalarına dönüştürmek için çok şey yapılabilir. Bir algoritmanın gereken ölçüde optimize edilemediğini fark ederseniz, bir uzmanlığı deneyin.

Boru hatlarının boşaltılması konusunda çok fazla endişe etmem (mevcut x86 varsayarak): şube tahmini uzun bir yol kat etti. Ne hakkında endişe ediyorum kod ve veri her biri (belki iki kod için) bir önbellek satırına uygun olmasını sağlamaktır. Bir kez getirme gecikmeleri, herhangi bir duraklamayı telafi edecek ferahlatıcı derecede düşüktür. Ayrıca, iç döngünüzün belki on talimat kadar olması gerektiği anlamına gelir (sıralama algoritmamda iki farklı iç döngü vardır, sırasıyla 10 talimat / 22 bayt ve 9/22 uzunluğundadır). Kodun herhangi bir div içermediğini varsayarsak, kör edici bir şekilde hızlı olacağından emin olabilirsiniz.


Cevabınızı nasıl anlayacağınızdan emin değilim. Öncelikle hangi algoritmayı önerdiğini anlamıyorum? Ve 720 olası sipariş arasında geçiş yapmak zorunda kalmanız nasıl optimum olabilir (mevcut cevaplar 720 döngüden daha az sürer). Eğer rasgele girdiniz varsa (teorik düzeyde bile olsa), tüm tahsilat verilerinin umurunda olmadığı durumlar dışında, şube tahmininin 50-50'den daha iyi nasıl performans gösterebileceğini hayal edemiyorum. Ayrıca, daha önce önerilen çoğu iyi çözümün zaten önbellekte hem veri hem de kod ile çalışması muhtemeldir. Ama belki cevabını tamamen yanlış anladım. Biraz kod göstermeye ne dersin?
kriss

Demek istediğim 6 tamsayıdan sadece 720 (6!) Farklı kombinasyon vardı ve hepsini aday algoritmalarla çalıştırarak bahsettiğim gibi birçok şeyi belirleyebilirsin - bu teorik kısım. Pratik kısım, bu algoritmanın mümkün olduğunca az saat döngüsünde çalışacak şekilde ayarlanmasıdır. 6 tamsayıyı sıralamak için başlangıç ​​noktam, 1, 4 boşluklu bir kabuktur. 4 boşluk, 1 boşlukta iyi bir şube tahmini için zemin hazırlar.
Olof Forshell

6 için 1, 4 boşluk kabuğu! benzersiz kombinasyonlar (012345 ile başlayan ve 543210 ile biten) en iyi 7 karşılaştırma ve 0 borsaya ve en kötü 14 karşılaştırma ve 10 borsaya sahip olacaktır. Ortalama vaka yaklaşık 11.14 karşılaştırma ve 6 değişimdir.
Olof Forshell

1
"Düzenli rastgele dağılım" alamadım - yaptığım her olası kombinasyonu test etmek ve min / ortalama / maks istatistiklerini belirlemektir. Shellsort, nihai artışın (1) saf bir ekleme türünde olduğu gibi tek başına gerçekleştirilmesinden çok daha az iş yapacağı şekilde azalan artışların bir dizi ekleme türüdür. Saat sayımıyla ilgili olarak, algoritmam ortalama 406 saat döngüsü gerektirir ve bu, istatistik toplama ve gerçek sıralama rutinine iki çağrı yapmayı içerir - her boşluk için bir tane. Bu bir Athlon M300 mobil derleyici OpenWatcom'da.
Olof Forshell

1
"düzenli rastgele dağılım", sıralanan gerçek verilerin her kombinasyonunun eşit olasılıkta olmayabileceği anlamına gelir. Her kombinasyon eşit olasılıkta değilse, istatistikleriniz kırılır çünkü ortalama, belirli bir dağılımın kaç kez gerçekleşme olasılığını hesaba katması gerekir. Saat sayımı için, bu tür başka bir uygulamayı denerseniz (yukarıda verilen bağlantılar) ve test sisteminizde çalıştırırsanız, karşılaştırma için bir temelimiz olacak ve seçtiğiniz kişinin ne kadar iyi performans gösterdiğini göreceğiz.
kriss

1

Bunun eski bir soru olduğunu biliyorum.

Ama paylaşmak istediğim farklı bir çözüm yazdım.
İç içe MIN MAX dışında hiçbir şey kullanmamak,

Her biri 114 kullandığı için hızlı değil,
aynı şekilde 75'e düşürebilir -> pastebin

Ama artık sadece min max değil.

AVX ile aynı anda birden fazla tamsayıda min / maks yapmak ne işe yarar?

PMINSW referansı

#include <stdio.h>

static __inline__ int MIN(int a, int b){
int result =a;
__asm__ ("pminsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ int MAX(int a, int b){
int result = a;
__asm__ ("pmaxsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ unsigned long long rdtsc(void){
  unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" :
  "=A" (x));
  return x;
}

#define MIN3(a, b, c) (MIN(MIN(a,b),c))
#define MIN4(a, b, c, d) (MIN(MIN(a,b),MIN(c,d)))

static __inline__ void sort6(int * in) {
  const int A=in[0], B=in[1], C=in[2], D=in[3], E=in[4], F=in[5];

  in[0] = MIN( MIN4(A,B,C,D),MIN(E,F) );

  const int
  AB = MAX(A, B),
  AC = MAX(A, C),
  AD = MAX(A, D),
  AE = MAX(A, E),
  AF = MAX(A, F),
  BC = MAX(B, C),
  BD = MAX(B, D),
  BE = MAX(B, E),
  BF = MAX(B, F),
  CD = MAX(C, D),
  CE = MAX(C, E),
  CF = MAX(C, F),
  DE = MAX(D, E),
  DF = MAX(D, F),
  EF = MAX(E, F);

  in[1] = MIN4 (
  MIN4( AB, AC, AD, AE ),
  MIN4( AF, BC, BD, BE ),
  MIN4( BF, CD, CE, CF ),
  MIN3( DE, DF, EF)
  );

  const int
  ABC = MAX(AB,C),
  ABD = MAX(AB,D),
  ABE = MAX(AB,E),
  ABF = MAX(AB,F),
  ACD = MAX(AC,D),
  ACE = MAX(AC,E),
  ACF = MAX(AC,F),
  ADE = MAX(AD,E),
  ADF = MAX(AD,F),
  AEF = MAX(AE,F),
  BCD = MAX(BC,D),
  BCE = MAX(BC,E),
  BCF = MAX(BC,F),
  BDE = MAX(BD,E),
  BDF = MAX(BD,F),
  BEF = MAX(BE,F),
  CDE = MAX(CD,E),
  CDF = MAX(CD,F),
  CEF = MAX(CE,F),
  DEF = MAX(DE,F);

  in[2] = MIN( MIN4 (
  MIN4( ABC, ABD, ABE, ABF ),
  MIN4( ACD, ACE, ACF, ADE ),
  MIN4( ADF, AEF, BCD, BCE ),
  MIN4( BCF, BDE, BDF, BEF )),
  MIN4( CDE, CDF, CEF, DEF )
  );


  const int
  ABCD = MAX(ABC,D),
  ABCE = MAX(ABC,E),
  ABCF = MAX(ABC,F),
  ABDE = MAX(ABD,E),
  ABDF = MAX(ABD,F),
  ABEF = MAX(ABE,F),
  ACDE = MAX(ACD,E),
  ACDF = MAX(ACD,F),
  ACEF = MAX(ACE,F),
  ADEF = MAX(ADE,F),
  BCDE = MAX(BCD,E),
  BCDF = MAX(BCD,F),
  BCEF = MAX(BCE,F),
  BDEF = MAX(BDE,F),
  CDEF = MAX(CDE,F);

  in[3] = MIN4 (
  MIN4( ABCD, ABCE, ABCF, ABDE ),
  MIN4( ABDF, ABEF, ACDE, ACDF ),
  MIN4( ACEF, ADEF, BCDE, BCDF ),
  MIN3( BCEF, BDEF, CDEF )
  );

  const int
  ABCDE= MAX(ABCD,E),
  ABCDF= MAX(ABCD,F),
  ABCEF= MAX(ABCE,F),
  ABDEF= MAX(ABDE,F),
  ACDEF= MAX(ACDE,F),
  BCDEF= MAX(BCDE,F);

  in[4]= MIN (
  MIN4( ABCDE, ABCDF, ABCEF, ABDEF ),
  MIN ( ACDEF, BCDEF )
  );

  in[5] = MAX(ABCDE,F);
}

int main(int argc, char ** argv) {
  int d[6][6] = {
    {1, 2, 3, 4, 5, 6},
    {6, 5, 4, 3, 2, 1},
    {100, 2, 300, 4, 500, 6},
    {100, 2, 3, 4, 500, 6},
    {1, 200, 3, 4, 5, 600},
    {1, 1, 2, 1, 2, 1}
  };

  unsigned long long cycles = rdtsc();
  for (int i = 0; i < 6; i++) {
    sort6(d[i]);
  }
  cycles = rdtsc() - cycles;
  printf("Time is %d\n", (unsigned)cycles);

  for (int i = 0; i < 6; i++) {
    printf("d%d : %d %d %d %d %d %d\n", i,
     d[i][0], d[i][1], d[i][2],
     d[i][3], d[i][4], d[i][5]);
  }
}

DÜZENLEME:
Rex Kerr'den esinlenilen sıralama düzeni çözümü, Yukarıdaki karmaşadan çok daha hızlı

static void sort6(int *o) {
const int 
A=o[0],B=o[1],C=o[2],D=o[3],E=o[4],F=o[5];
const unsigned char
AB = A>B, AC = A>C, AD = A>D, AE = A>E,
          BC = B>C, BD = B>D, BE = B>E,
                    CD = C>D, CE = C>E,
                              DE = D>E,
a =          AB + AC + AD + AE + (A>F),
b = 1 - AB      + BC + BD + BE + (B>F),
c = 2 - AC - BC      + CD + CE + (C>F),
d = 3 - AD - BD - CD      + DE + (D>F),
e = 4 - AE - BE - CE - DE      + (E>F);
o[a]=A; o[b]=B; o[c]=C; o[d]=D; o[e]=E;
o[15-a-b-c-d-e]=F;
}

1
her zaman yeni çözümler görmek güzel. Bazı kolay optimizasyonlar mümkün görünüyor. Sonunda Sıralama Ağlarından çok farklı olmayabilir.
kriss

Evet, MIN ve MAX sayısı azaltılabilir, örneğin MIN (AB, CD) birkaç kez tekrar eder, ancak onları çok düşürmek zor olacaktır. Test vakalarınızı ekledim.
PrincePolka

pmin / maxsw, paketlenmiş 16 bit işaretli tamsayılarda ( int16_t) çalışır. Ancak C işleviniz bir dizi int(bu asmsözdizimini destekleyen tüm C uygulamalarında 32 bit) sıraladığını iddia eder . Yüksek yarılarında sadece 0 olan küçük pozitif tamsayılarla test ettiniz mi? Bu işe yarayacak ... intSSE4.1'e ihtiyacınız var pmin/maxsd(d = dword). felixcloutier.com/x86/pminsd:pminsq veya pminusdiçin uint32_t.
Peter Cordes

1

En azından sistemimde, aşağıda tanımlanan fonksiyonların sort6_iterator()ve sort6_iterator_local()her ikisinin de yukarıdaki mevcut kayıt tutucusundan en az hızlı ve sıklıkla fark edilir derecede daha hızlı çalıştığını buldum :

#define MIN(x, y) (x<y?x:y)
#define MAX(x, y) (x<y?y:x)

template<class IterType> 
inline void sort6_iterator(IterType it) 
{
#define SWAP(x,y) { const auto a = MIN(*(it + x), *(it + y)); \
  const auto b = MAX(*(it + x), *(it + y)); \
  *(it + x) = a; *(it + y) = b; }

  SWAP(1, 2) SWAP(4, 5)
  SWAP(0, 2) SWAP(3, 5)
  SWAP(0, 1) SWAP(3, 4)
  SWAP(1, 4) SWAP(0, 3)
  SWAP(2, 5) SWAP(1, 3)
  SWAP(2, 4)
  SWAP(2, 3)
#undef SWAP
}

Bu işlevi std::vectorzamanlama kodumda yineleyici olarak geçtim .

Ben (gibi yorumlardan şüpheli bu yineleyicinızı kullanarak yineleyici aksi olmazdı hangi atıfta bulunmaktadır ve bu güvenceler izin gr ++ olduğunu ve belleğe olamaz ne hakkında gr ++ belirli güvenceler verdiğini ve başka yerlerde) sıralama kodunu daha iyi optimize edin (örneğin, işaretçilerle, derleyici tüm işaretçilerin farklı bellek konumlarını gösterdiğinden emin olamaz). Doğru hatırlıyorsam, bu da birçok STL algoritmasının genellikle bu kadar iyi bir performansa sahip olmasının nedeninin bir parçasıdırstd::sort() .

Ayrıca, sort6_iterator()bir bazı zamanlar (yine, işlev olarak adlandırılır ki burada bağlama bağlı olarak) sürekli olarak aşağıdaki sıralama işlevi ile daha iyi performans olan dizerek önce kopyalar yerel değişkenler veri. 1 Yalnızca 6 yerel değişken tanımlandığından, bu yerel değişkenler ilkel ise, muhtemelen gerçekte RAM'de saklanmazlar ve bunun yerine işlev çağrısının sonuna kadar yalnızca CPU kayıtlarında saklanırlar, bu da bu sıralamanın yapılmasına yardımcı olur hızlı işlev. (Ayrıca, derleyicinin farklı yerel değişkenlerin bellekte farklı konumları olduğunu bilmesine yardımcı olur).

template<class IterType> 
inline void sort6_iterator_local(IterType it) 
{
#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
  const auto b = MAX(data##x, data##y); \
  data##x = a; data##y = b; }
//DD = Define Data
#define DD1(a)   auto data##a = *(it + a);
#define DD2(a,b) auto data##a = *(it + a), data##b = *(it + b);
//CB = Copy Back
#define CB(a) *(it + a) = data##a;

  DD2(1,2)    SWAP(1, 2)
  DD2(4,5)    SWAP(4, 5)
  DD1(0)      SWAP(0, 2)
  DD1(3)      SWAP(3, 5)
  SWAP(0, 1)  SWAP(3, 4)
  SWAP(1, 4)  SWAP(0, 3)   CB(0)
  SWAP(2, 5)  CB(5)
  SWAP(1, 3)  CB(1)
  SWAP(2, 4)  CB(4)
  SWAP(2, 3)  CB(2)        CB(3)
#undef CB
#undef DD2
#undef DD1
#undef SWAP
}

Not tanımlayan o SWAP()kadar izler bazı biraz daha iyi performans kez sonuçlarını biraz daha kötü performans veya performansta önemsiz farklılıklar ile sonuçlanan çoğu zaman olmasına rağmen.

#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
  data##y = MAX(data##x, data##y); \
  data##x = a; }

Sadece ilkel veri türlerinde bir sıralama algoritması istiyorsanız, gcc -O3, sıralama işlevine yapılan çağrı 1'de hangi bağlamda görünürse görünse de , girdiyi nasıl geçirdiğinize bağlı olarak, optimizasyonda sürekli olarak iyidir. algoritmalar:

template<class T> inline void sort6(T it) {
#define SORT2(x,y) {if(data##x>data##y){auto a=std::move(data##y);data##y=std::move(data##x);data##x=std::move(a);}}
#define DD1(a)   register auto data##a=*(it+a);
#define DD2(a,b) register auto data##a=*(it+a);register auto data##b=*(it+b);
#define CB1(a)   *(it+a)=data##a;
#define CB2(a,b) *(it+a)=data##a;*(it+b)=data##b;
  DD2(1,2) SORT2(1,2)
  DD2(4,5) SORT2(4,5)
  DD1(0)   SORT2(0,2)
  DD1(3)   SORT2(3,5)
  SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
  SORT2(1,4) SORT2(0,3) CB1(0)
  SORT2(2,4) CB1(4)
  SORT2(1,3) CB1(1)
  SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

Veya değişkenleri referans olarak iletmek istiyorsanız bunu kullanın (aşağıdaki işlev ilk 5 satırında yukarıdakilerden farklıdır):

template<class T> inline void sort6(T& e0, T& e1, T& e2, T& e3, T& e4, T& e5) {
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   register auto data##a=e##a;
#define DD2(a,b) register auto data##a=e##a;register auto data##b=e##b;
#define CB1(a)   e##a=data##a;
#define CB2(a,b) e##a=data##a;e##b=data##b;
  DD2(1,2) SORT2(1,2)
  DD2(4,5) SORT2(4,5)
  DD1(0)   SORT2(0,2)
  DD1(3)   SORT2(3,5)
  SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
  SORT2(1,4) SORT2(0,3) CB1(0)
  SORT2(2,4) CB1(4)
  SORT2(1,3) CB1(1)
  SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

registerAnahtar kelimeyi kullanmanın nedeni, bunun bu değerleri yazmaçlarda istediğinizi bildiğiniz birkaç kez olması. registerDerleyici olmadan , çoğu zaman bunu çözer, ancak bazen çözmez. registerAnahtar kelimeyi kullanmak bu sorunun çözülmesine yardımcı olur. Bununla birlikte, normal olarak, registerkodunuzu yavaşlatmaktan daha yavaş olacağı için anahtar kelimeyi kullanmayın .

Ayrıca, şablonların kullanımını not edin. Bu amaç için yapılır, çünkü inlineanahtar kelime ile bile , şablon fonksiyonları genellikle vanilya C fonksiyonlarından gcc ile çok daha agresif bir şekilde optimize edilir (bu vanilya C fonksiyonları için fonksiyon işaretçileri ile uğraşmak zorunda olan gcc ile ilgilidir, ancak şablon fonksiyonları ile değil).

  1. Çeşitli sıralama işlevlerini zamanlarken, sıralama işlevine yapılan çağrının yapıldığı bağlamın (yani, çevreleyen kodun) performans üzerinde önemli bir etkisi olduğunu fark ettim. Örneğin, program yeterince basit olsaydı, sıralama işlevini bir işaretçiyi bir yineleyiciden geçirme ile geçirme arasında genellikle performans farkı pek yoktu; aksi takdirde yineleyiciler kullanmak genellikle fark edilir derecede daha iyi performans ve hiçbir zaman (en azından şu ana kadarki deneyimlerime göre) belirgin bir şekilde daha kötü performans ile sonuçlandı. Bunun, g ++ global olarak yeterince basit bir kodu optimize edebileceğinden şüpheleniyorum.

0

'Sıralı listeyi birleştir' türünü deneyin. :) İki dizi kullanın. Küçük ve büyük diziler için en hızlısı.
Eğer karar verirseniz, sadece insertin nerede olduğunu kontrol edersiniz. İhtiyacınız olmayan diğer büyük değerler karşılaştırın (cmp = ab> 0).
4 sayı için, sistem 4-5 cmp (~ 4.6) veya 3-6 cmp (~ 4.9) kullanabilirsiniz. Kabarcık sıralama 6 cmp (6) kullanın. Büyük sayılar yavaş kod için cmp sürü.
Bu kod 5 cmp kullanır (MSL sıralaması değil):
if (cmp(arr[n][i+0],arr[n][i+1])>0) {swap(n,i+0,i+1);} if (cmp(arr[n][i+2],arr[n][i+3])>0) {swap(n,i+2,i+3);} if (cmp(arr[n][i+0],arr[n][i+2])>0) {swap(n,i+0,i+2);} if (cmp(arr[n][i+1],arr[n][i+3])>0) {swap(n,i+1,i+3);} if (cmp(arr[n][i+1],arr[n][i+2])>0) {swap(n,i+1,i+2);}

Temel MSL 9 8 7 6 5 4 3 2 1 0 89 67 45 23 01 ... concat two sorted lists, list length = 1 6789 2345 01 ... concat two sorted lists, list length = 2 23456789 01 ... concat two sorted lists, list length = 4 0123456789 ... concat two sorted lists, list length = 8

js kodu

function sortListMerge_2a(cmp)	
{
var step, stepmax, tmp, a,b,c, i,j,k, m,n, cycles;
var start = 0;
var end   = arr_count;
//var str = '';
cycles = 0;
if (end>3)
	{
	stepmax = ((end - start + 1) >> 1) << 1;
	m = 1;
	n = 2;
	for (step=1;step<stepmax;step<<=1)	//bounds 1-1, 2-2, 4-4, 8-8...
		{
		a = start;
		while (a<end)
			{
			b = a + step;
			c = a + step + step;
			b = b<end ? b : end;
			c = c<end ? c : end;
			i = a;
			j = b;
			k = i;
			while (i<b && j<c)
				{
				if (cmp(arr[m][i],arr[m][j])>0)
					{arr[n][k] = arr[m][j]; j++; k++;}
				else	{arr[n][k] = arr[m][i]; i++; k++;}
				}
			while (i<b)
				{arr[n][k] = arr[m][i]; i++; k++;
}
			while (j<c)
				{arr[n][k] = arr[m][j]; j++; k++;
}
			a = c;
			}
		tmp = m; m = n; n = tmp;
		}
	return m;
	}
else
	{
	// sort 3 items
	sort10(cmp);
	return m;
	}
}


0

Cmp == 0 kullanımıyla 4 öğeyi sıralayın. Cmp sayısı ~ 4.34'tür (yerel FF ~ 4.52'ye sahiptir), ancak listeleri birleştirmekten 3 kat daha fazla zaman alır. Ama büyük sayılar veya büyük metin varsa daha az cmp işlemleri daha iyi. Düzenle: onarılan hata

Çevrimiçi test http://mlich.zam.slu.cz/js-sort/x-sort-x2.htm

function sort4DG(cmp,start,end,n) // sort 4
{
var n     = typeof(n)    !=='undefined' ? n   : 1;
var cmp   = typeof(cmp)  !=='undefined' ? cmp   : sortCompare2;
var start = typeof(start)!=='undefined' ? start : 0;
var end   = typeof(end)  !=='undefined' ? end   : arr[n].length;
var count = end - start;
var pos = -1;
var i = start;
var cc = [];
// stabilni?
cc[01] = cmp(arr[n][i+0],arr[n][i+1]);
cc[23] = cmp(arr[n][i+2],arr[n][i+3]);
if (cc[01]>0) {swap(n,i+0,i+1);}
if (cc[23]>0) {swap(n,i+2,i+3);}
cc[12] = cmp(arr[n][i+1],arr[n][i+2]);
if (!(cc[12]>0)) {return n;}
cc[02] = cc[01]==0 ? cc[12] : cmp(arr[n][i+0],arr[n][i+2]);
if (cc[02]>0)
    {
    swap(n,i+1,i+2); swap(n,i+0,i+1); // bubble last to top
    cc[13] = cc[23]==0 ? cc[12] : cmp(arr[n][i+1],arr[n][i+3]);
    if (cc[13]>0)
        {
        swap(n,i+2,i+3); swap(n,i+1,i+2); // bubble
        return n;
        }
    else    {
    cc[23] = cc[23]==0 ? cc[12] : (cc[01]==0 ? cc[30] : cmp(arr[n][i+2],arr[n][i+3]));  // new cc23 | c03 //repaired
        if (cc[23]>0)
            {
            swap(n,i+2,i+3);
            return n;
            }
        return n;
        }
    }
else    {
    if (cc[12]>0)
        {
        swap(n,i+1,i+2);
        cc[23] = cc[23]==0 ? cc[12] : cmp(arr[n][i+2],arr[n][i+3]); // new cc23
        if (cc[23]>0)
            {
            swap(n,i+2,i+3);
            return n;
            }
        return n;
        }
    else    {
        return n;
        }
    }
return n;
}

1
Kullanım durumu, sorunun ilk bağlamından biraz farklıdır. Sabit uzunluklu detaylarda detaylar önemlidir ve swap sayımı yeterli değildir. Zaman harcayacak gerçek bir tür olmasa bile şaşırmayacaktım, ama init'te tamamen farklı bir ışık türü typeof () çağırıyor. Javascript kullanarak gerçek saat zaman mesure nasıl yapacağımı bilmiyorum. Belki düğüm ile?
kriss

0

Belki ben partiye geç, ama en azından benim katkım bir olduğunu yeni bir yaklaşım.

  • Kod gerçekten satır içine alınmalıdır
  • eğimli olsa bile, çok fazla dal var
  • analiz kısmı temelde N = 6 için uygun görünen O (N (N-1))
  • eğer kod daha etkili olabilir maliyetiswap yüksek olacağını (maliyeti yalıtılarak compare)
  • Statik fonksiyonların satır içine alındığına güveniyorum.
  • Yöntem rank-sort ile ilgilidir
    • rütbeler yerine, rölatif rütbeler (ofsetler) kullanılır.
    • sıralamaların toplamı, herhangi bir permütasyon grubundaki her döngü için sıfırdır .
    • SWAP()iki elemanın yerine , döngüler kovalanır, yalnızca bir sıcaklık ve bir (kayıt-> kayıt) takas (yeni <- eski) gerekir.

Güncelleme: kodu biraz değiştirdi, bazı insanlar C kodunu derlemek için C ++ derleyicilerini kullanıyor ...

#include <stdio.h>

#if WANT_CHAR
typedef signed char Dif;
#else
typedef signed int Dif;
#endif

static int walksort (int *arr, int cnt);
static void countdifs (int *arr, Dif *dif, int cnt);
static void calcranks(int *arr, Dif *dif);

int wsort6(int *arr);

void do_print_a(char *msg, int *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
        fprintf(stderr, " %3d", *arr);
        }
fprintf(stderr,"\n");
}

void do_print_d(char *msg, Dif *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
        fprintf(stderr, " %3d", (int) *arr);
        }
fprintf(stderr,"\n");
}

static void inline countdifs (int *arr, Dif *dif, int cnt)
{
int top, bot;

for (top = 0; top < cnt; top++ ) {
        for (bot = 0; bot < top; bot++ ) {
                if (arr[top] < arr[bot]) { dif[top]--; dif[bot]++; }
                }
        }
return ;
}
        /* Copied from RexKerr ... */
static void inline calcranks(int *arr, Dif *dif){

dif[0] =     (arr[0]>arr[1])+(arr[0]>arr[2])+(arr[0]>arr[3])+(arr[0]>arr[4])+(arr[0]>arr[5]);
dif[1] = -1+ (arr[1]>=arr[0])+(arr[1]>arr[2])+(arr[1]>arr[3])+(arr[1]>arr[4])+(arr[1]>arr[5]);
dif[2] = -2+ (arr[2]>=arr[0])+(arr[2]>=arr[1])+(arr[2]>arr[3])+(arr[2]>arr[4])+(arr[2]>arr[5]);
dif[3] = -3+ (arr[3]>=arr[0])+(arr[3]>=arr[1])+(arr[3]>=arr[2])+(arr[3]>arr[4])+(arr[3]>arr[5]);
dif[4] = -4+ (arr[4]>=arr[0])+(arr[4]>=arr[1])+(arr[4]>=arr[2])+(arr[4]>=arr[3])+(arr[4]>arr[5]);
dif[5] = -(dif[0]+dif[1]+dif[2]+dif[3]+dif[4]);
}

static int walksort (int *arr, int cnt)
{
int idx, src,dst, nswap;

Dif difs[cnt];

#if WANT_REXK
calcranks(arr, difs);
#else
for (idx=0; idx < cnt; idx++) difs[idx] =0;
countdifs(arr, difs, cnt);
#endif
calcranks(arr, difs);

#define DUMP_IT 0
#if DUMP_IT
do_print_d("ISteps ", difs, cnt);
#endif

nswap = 0;
for (idx=0; idx < cnt; idx++) {
        int newval;
        int step,cyc;
        if ( !difs[idx] ) continue;
        newval = arr[idx];
        cyc = 0;
        src = idx;
        do      {
                int oldval;
                step = difs[src];
                difs[src] =0;
                dst = src + step;
                cyc += step ;
                if(dst == idx+1)idx=dst;
                oldval = arr[dst];
#if (DUMP_IT&1)
                fprintf(stderr, "[Nswap=%d] Cyc=%d Step=%2d Idx=%d  Old=%2d New=%2d #### Src=%d Dst=%d[%2d]->%2d <-- %d\n##\n"
                        , nswap, cyc, step, idx, oldval, newval
                        , src, dst, difs[dst], arr[dst]
                        , newval  );
                do_print_a("Array ", arr, cnt);
                do_print_d("Steps ", difs, cnt);
#endif

                arr[dst] = newval;
                newval = oldval;
                nswap++;
                src = dst;
                } while( cyc);
        }

return nswap;
}
/*************/
int wsort6(int *arr)
{
return walksort(arr, 6);
}

kabarcık türüne benziyor. En yavaş uygulama için potansiyel olarak iyi bir yarışmacıdır, ancak kod üzerinde çalışmanın bu kadar fark yaratıp yaratmadığını bilmek hala ilgi çekici olabilir. Lütfen kodunuzu diğerleriyle aynı biçimde yerleştirin, böylece karşılaştırmalı değerlendirmeyi çalıştırabiliriz.
kriss

@kriss en.wikipedia.org/wiki/Permutation_group Kesinlikle kabarcık sıralaması değildir : kod verilen permütasyondaki döngüleri algılar ve bu döngüleri yürür ve her bir öğeyi son yerine koyar. Son wsort6()işlev doğru arayüze sahiptir.
joop

@ joop: benim kötü, kabarcık tür gerçekten. Bu bağlamda söyleniyor ben hala kodun diğer mevcut uygulamalardan çok daha kötü olmasını bekliyorum. Bu arada, Rank Order çözümü, her öğenin son konumunu doğrudan bulduğu için takas sayısı açısından en uygunudur. Ayrıca, sıralanan tüm sayıların burada olduğu gibi farklı olduğu hipotezini kaldırdığımızda walksort'un çalışıp çalışmadığı da belirsizdir. Kodu karşılaştırmak için izleme kodunu kullanmalıyız. Ayrıca genellikle bir C ++ derleyicisi üzerinde derleme gibi, OP değişkeni "new" (ve sözdizimi vurgulama sonlandırır) çağırdı çünkü kod çalışmaz.
kriss

Yöntem nihai atamaları yapılır, sıralamada çok yakın bir yerde . Rütbelerin dışında o1..o5, ikinci geçici e[6]diziye gerek yoktur . Ve: bir C ++ derleyicisi üzerinde C kodu derleme ve kodu suçlama?
joop

@ greybeard: teşekkür ederim, daha önce bir boşluk ekledim #include. Düzeltildi
wildplasser

0
//Bruteforce compute unrolled count dumbsort(min to 0-index)
void bcudc_sort6(int* a)
{
    int t[6] = {0};
    int r1,r2;

    r1=0;
    r1 += (a[0] > a[1]);
    r1 += (a[0] > a[2]);
    r1 += (a[0] > a[3]);
    r1 += (a[0] > a[4]);
    r1 += (a[0] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[0];

    r2=0;
    r2 += (a[1] > a[0]);
    r2 += (a[1] > a[2]);
    r2 += (a[1] > a[3]);
    r2 += (a[1] > a[4]);
    r2 += (a[1] > a[5]);
    while(t[r2]){r2++;} 
    t[r2] = a[1];

    r1=0;
    r1 += (a[2] > a[0]);
    r1 += (a[2] > a[1]);
    r1 += (a[2] > a[3]);
    r1 += (a[2] > a[4]);
    r1 += (a[2] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[2];

    r2=0;
    r2 += (a[3] > a[0]);
    r2 += (a[3] > a[1]);
    r2 += (a[3] > a[2]);
    r2 += (a[3] > a[4]);
    r2 += (a[3] > a[5]);
    while(t[r2]){r2++;} 
    t[r2] = a[3];

    r1=0;
    r1 += (a[4] > a[0]);
    r1 += (a[4] > a[1]);
    r1 += (a[4] > a[2]);
    r1 += (a[4] > a[3]);
    r1 += (a[4] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[4];

    r2=0;
    r2 += (a[5] > a[0]);
    r2 += (a[5] > a[1]);
    r2 += (a[5] > a[2]);
    r2 += (a[5] > a[3]);
    r2 += (a[5] > a[4]);
    while(t[r2]){r2++;} 
    t[r2] = a[5];

    a[0]=t[0];
    a[1]=t[1];
    a[2]=t[2];
    a[3]=t[3];
    a[4]=t[4];
    a[5]=t[5];
}

static __inline__ void sort6(int* a)
{
    #define wire(x,y); t = a[x] ^ a[y] ^ ( (a[x] ^ a[y]) & -(a[x] < a[y]) ); a[x] = a[x] ^ t; a[y] = a[y] ^ t;
    register int t;

    wire( 0, 1); wire( 2, 3); wire( 4, 5);
    wire( 3, 5); wire( 0, 2); wire( 1, 4);
    wire( 4, 5); wire( 2, 3); wire( 0, 1); 
    wire( 3, 4); wire( 1, 2); 
    wire( 2, 3);

    #undef wire
}

Hız ne olursa olsun, çalıştığından emin misiniz? Bruteforce türünde döngüler şüpheli. Bana öyle geliyor ki, sıralı değerlerde sıfır varsa, işe yaramazlar.
kriss

1
t [6] dizisi 0x0 olarak başlatıldı. Bu nedenle, 0x0 değerli bir anahtarın nereye ve ne şekilde yazılacağı önemli değildir.
FranG

-1

Eğer sadece 6 element varsa ve paralellikten faydalanabiliyorsanız, koşullu dallanmayı en aza indirgemek, vb. Bazı mimarilerde oldukça hızlı olabileceğini düşünürdüm (hafızayı önceden konumlandırdığınız sürece)


9
720 sipariş vardır ve hızlı versiyonları 100 döngü altındadır. Büyük paralellik kaldıraç olsa bile, böyle küçük bir zaman diliminde, iş parçacığı oluşturma ve senkronizasyon maliyeti dizileri tek bir çekirdek üzerinde sıralamanın maliyetini muhtemelen aşacaktır.
Kevin Stock

-3

Üç farklı Sınıflandırma Algoritması sınıfını temsil eden üç tipik sıralama yöntemi şunlardır:

Insertion Sort: Θ(n^2)

Heap Sort: Θ(n log n)

Count Sort: Θ(3n)

Ama en hızlı sıralama algoritması hakkındaki Stefan Nelsson tartışmasına bir göz atın. O kadar iner bir çözümü tartışmak nerede O(n log log n).. kontrol C uygulanmasını

Bu Yarı Doğrusal Sıralama algoritması 1995 yılında bir makale tarafından sunulmuştur:

A. Andersson, T. Hagerup, S. Nilsson ve R. Raman. Doğrusal zamanda sıralama? 27. Yıllık ACM Bilgisayar Teorisi Sempozyumu, Bildiriler Kitabı, sayfa 427-436, 1995.


8
Bu ilginç ama konunun yanında. Big-constant, sabit faktörleri gizlemek ve problem boyutu (n) büyüdüğünde eğilimi göstermek için tasarlanmıştır. Buradaki problem tamamen sabit bir problem büyüklüğü (n = 6) ve sabit faktörleri dikkate almakla ilgilidir.
kriss

@kriss haklısın, karşılaştırmam asimptotik, bu yüzden pratik karşılaştırma bu dava için daha hızlı olup olmadığını gösterecek
Khaled.K

4
Sonuçlandıramazsınız, çünkü her farklı algoritma farklı bir K çarpma sabitini (ve ayrıca bir C katkı sabitini) gizler. yani: ekleme sıralaması için k0, c0, yığın sıralaması için k1, c1 vb. Tüm bu sabitler aslında farklıdır (fiziksel termalarda her algoritmanın kendi "sürtünme katsayısına" sahip olduğunu söyleyebilirsiniz) bu durumda (veya herhangi bir sabit n durumunda) bir algoritmanın aslında daha hızlı olduğu sonucuna varamazsınız.
kriss
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.