10 numara sıralamak için en hızlı yolu? (sayılar 32 bit)


211

Bir problemi çözüyorum ve 10 rakamı (int32) çok hızlı bir şekilde sıralamayı içeriyor. Uygulamamın 10 rakamı olabildiğince hızlı bir şekilde milyonlarca kez sıralaması gerekiyor. Milyarlarca elementin veri setini örnek alıyorum ve her seferinde 10 numara seçmem (basitleştirilmiş) ve bunları sıralamam (ve sıralı 10 element listesinden sonuçlar çıkarmam) gerekiyor.

Şu anda ekleme sıralama kullanıyorum ama ekleme sıralama yenmek için 10 sayı benim özel sorun için çok hızlı bir özel sıralama algoritması uygulamak hayal edebiliyorum.

Bu soruna nasıl yaklaşılacağı hakkında bir fikri olan var mı?


14
Göründüğü kadar kaba, bir dizi iç içe geçmiş ififade en iyi şekilde çalışmalıdır. Döngülerden kaçının.
John Alexiou

8
Sayıların permütasyon setindeki herhangi bir yanlılıkla verilmesini mi bekliyorsunuz, yoksa eşit olarak dağıtılacak mı? Bir listenin sıralaması ile bir sonraki listenin sıralaması arasında herhangi bir ilişki olacak mı?
Douglas Zare

4
Tüm veri kümesi (milyarlarca sayıyla) Benford yasasına göre dağıtılır, ancak öğeleri bu setten rastgele seçtiğimde, artık değildirler (sanırım).
bodacydo

13
Bunu okumak isteyebilirsiniz stackoverflow.com/q/2786899/995714
phuclv

11
Milyarlarca öğeden rastgele seçim yapıyorsanız, bu verileri içeri çekme gecikmesinin, tüm veri kümesi RAM'de olsa bile, seçilen öğeleri sıralamak için gereken süreden daha fazla etkisi olabilir. Verileri rastgele veya sırayla verileri seçerek performansı test ederek etkisini test edebilirsiniz.
Steve

Yanıtlar:


213

(HelloWorld'ün sıralama ağlarına bakma önerisini takip etmek.)

29 karşılaştırma / takas ağı, 10 girişli bir sıralama yapmanın en hızlı yolu gibi görünüyor. Waksman'ın 1969'da Javascript'te bu örnek için doğrudan C'ye çevirmesi gereken ağı kullandım, çünkü sadece ififadelerin, karşılaştırmaların ve takasların bir listesi .

function sortNet10(data) {	// ten-input sorting network by Waksman, 1969
    var swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
    return(data);
}

alert(sortNet10([5,7,1,8,4,3,6,9,2,0]));

İşte ağın bağımsız aşamalara bölünmüş bir grafik temsili. Paralel işlemeden yararlanmak için 5-4-3-4-4-4-3-2 gruplaması 4-4-4-4-4-4-3-2 gruplamasına dönüştürülebilir.
10 girişli sıralama ağı (Waksman, 1969)

10 girişli sıralama ağı (Waksman, 1969) yeniden gruplandırıldı


69
öneri; bir takas makrosu kullanın. gibi#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
Peter Cordes

9
Bunun asgari düzeyde olduğu mantıksal olarak gösterilebilir mi?
corsiKa

8
@corsiKa Evet, sıralama ağları bilgisayar biliminin ilk günlerinden beri bir araştırma alanı olmuştur. Birçok durumda, onlarca yıldır optimal çözümler bilinmektedir. Bkz en.wikipedia.org/wiki/Sorting_network
M69 '' hem huysuz ve unwelcoming ''

8
Test etmek için bir Jsperf yaptım ve Ağ Sıralama'nın tarayıcıların yerel sıralamasının 20 katından daha hızlı olduğunu onaylayabilirim. jsperf.com/fastest-10-number-sort
Daniel

9
@Katai Bu, derleyicinizin üretebileceği optimizasyonu yok eder. Kötü bir fikir. Daha fazla bilgi için bunu okuyun en.wikipedia.org/wiki/…
Antzi

88

Bu sabit boyutla uğraşırken Sıralama Ağlarına bir göz atın . Bu algoritmalar sabit bir çalışma süresine sahiptir ve girdilerinden bağımsızdır. Kullanım durumunuz için bazı sıralama algoritmalarının sahip olduğu ek yüke sahip değilsiniz.

Bitonik sıralama böyle bir ağın uygulamasıdır. Bu işlem en iyi CPU'da len (n) <= 32 ile çalışır. Daha büyük girişlerde bir GPU'ya geçmeyi düşünebilirsiniz. https://en.wikipedia.org/wiki/Sorting_network

Btw, sıralama algoritmalarını karşılaştırmak için iyi bir sayfa budur (eksik olsa da) bitonic sort.

http://www.sorting-algorithms.com


3
@ ErickG.Hagstrom Birçok çözüm var; 29 karşılaştırma kullandıkları sürece eşit derecede verimlidirler. 1969'dan beri Waksman'ın çözümünü kullandım; görünüşe göre 29 karşılaştırma versiyonunu ilk keşfeden oldu.
m69 '' snarky and unwelcoming ''

1
Evet, @ m69. Bir milyondan fazla var. Waksman'ın çözümünün uzunluğu 29 ve derinliği 9'dur. Bağlantı kurduğum çözüm, derinlik boyutuna göre bir gelişmedir: uzunluk = 29, derinlik = 8. Tabii ki, C'de uygulandığında, derinlik önemli değildir.
Erick G. Hagstrom

4
@ ErickG.Hagstrom Görünüşe göre birincisi 1973'te Knuth tarafından bulunan derinlik 7'ye sahip 87 çözüm var, ancak hızlı bir Google ile bunların hiçbirini bulamadım. larc.unt.edu/ian/pubs/9-input.pdf (bkz. Sonuç, s.14)
m69 '' keskin ve hoş olmayan ''

4
@ ErickG.Hagstrom: derinlik "C düzeyinde" bir fark yaratmayabilir, ancak muhtemelen derleyici ve CPU onunla bittikten sonra, CPU içinde kısmen paralel hale gelme şansı vardır ve bu nedenle daha küçük derinlik yardımcı olabilir. CPU'ya bağlı olarak, elbette: bazı CPU'lar nispeten basittir ve birbiri ardına bir şey yaparlar, ancak bazı CPU'lar uçuşta birden fazla işlem yapabilir, özellikle de gerekli olan yığıntaki herhangi bir yük ve depo için çok farklı performans elde edebilirsiniz. nasıl yapıldıklarına bağlı olarak 10 değişkeni değiştirmek için.
Steve Jessop

1
@ ErickG.Hagstrom Ian Parberry'nin makalesinden hemen anlaşılmadı, ancak derinlik-7 ağlarının uzunluğu 29'dan büyük. Bkz. Knuth, "Bilgisayar Programlama Sanatı Cilt III", §5.3.4, incir . 49 ve 51.
m69 '' snarky and unwelcoming ''

33

SIMD kayıtlarında yapabilmeniz için 4'lü gruplar halinde karşılaştırmaları olan bir sıralama ağı kullanın. Paketlenmiş min / maks talimatları bir paketlenmiş karşılaştırıcı işlevi uygular. Maalesef, şu anda gördüğümü hatırladığım bir sayfayı aramak için zamanım yok, ancak umarım SIMD veya SSE sıralama ağlarında arama yapmak bir şey ortaya çıkarır.

x86 SSE, dört adet 32 ​​bitlik vektörler için paketlenmiş 32 bit tam sayı min ve maksimum talimatlara sahiptir. AVX2 (Haswell ve üstü) aynı fakat 8 int 256b vektörleri için. Ayrıca, etkili karıştırma talimatları da vardır.

Çok sayıda bağımsız küçük türünüz varsa, vektörleri kullanarak paralel olarak 4 veya 8 çeşit yapmak mümkün olabilir. Esp. öğeleri rastgele seçiyorsanız (sıralanacak veriler bellekte yine de bitişik olmayacaktır), karışıklıklardan kaçınabilir ve ihtiyacınız olan sırayla karşılaştırabilirsiniz. 10 inçlik 4 (AVX2: 8) listesindeki tüm verileri tutmak için 10 kayıt, hala sıfırdan boşluk için 6 reg bırakır.

İlişkili verileri de sıralamanız gerektiğinde vektör sıralama ağları daha az verimlidir. Bu durumda, en etkili yol, hangi öğelerin değiştiğinin maskesini almak için paketlenmiş bir karşılaştırma kullanmak ve ilişkili verilerin vektörlerini (referanslar) karıştırmak için bu maskeyi kullanmak gibi görünmektedir.


26

Kayıtsız, dalsız seçim sıralamasına ne dersiniz?

#include <iostream>
#include <algorithm>
#include <random>

//return the index of the minimum element in array a
int min(const int * const a) {
  int m = a[0];
  int indx = 0;
  #define TEST(i) (m > a[i]) && (m = a[i], indx = i ); 
  //see http://stackoverflow.com/a/7074042/2140449
  TEST(1);
  TEST(2);
  TEST(3);
  TEST(4);
  TEST(5);
  TEST(6);
  TEST(7);
  TEST(8);
  TEST(9);
  #undef TEST
  return indx;
}

void sort( int * const a ){
  int work[10];
  int indx;
  #define GET(i) indx = min(a); work[i] = a[indx]; a[indx] = 2147483647; 
  //get the minimum, copy it to work and set it at max_int in a
  GET(0);
  GET(1);
  GET(2);
  GET(3);
  GET(4);
  GET(5);
  GET(6);
  GET(7);
  GET(8);
  GET(9);
  #undef GET
  #define COPY(i) a[i] = work[i];
  //copy back to a
  COPY(0);
  COPY(1);
  COPY(2);
  COPY(3);
  COPY(4);
  COPY(5);
  COPY(6);
  COPY(7);
  COPY(8);
  COPY(9);
  #undef COPY
}

int main() {
  //generating and printing a random array
  int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
  std::random_device rd;
  std::mt19937 g(rd());
  std::shuffle( a, a+10, g);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  }
  std::cout << std::endl;

  //sorting and printing again
  sort(a);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  } 

  return 0;
}

http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6

Sadece ilgili satırlar ilk ikisidir #define.

İki liste kullanır ve birincisini on kez tamamen kontrol eder, bu da kötü uygulanmış bir seçim sıralaması olur, ancak modern işlemcilerle ve bu kadar küçük bir veri kümesiyle telafi edebilen dallardan ve değişken uzunluktaki döngülerden kaçınır.


Karşılaştırma

Sıralama ağına karşı karşılaştırma yaptım ve kodum daha yavaş görünüyor. Ancak açmayı ve kopyayı kaldırmaya çalıştım. Bu kodu çalıştırma:

#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>

int min(const int * const a, int i) {
  int m = a[i];
  int indx = i++;
  for ( ; i<10; i++) 
    //see http://stackoverflow.com/a/7074042/2140449
    (m > a[i]) && (m = a[i], indx = i ); 
  return indx;
}

void sort( int * const a ){
  for (int i = 0; i<9; i++)
    std::swap(a[i], a[min(a,i)]); //search only forward
}


void sortNet10(int * const data) {  // ten-input sorting network by Waksman, 1969
    int swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
}


std::chrono::duration<double> benchmark( void(*func)(int * const), const int seed ) {
  std::mt19937 g(seed);
  int a[10] = {10,11,12,13,14,15,16,17,18,19};
  std::chrono::high_resolution_clock::time_point t1, t2; 
  t1 = std::chrono::high_resolution_clock::now();
  for (long i = 0; i < 1e7; i++) {
    std::shuffle( a, a+10, g);
    func(a);
  }
  t2 = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
}

int main() {
  std::random_device rd;
  for (int i = 0; i < 10; i++) {
    const int seed = rd();
    std::cout << "seed = " << seed << std::endl;
    std::cout << "sortNet10: " << benchmark(sortNet10, seed).count() << std::endl;
    std::cout << "sort:      " << benchmark(sort,      seed).count() << std::endl;
  }
  return 0;
}

Sıralama ağına göre sürekli olarak dalsız seçim sıralama için daha iyi sonuç alıyorum .

$ gcc -v
gcc version 5.2.0 (GCC) 
$ g++ -std=c++11 -Ofast sort.cpp && ./a.out
seed = -1727396418
sortNet10: 2.24137
sort:      2.21828
seed = 2003959850
sortNet10: 2.23914
sort:      2.21641
seed = 1994540383
sortNet10: 2.23782
sort:      2.21778
seed = 1258259982
sortNet10: 2.25199
sort:      2.21801
seed = 1821086932
sortNet10: 2.25535
sort:      2.2173
seed = 412262735
sortNet10: 2.24489
sort:      2.21776
seed = 1059795817
sortNet10: 2.29226
sort:      2.21777
seed = -188551272
sortNet10: 2.23803
sort:      2.22996
seed = 1043757247
sortNet10: 2.2503
sort:      2.23604
seed = -268332483
sortNet10: 2.24455
sort:      2.24304

4
Sonuçlar çok etkileyici değil, aslında beklediğim gibi. Sıralama ağı takasları değil karşılaştırmaları en aza indirir. Tüm değerler zaten önbellekte olduğunda karşılaştırmalar takaslardan çok daha ucuzdur, bu nedenle bir seçim sıralaması (takas sayısını en aza indiren) üst ele sahiptir. (ve çok daha fazla karşılaştırma yok: 29 karşılaştırmalı ağ, 29 swap'a kadar ?; vs. 45 karşılaştırmalı seçim sıralaması ve en fazla 9 swap)
örnek

7
Oh ve dalları var - çizgi for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i ); son derece iyi optimize edilmedikçe . (kısa devre genellikle bir dallanma şeklidir)
örnek

1
@EugeneRyabtsev de, ama her zaman aynı rastgele dizilerle beslenir, bu yüzden iptal etmelidir. Ben değiştirmeye çalışmış std::shuffleolan for (int n = 0; n<10; n++) a[n]=g();. Yürütme süresi yarıya indirildi ve ağ artık daha hızlı.
DarioP

Bu libc ++ 'larla nasıl karşılaştırılır std::sort?
gnzlbg

1
@gnzlbg Ben de denedim std::sortama o kadar kötü bir performans sergiliyordu, ben bile onu karşılaştırmaya dahil etmedi. Sanırım küçük veri setlerinde oldukça fazla yük var.
DarioP

20

Soru, bunun bir tür web tabanlı uygulama olduğunu söylemiyor. Gözüme çarpan tek şey şuydu:

Milyarlarca elementin veri setini örnek alıyorum ve her seferinde 10 sayı seçmem (basitleştirilmiş) ve bunları sıralamam (ve sıralı 10 element listesinden sonuçlar çıkarmam) gerekiyor.

Bir yazılım ve donanım mühendisi olarak bu kesinlikle bana "FPGA" diye bağırıyor . Sıralı sayılar kümesinden ne tür sonuçlar çıkarmanız gerektiğini veya verilerin nereden geldiğini bilmiyorum ama bu "sırala ve yüz milyar arasında bir yerde işlem yapmanın neredeyse önemsiz olacağını biliyorum. saniye başına işlemleri analiz eder . Geçmişte FPGA destekli DNA sekanslama çalışması yaptım. Sorun bu tür bir çözüm için çok uygun olduğunda FPGA'ların büyük işlem gücünü yenmek neredeyse imkansızdır.

Bir düzeyde, tek sınırlayıcı faktör, verileri bir FPGA'ya ne kadar çabuk kürekleyebileceğiniz ve ne kadar çabuk çıkarabileceğinizdir.

Bir referans noktası olarak, saniyede yaklaşık 300 milyon piksel hızında 32 bit RGB görüntü verisi alan yüksek performanslı gerçek zamanlı bir görüntü işlemcisi tasarladım. Veriler FIR filtreleri, matris çarpanları, arama tabloları, uzamsal kenar algılama blokları ve diğer sondan çıkmadan önce bir dizi başka işlemden aktarıldı. Bunların hepsi, yaklaşık 33MHz'den doğru hatırlarsam 400MHz'e kadar iç saatli bir alana sahip nispeten küçük bir Xilinx Virtex2 FPGA'da. Oh, evet, ayrıca bir DDR2 denetleyici uygulaması vardı ve iki DDR2 bellek bankası çalıştırdı.

Bir FPGA, yüzlerce MHz'de çalışırken her saat geçişinde bir tür on 32 bit sayı verebilir. Veriler işleme boru hatlarını / hatlarını doldurduğundan, işlemin başlangıcında kısa bir gecikme olacaktır. Bundan sonra saat başına bir sonuç alabilmelisiniz. Veya, işleme, sıralama ve analiz boru hattının çoğaltılmasıyla paralel hale getirilebiliyorsa. Prensip olarak çözüm neredeyse önemsizdir.

Mesele şu: Uygulama PC'ye bağlı değilse ve veri akışı ve işleme bir FPGA çözümüyle (tek başına veya makinede yardımcı işlemci kartı olarak) "uyumluysa" gitmeniz mümkün değildir Algoritma ne olursa olsun, herhangi bir dilde yazılmış yazılımla elde edilebilir performans seviyesini geçebilme.

DÜZENLE:

Sadece hızlı arama yaptım ve sizin için yararlı olabilecek bir makale buldum. Görünüşe göre 2012 yılına dayanıyor. Bugün performansta çok daha iyi (ve o zaman bile). İşte burada:

FPGA'larda Ağları Sıralama


10

Son zamanlarda derleme zamanında bir sıralama ağı oluşturmak için Bose-Nelson algoritmasını kullanan küçük bir sınıf yazdım .

10 numara için çok hızlı bir sıralama oluşturmak için kullanılabilir.

/**
 * A Functor class to create a sort for fixed sized arrays/containers with a
 * compile time generated Bose-Nelson sorting network.
 * \tparam NumElements  The number of elements in the array or container to sort.
 * \tparam T            The element type.
 * \tparam Compare      A comparator functor class that returns true if lhs < rhs.
 */
template <unsigned NumElements, class Compare = void> class StaticSort
{
    template <class A, class C> struct Swap
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            T t = Compare()(v0, v1) ? v0 : v1; // Min
            v1 = Compare()(v0, v1) ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A> struct Swap <A, void>
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            // Explicitly code out the Min and Max to nudge the compiler
            // to generate branchless code.
            T t = v0 < v1 ? v0 : v1; // Min
            v1 = v0 < v1 ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A, class C, int I, int J, int X, int Y> struct PB
    {
        inline PB(A &a)
        {
            enum { L = X >> 1, M = (X & 1 ? Y : Y + 1) >> 1, IAddL = I + L, XSubL = X - L };
            PB<A, C, I, J, L, M> p0(a);
            PB<A, C, IAddL, J + M, XSubL, Y - M> p1(a);
            PB<A, C, IAddL, J, XSubL, M> p2(a);
        }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 1>
    {
        inline PB(A &a) { Swap<A, C> s(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 2>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J); Swap<A, C> s1(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 2, 1>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J - 1); Swap<A, C> s1(a, I, J - 1); }
    };

    template <class A, class C, int I, int M, bool Stop = false> struct PS
    {
        inline PS(A &a)
        {
            enum { L = M >> 1, IAddL = I + L, MSubL = M - L};
            PS<A, C, I, L, (L <= 1)> ps0(a);
            PS<A, C, IAddL, MSubL, (MSubL <= 1)> ps1(a);
            PB<A, C, I, IAddL, L, MSubL> pb(a);
        }
    };

    template <class A, class C, int I, int M> struct PS <A, C, I, M, true>
    {
        inline PS(A &a) {}
    };

public:
    /**
     * Sorts the array/container arr.
     * \param  arr  The array/container to be sorted.
     */
    template <class Container> inline void operator() (Container &arr) const
    {
        PS<Container, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };

    /**
     * Sorts the array arr.
     * \param  arr  The array to be sorted.
     */
    template <class T> inline void operator() (T *arr) const
    {
        PS<T*, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };
};

#include <iostream>
#include <vector>

int main(int argc, const char * argv[])
{
    enum { NumValues = 10 };

    // Arrays
    {
        int rands[NumValues];
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    std::cout << "\n";

    // STL Vector
    {
        std::vector<int> rands(NumValues);
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    return 0;
}

Bir if (compare) swapifade yerine, üçlü operatörleri min ve max olarak açıkça kodladığımızı unutmayın. Bu, derleyiciyi dalsız kod kullanarak sürüklemeye yardımcı olmak içindir.

Deneyler

Aşağıdaki kriterler clang -O3 ile derlenmiş ve 2012 ortası macbook air'imde yayınlanmıştır.

Rasgele verileri sıralama

DarioP koduyla karşılaştırıldığında, 10 büyüklüğünde 1 milyon 32 bit int dizisini sıralamak için geçen milisaniye sayısı:

Sabit Kodlu Sıralama Net 10: 88.774 ms
Templated Bose-Nelson sıralama 10: 27.815 ms

Bu şablon yaklaşımı kullanarak, diğer sayıda öğe için derleme zamanında sıralama ağları da oluşturabiliriz.

Çeşitli boyutlardaki 1 milyon diziyi sıralama zamanı (milisaniye cinsinden).
2, 4, 8 boyutlu diziler için milisaniye sayısı sırasıyla 1.943, 8.655, 20.246'dır.
C ++ Templated Bose-Nelson Statik Sıralama zamanlamaları

Açılmamış yerleştirme sıralaması için Glenn Teitelbaum'a kredi .

İşte 6 elementten oluşan küçük diziler için tür başına ortalama saatler. Karşılaştırma kodu ve örnekleri şu soruda bulunabilir:
En hızlı sabit uzunluk 6 int dizisi

Direct call to qsort library function       : 326.81
Naive implementation (insertion sort)       : 132.98
Insertion Sort (Daniel Stutzbach)           : 104.04
Insertion Sort Unrolled                     : 99.64
Insertion Sort Unrolled (Glenn Teitelbaum)  : 81.55
Rank Order                                  : 44.01
Rank Order with registers                   : 42.40
Sorting Networks (Daniel Stutzbach)         : 88.06
Sorting Networks (Paul R)                   : 31.64
Sorting Networks 12 with Fast Swap          : 29.68
Sorting Networks 12 reordered Swap          : 28.61
Reordered Sorting Network w/ fast swap      : 24.63
Templated Sorting Network (this class)      : 25.37

6 eleman için sorudaki en hızlı örnek kadar hızlı performans gösterir.

Sıralı verileri sıralama performansı

Genellikle, girdi dizileri zaten sıralanmış veya çoğunlukla sıralanmış olabilir.
Bu gibi durumlarda, yerleştirme sıralaması daha iyi bir seçim olabilir.

resim açıklamasını buraya girin

Verilere bağlı olarak uygun bir sıralama algoritması seçmek isteyebilirsiniz.

Karşılaştırma ölçütleri için kullanılan kodu burada bulabilirsiniz .


Benim algo için bir karşılaştırma eklemek için herhangi bir şans?
Glenn Teitelbaum

Eğer bu ilave bir şans @GlennTeitelbaum sizin kriterler ve ifşa araçlar ve sonuçlar?
greybeard

Sıralı girdi sıralamak için veri eklemek için Şeref.
greybeard

Bazı sistemlerde v1 = v0 < v1 ? v1 : v0; // Maxbu değiştirilebilir bu durumda hala şube olabilir, v1 += v0 - tçünkü eğer tolduğunu v0daha sonra v1 + v0 -t == v1 + v0 - v0 == v1başka tbir v1vev1 + v0 -t == v1 + v0 - v1 == v0
Glenn Teitelbaum

Üçlü genellikle modern derleyiciler hakkında bir talimat maxssveya minssderleme yapar. Ancak işe yaramadığı durumlarda, başka takas yöntemleri kullanılabilir. :)
Vectörize

5

Bir ağ sıralamasının küçük dizilerde hızlı olma olasılığı yüksek olsa da, bazen uygun şekilde optimize edilmişse ekleme sıralamasını yenemezsiniz. Örneğin, 2 öğeli toplu kesici uç:

{
    final int a=in[0]<in[1]?in[0]:in[1];
    final int b=in[0]<in[1]?in[1]:in[0];
    in[0]=a;
    in[1]=b;
}
for(int x=2;x<10;x+=2)
{
    final int a=in[x]<in[x+1]?in[x]:in[x+1];
    final int b=in[x]<in[x+1]?in[x+1]:in[x];
    int y= x-1;

    while(y>=0&&in[y]>b)
    {
        in[y+2]= in[y];
        --y;
    }
    in[y+2]=b;
    while(y>=0&&in[y]>a)
    {
        in[y+1]= in[y];
        --y;
    }
    in[y+1]=a;
}

Neden tekrar ettiğinden emin değil misin in[y+2]= in[y];?
Glenn Teitelbaum

Wow, bunu nasıl yaptım? Ve birisinin fark etmesi ne kadar zaman aldı? Cevap: Bir yazım hatası değil: Hem anahtar hem de değer dizisine sahip farklı bir algoritmayı uyarlıyordum.
warren

3

Tamamen açabilirsiniz insertion sort

Bunu daha kolay, özyinelemeli hale getirmek için template fonksiyonlar herhangi bir fonksiyon yükü olmadan kullanılabilir. Zaten a olduğundan template, intbir templateparametre de olabilir. Bu ayrıca 10'dan farklı kodlama dizisi boyutlarının oluşturulması önemsizdir.

Sıralama int x[10]insert_sort<int, 9>::sort(x);Sınıfın son öğenin dizinini kullanması nedeniyle çağrıyı . Bu kaydırılabilir, ancak bu daha fazla kod okumak olacaktır.

template <class T, int NUM>
class insert_sort;

template <class T>
class insert_sort<T,0>
// stop template recursion
// sorting 1 item is a no-op
{
public:
    static void place(T *x) {}
    static void sort(T * x) {}
};

template <class T, int NUM>
class insert_sort
// use template recursion to do insertion sort
// NUM is the index of the last item, eg. for x[10] call <9>
{
public:
    static void place(T *x)
    {
        T t1=x[NUM-1];
        T t2=x[NUM];
        if (t1 > t2)
        {
            x[NUM-1]=t2;
            x[NUM]=t1;
            insert_sort<T,NUM-1>::place(x);
        }
    }
    static void sort(T * x)
    {
        insert_sort<T,NUM-1>::sort(x); // sort everything before
        place(x);                    // put this item in
    }
};

Testlerimde bu, sıralama ağı örneklerinden daha hızlıydı.


0

Burada açıkladığımla benzer nedenlerden dolayı , sıralama ağının buradan alındığı aşağıdaki sıralama işlevleri sort6_iterator()ve sort10_iterator_local()iyi performans göstermelidir :

template<class IterType> 
inline void sort10_iterator(IterType it) 
{
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   auto data##a=*(data+a);
#define DD2(a,b) auto data##a=*(data+a), data##b=*(data+b);
#define CB1(a)   *(data+a)=data##a;
#define CB2(a,b) *(data+a)=data##a;*(data+b)=data##b;
  DD2(1,4) SORT2(1,4) DD2(7,8) SORT2(7,8) DD2(2,3) SORT2(2,3) DD2(5,6) SORT2(5,6) DD2(0,9) SORT2(0,9) 
  SORT2(2,5) SORT2(0,7) SORT2(8,9) SORT2(3,6) 
  SORT2(4,9) SORT2(0,1) 
  SORT2(0,2) CB1(0) SORT2(6,9) CB1(9) SORT2(3,5) SORT2(4,7) SORT2(1,8) 
  SORT2(3,4) SORT2(5,8) SORT2(6,7) SORT2(1,2) 
  SORT2(7,8) CB1(8) SORT2(1,3) CB1(1) SORT2(2,5) SORT2(4,6) 
  SORT2(2,3) CB1(2) SORT2(6,7) CB1(7) SORT2(4,5) 
  SORT2(3,4) CB2(3,4) SORT2(5,6) CB2(5,6) 
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

Bu işlevi çağırmak için bir std::vectoryineleyici geçtim .


0

Bir yerleştirme sıralaması, en iyi 9 ve en kötü 45 olan 10 girdiyi sıralamak için ortalama 29,6 karşılaştırmayı gerektirir (ters sırada verilen giriş verilir).

Bir {9,6,1} mermisi, 10 girdiyi sıralamak için ortalama 25,5 karşılaştırmayı gerektirecektir. En iyi durum 14 karşılaştırma, en kötü 34 ve tersine bir girişi sıralama 22 gerektirir.

Bu nedenle, yerleştirme sıralaması yerine mermi kullanmak ortalama kasayı% 14 azaltır. En iyi durum% 56 artırılsa da, en kötü durum% 24 azaltılır; bu durum en kötü durum performansının kontrol altında tutulmasının önemli olduğu uygulamalarda önemlidir. Ters durum% 51 azaltılır.

Ekleme sıralamasına aşina olduğunuzdan, algoritmayı {9,6} için bir sıralama ağı olarak uygulayabilir ve daha sonra ekleme sıralamasını ({1}) bundan sonra uygulayabilirsiniz:

i[0] with i[9]    // {9}

i[0] with i[6]    // {6}
i[1] with i[7]    // {6}
i[2] with i[8]    // {6}
i[3] with i[9]    // {6}

i[0 ... 9]        // insertion sort
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.