10 नंबर सॉर्ट करने का सबसे तेज़ तरीका? (संख्या 32 बिट हैं)


211

मैं एक समस्या को हल कर रहा हूं और इसमें 10 नंबर (int32) को बहुत जल्दी छांटना शामिल है। मेरे आवेदन को जितनी जल्दी हो सके 10 बार लाखों की संख्या में सॉर्ट करने की आवश्यकता है। मैं अरबों तत्वों के डेटा सेट का नमूना ले रहा हूं और हर बार मुझे इसमें से 10 नंबर निकालने की जरूरत है (सरलीकृत) और उन्हें क्रमबद्ध करें (और सॉर्ट किए गए 10 तत्व सूची से निष्कर्ष बनाएं)।

वर्तमान में मैं प्रविष्टि सॉर्ट का उपयोग कर रहा हूं, लेकिन मुझे लगता है कि मैं 10 नंबर की मेरी विशिष्ट समस्या के लिए एक बहुत तेज़ कस्टम सॉर्टिंग एल्गोरिथ्म को लागू कर सकता हूं जो प्रविष्टि सॉर्ट को हरा देगा।

क्या किसी को इस समस्या के बारे में कोई विचार है?


14
जैसा कि क्रूड लगता है, नेस्टेड ifस्टेटमेंट की एक श्रृंखला को सबसे अच्छा काम करना चाहिए। छोरों से बचें।
जॉन अलेक्सिउ

8
क्या आप उम्मीद करते हैं कि क्रमपरिवर्तन के सेट में किसी पूर्वाग्रह के साथ आपको नंबर दिए जाएंगे, या उन्हें समान रूप से वितरित किया जाएगा? क्या एक सूची के क्रम और अगले के बीच कोई संबंध होगा?
डगलस ज़ेरे

4
पूरा डेटा सेट (अरबों की संख्या के साथ) बेनफोर्ड के कानून के अनुसार वितरित किया जाता है, लेकिन जब मैं इस सेट से बेतरतीब ढंग से तत्वों को चुनता हूं, तो वे अब नहीं हैं (मुझे लगता है)।
बोडैसिडो


11
यदि आप अरबों तत्वों से बेतरतीब ढंग से चयन कर रहे हैं, तो यह बहुत संभव है कि उस डेटा को खींचने के लिए विलंबता चयनित तत्वों को छाँटने के लिए आवश्यक समय से अधिक प्रभाव हो, भले ही संपूर्ण डेटा सेट रैम में हो। आप क्रमिक रूप से बनाम क्रमिक रूप से डेटा का चयन करके बेंचमार्किंग प्रदर्शन द्वारा प्रभाव का परीक्षण कर सकते हैं।
स्टीव एस।

जवाबों:


213

(सॉर्टिंग नेटवर्क में देखने के लिए HelloWorld के सुझाव पर चलें।)

ऐसा लगता है कि 10-इनपुट सॉर्ट करने के लिए 29-तुलना / स्वैप नेटवर्क सबसे तेज़ तरीका है। मैंने 1969 में जावास्क्रिप्ट में इस उदाहरण के लिए जावास्क्रिप्ट में खोजे गए नेटवर्क का उपयोग किया, जिसे सीधे सी में अनुवाद करना चाहिए, क्योंकि यह सिर्फ ifबयानों, तुलनाओं और स्वैप की सूची है।

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]));

यहां स्वतंत्र चरणों में विभाजित नेटवर्क का चित्रमय प्रतिनिधित्व है। समानांतर प्रसंस्करण का लाभ उठाने के लिए, 5-4-3-4-4-4-3-2 समूह को 4-4-4-4-4-4-3-2-2 मिनट में बदला जा सकता है।
10-इनपुट सॉर्टिंग नेटवर्क (वाक्समैन, 1969)

10-इनपुट सॉर्टिंग नेटवर्क (वाक्समैन, 1969) फिर से समूहीकृत


69
सुझाव; स्वैप मैक्रो का उपयोग करें। जैसे#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
पीटर कॉर्ड्स

9
क्या यह तार्किक रूप से दिखाया जा सकता है कि यह न्यूनतम है?
corsiKa

8
@corsiKa हाँ, सॉर्टिंग नेटवर्क कंप्यूटर विज्ञान के शुरुआती दिनों से अनुसंधान का एक क्षेत्र रहा है। कई मामलों में, इष्टतम समाधान दशकों से ज्ञात हैं। देखें en.wikipedia.org/wiki/Sorting_network
M69 '' snarky और unwelcoming ''

8
मैंने परीक्षण करने के लिए एक Jsperf बनाया और मैं पुष्टि कर सकता हूं कि नेटवर्क सॉर्ट ब्राउज़र की मूल सॉर्ट से 20 गुना अधिक तेज़ है। jsperf.com/fastest-10-number-sort
डैनियल

9
@ कताई यह आपके कंपाइलर द्वारा निर्मित किसी भी अनुकूलन को नष्ट कर देगा। बुरा विचार। इसे और अधिक जानकारी के लिए पढ़ें en.wikipedia.org/wiki/…
Antzi

88

जब आप इस निश्चित आकार से निपटते हैं, तो सॉर्टिंग नेटवर्क पर एक नज़र डालें । इन एल्गोरिदम में एक निश्चित रनटाइम होता है और वे अपने इनपुट के लिए स्वतंत्र होते हैं। आपके उपयोग-मामले के लिए आपके पास ऐसा ओवरहेड नहीं है जो कुछ सॉर्टिंग एल्गोरिदम के पास है।

बिटोनिक सॉर्ट ऐसे नेटवर्क का कार्यान्वयन है। यह एक सीपीयू पर लेन (एन) <= ​​32 के साथ सबसे अच्छा काम करता है। बड़े इनपुट पर आप किसी GPU पर जाने के बारे में सोच सकते हैं। https://en.wikipedia.org/wiki/Sorting_network

Btw, छँटाई एल्गोरिदम की तुलना करने के लिए एक अच्छा पृष्ठ यह यहाँ एक है (हालांकि इसकी याद आ रही है bitonic sort

http://www.sorting-algorithms.com


3
@ ErickG.Hagstrom कई समाधान हैं; जब तक वे 29 तुलनाओं का उपयोग करते हैं, वे समान रूप से कुशल हैं। मैंने 1969 से वक्समैन के समाधान का इस्तेमाल किया; वह स्पष्ट रूप से 29-तुलना संस्करण की खोज करने वाले पहले व्यक्ति थे।
m69 '' झपकीदार और बेखबर '' 1

1
हाँ, @ m69। एक मिलियन से अधिक हैं। वैक्समैन के समाधान की लंबाई 29 है, और 9 की गहराई। जो समाधान मैंने जोड़ा है वह गहराई आयाम में एक सुधार है: लंबाई = 29, गहराई = 8. बेशक, जब सी में लागू किया जाता है, तो गहराई मायने नहीं रखती है।
एरिक जी। हागस्ट्रॉम

4
@ ErickG.Hagstrom जाहिरा तौर पर गहराई 7 के साथ 87 समाधान हैं, जिनमें से सबसे पहले 1973 में नुथ ने पाया था, लेकिन मैं उनमें से किसी को भी त्वरित Google के साथ नहीं ढूंढ पाया। larc.unt.edu/ian/pubs/9-input.pdf (निष्कर्ष देखें, पृ। 14)
m69 '' झपकी लेना और भटकाना ''

4
@ ErickG.Hagstrom: गहराई में "C स्तर पर" कोई अंतर नहीं हो सकता है, लेकिन संभवतः एक बार संकलक और CPU इसके साथ समाप्त हो गए हैं, कुछ संभावना है कि यह आंशिक रूप से CPU के भीतर समानांतर हो जाएगा और इसलिए छोटी गहराई मदद कर सकती है। सीपीयू के आधार पर, निश्चित रूप से: कुछ सीपीयू अपेक्षाकृत सरल होते हैं और एक के बाद एक काम करते हैं, जबकि कुछ सीपीयू में उड़ान में कई ऑपरेशन हो सकते हैं, विशेष रूप से आपको स्टैक के लिए किसी भी लोड और स्टोर के लिए बहुत अलग प्रदर्शन मिल सकता है, जिसकी आवश्यकता है 10 चर का हेरफेर करने का आदेश, वे कैसे किया जाता है पर निर्भर करता है।
स्टीव जेसोप

1
@ ErickG.Hagstrom यह इयान परबेरी द्वारा कागज से तुरंत स्पष्ट नहीं था, लेकिन गहराई -7 नेटवर्क की लंबाई 29 से अधिक है। नथ देखें, "द आर्ट ऑफ़ कंप्यूटर प्रोग्रामिंग Vol.III", §5.3.4, अंजीर । 49 और 51.
M69 '' snarky और unwelcoming ''

33

एक सॉर्टिंग नेटवर्क का उपयोग करें जिसकी 4 के समूहों में तुलना है, इसलिए आप इसे SIMD रजिस्टरों में कर सकते हैं। पैक्ड मिन / मैक्स निर्देशों की एक जोड़ी एक पैक्ड तुलनित्र फ़ंक्शन को लागू करती है। क्षमा करें, मेरे पास इस बारे में देखने वाले एक पृष्ठ को देखने के लिए अभी समय नहीं है, लेकिन उम्मीद है कि SIMD या SSE सॉर्टिंग नेटवर्क पर खोज करने से कुछ बदल जाएगा।

x86 SSE में पैक -32 बिट-पूर्णांक मिनट और अधिकतम 32 बिट्स के वैक्टर के लिए अधिकतम निर्देश हैं। एवीएक्स 2 (हसवेल और बाद में) में 8 इंच के 256 बी वैक्टर के लिए समान है। कुशल फेरबदल निर्देश भी हैं।

यदि आपके पास बहुत छोटे स्वतंत्र प्रकार हैं, तो वैक्टर का उपयोग करके समानांतर में 4 या 8 प्रकार करना संभव हो सकता है। Esp। यदि आप बेतरतीब ढंग से तत्वों का चयन कर रहे हैं (ताकि डेटा को सॉर्ट किया जाना वैसे भी मेमोरी में सन्निहित नहीं होगा), तो आप फेरबदल से बच सकते हैं और बस उस क्रम की तुलना करें जिसकी आपको आवश्यकता है। 10 रजिस्टर 4 (AVX2: 8) से सभी डेटा रखने के लिए 10 ints की सूची अभी भी खरोंच स्थान के लिए 6 regs छोड़ देता है।

यदि आप भी संबंधित डेटा को सॉर्ट करना चाहते हैं तो वेक्टर सॉर्टिंग नेटवर्क कम कुशल हैं। उस स्थिति में, सबसे कुशल तरीका यह है कि पैक्ड-तुलना का उपयोग करने के लिए एक मास्क प्राप्त करें, जिसमें से तत्व बदल गए, और संबंधित डेटा के संदर्भों के वैक्टर को मिश्रण करने के लिए उस मास्क का उपयोग करें।


26

एक अनियंत्रित, शाखा-कम चयन प्रकार के बारे में क्या?

#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

केवल प्रासंगिक लाइनें पहले दो हैं #define

यह दो सूचियों का उपयोग करता है और पूरी तरह से पहले दस बार पुन: जांच करता है जो एक बुरी तरह से लागू चयन प्रकार होगा, हालांकि यह शाखाओं और चर लंबाई छोरों से बचता है, जो आधुनिक प्रोसेसर और इस तरह के एक छोटे डेटा सेट के साथ क्षतिपूर्ति कर सकता है।


बेंचमार्क

मैंने छँटाई नेटवर्क के खिलाफ बेंचमार्क किया, और मेरा कोड धीमा लग रहा है। हालांकि मैंने अनियंत्रित और प्रति को हटाने की कोशिश की। इस कोड को चलाना:

#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;
}

मुझे लगातार छँटाई नेटवर्क की तुलना में शाखा-कम चयन प्रकार के लिए बेहतर परिणाम मिल रहा है ।

$ 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
परिणाम बहुत प्रभावशाली नहीं हैं, लेकिन वास्तव में मैंने जो उम्मीद की थी। सॉर्टिंग नेटवर्क तुलना को कम करता है, स्वैप नहीं। जब सभी मान पहले से ही कैश में हैं तो तुलना स्वैप की तुलना में बहुत सस्ती है, इसलिए एक चयन प्रकार (जो स्वैप की संख्या को कम करता है) में ऊपरी हाथ होता है। (और देखते हैं न कि कई और अधिक की तुलना: 29 compasions, ऊपर 29 स्वैप ?; बनाम चयन करने के लिए तरह 45 तुलना के साथ और सबसे 9 स्वैप पर साथ नेटवर्क)
उदाहरण

7
ओह और इसकी शाखाएँ हैं - जब तक कि रेखा for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i ); असाधारण रूप से अच्छी तरह से अनुकूलित न हो। (शॉर्ट-सर्क्युटिंग आमतौर पर ब्रांचिंग का एक रूप है)
उदाहरण

1
@EugeneRyabtsev वह भी, लेकिन इसे हर समय एक ही यादृच्छिक अनुक्रम के साथ खिलाया जाता है, इसलिए इसे रद्द कर देना चाहिए। मैंने std::shuffleसाथ बदलने की कोशिश की for (int n = 0; n<10; n++) a[n]=g();। निष्पादन का समय आधा हो गया है और नेटवर्क अब तेज है।
DarioP

यह कैसे libc ++ की तुलना में है std::sort?
gnzlbg

1
@gnzlbg मैंने भी कोशिश की std::sortलेकिन यह इतनी बुरी तरह से प्रदर्शन कर रहा था कि मैंने इसे बेंचमार्क में शामिल नहीं किया। मुझे लगता है कि छोटे डेटा सेट के साथ काफी ओवरहेड है।
DarioP

20

सवाल यह नहीं कहता है कि यह किसी तरह का वेब-आधारित अनुप्रयोग है। मेरी नज़र में एक चीज़ थी:

मैं अरबों तत्वों के डेटा सेट का नमूना ले रहा हूं और हर बार मुझे इसमें से 10 नंबर निकालने की जरूरत है (सरलीकृत) और उन्हें क्रमबद्ध करें (और सॉर्ट किए गए 10 तत्व सूची से निष्कर्ष बनाएं)।

एक सॉफ्टवेयर और हार्डवेयर इंजीनियर के रूप में यह मेरे लिए "FPGA" चिल्लाता है। मुझे नहीं पता कि आपको संख्याओं के क्रमबद्ध सेट से किस तरह के निष्कर्ष निकालने की ज़रूरत है या डेटा कहाँ से आता है, लेकिन मुझे पता है कि एक सौ मिलियन और इन "सॉर्ट-एंड-" के बिल के बीच कहीं न कहीं प्रोसेस करना लगभग तुच्छ होगा। विश्लेषण "संचालन प्रति सेकंड । मैंने अतीत में FPGA- सहायता प्राप्त डीएनए अनुक्रमण कार्य किया है। FPGA की विशाल प्रसंस्करण शक्ति को हरा पाना लगभग असंभव है, जब समस्या उस प्रकार के समाधान के लिए अच्छी तरह से अनुकूल हो।

कुछ स्तर पर एकमात्र सीमित कारक यह हो जाता है कि आप कितनी जल्दी एक FPGA में डेटा को फावड़ा कर सकते हैं और कितनी जल्दी आप इसे बाहर निकाल सकते हैं।

एक संदर्भ के रूप में, मैंने एक उच्च प्रदर्शन वाले वास्तविक समय के छवि प्रोसेसर को डिज़ाइन किया, जिसे प्रति सेकंड लगभग 300 मिलियन पिक्सेल की दर से 32 बिट आरजीबी छवि डेटा प्राप्त हुआ। एफआईआर फिल्टर, मैट्रिक्स मल्टीप्लायर, लुकिंग टेबल, स्थानिक एज डिटेक्शन ब्लॉक और दूसरे छोर से बाहर आने से पहले कई अन्य ऑपरेशन के माध्यम से डेटा प्रवाहित होता है। 400MHz के बारे में अगर मैं सही ढंग से याद है, के बारे में 33MHz से फैले आंतरिक क्लीयरिंग के साथ अपेक्षाकृत छोटे Xilinx Virtex2 FPGA पर यह सब। ओह, हाँ, इसका DDR2 नियंत्रक कार्यान्वयन भी था और DDR2 मेमोरी के दो बैंकों को चलाया।

एक FPGA सैकड़ों मेगाहर्ट्ज पर काम करते हुए हर घड़ी संक्रमण पर दस 32 बिट संख्या का उत्पादन कर सकता है। ऑपरेशन की शुरुआत में थोड़ी देरी होगी क्योंकि डेटा प्रोसेसिंग पाइपलाइन / एस को भरता है। उसके बाद आपको प्रति घड़ी एक परिणाम प्राप्त करने में सक्षम होना चाहिए। या इससे अधिक अगर सॉर्ट-एंड-एनालिसिस पाइपलाइन की प्रतिकृति के माध्यम से प्रसंस्करण को समानांतर किया जा सकता है। सिद्धांत रूप में, समाधान लगभग तुच्छ है।

मुद्दा यह है: यदि एप्लिकेशन पीसी-बाउंड नहीं है और डेटा स्ट्रीम और प्रोसेसिंग एक FPGA समाधान के साथ "संगत" है (या तो स्टैंड-अलोन या मशीन में सह-प्रोसेसर कार्ड के रूप में) कोई रास्ता नहीं है जिससे आप जा रहे हैं एल्गोरिथ्म की परवाह किए बिना, किसी भी भाषा में लिखे गए सॉफ़्टवेयर के साथ प्रदर्शन के प्राप्य स्तर को हरा सकने में सक्षम होना।

संपादित करें:

बस त्वरित खोज की और एक कागज पाया जो आपके लिए उपयोगी हो सकता है। ऐसा लग रहा है कि यह 2012 की है। आप आज (और फिर वापस भी) प्रदर्शन में बेहतर कर सकते हैं। यह रहा:

FPGAs पर सॉर्टिंग नेटवर्क


10

मैंने हाल ही में एक छोटा वर्ग लिखा है जो संकलन समय पर सॉर्टिंग नेटवर्क उत्पन्न करने के लिए बोस-नेल्सन एल्गोरिदम का उपयोग करता है।

इसका उपयोग 10 नंबर के लिए एक बहुत तेज़ सॉर्ट बनाने के लिए किया जा सकता है।

/**
 * 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;
}

ध्यान दें कि एक if (compare) swapबयान के बजाय , हम स्पष्ट रूप से न्यूनतम और अधिकतम के लिए टर्नरी ऑपरेटरों को कोड करते हैं। यह संकलक कोड का उपयोग करके संकलक को कुहनी से हलका करने में मदद करता है।

मानक

निम्नलिखित बेंचमार्क clang -O3 के साथ संकलित किए गए हैं और मेरे मध्य 2012 मैकबुक एयर पर चले गए हैं।

यादृच्छिक डेटा को सॉर्ट करना

इसे DarioP के कोड के साथ तुलना करते हुए, यहां मिलीसेकंड की संख्या को आकार 10 की 1 मिलियन 32-बिट अंतर सारणी की तरह लिया जाता है:

हार्डकोड सॉर्ट नेट 10: 88.774 एमएस अस्थायी
बोस-नेल्सन सॉर्ट 10: 27.815 एमएस

इस अस्थायी दृष्टिकोण का उपयोग करके, हम अन्य तत्वों की संख्या के लिए संकलन समय पर छँटाई नेटवर्क भी उत्पन्न कर सकते हैं।

समय (मिलीसेकंड में) विभिन्न आकारों के 1 मिलियन सरणियों को सॉर्ट करने के लिए।
आकार 2, 4, 8 के सरणियों के लिए मिलीसेकंड की संख्या क्रमशः 1.943, 8.655, 20.246 है।
सी ++ अस्थायी बोस-नेल्सन स्थैतिक क्रमबद्ध समय

क्रेडिट अनियंत्रित प्रविष्टि प्रकार के लिए ग्लेन Teitelbaum को।

यहां 6 तत्वों के छोटे सरणियों के लिए प्रति घंटे औसत घड़ियां हैं। बेंचमार्क कोड और उदाहरण इस सवाल पर पाए जा सकते हैं:
निश्चित लंबाई 6 इंट सरणी का सबसे तेज़ प्रकार

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 तत्वों के लिए प्रश्न में सबसे तेज़ उदाहरण के रूप में तेज़ प्रदर्शन करता है।

सॉर्ट किए गए डेटा को सॉर्ट करने के लिए प्रदर्शन

अक्सर, इनपुट सरणियों को पहले से ही हल किया जा सकता है या अधिकतर क्रमबद्ध किया जा सकता है।
ऐसे मामलों में, प्रविष्टि प्रकार बेहतर विकल्प हो सकता है।

यहां छवि विवरण दर्ज करें

आप डेटा के आधार पर एक उपयुक्त छँटाई एल्गोरिथ्म चुनना चाह सकते हैं।

बेंचमार्क के लिए इस्तेमाल किया गया कोड यहां पाया जा सकता है


कोई भी मौका आप नीचे दिए गए मेरे अहंकार के लिए एक तुलना जोड़ सकते हैं?
ग्लेन टिटेलबाम

@GlennTeitelbaum किसी भी मौका है जिसे आपने अपने बेंचमार्क में जोड़ा है और इसका मतलब और परिणाम का खुलासा किया है?
ग्रेबर्ड

सॉर्टिंग सॉर्ट किए गए इनपुट पर डेटा जोड़ने के लिए यश।
ग्रेबर्ड

कुछ सिस्टम पर v1 = v0 < v1 ? v1 : v0; // Maxअभी भी शाखाएं भी हो सकती, उस मामले में यह साथ बदला जा सकता v1 += v0 - tहै, तो क्योंकि tहै v0तो v1 + v0 -t == v1 + v0 - v0 == v1बाकी tहै v1औरv1 + v0 -t == v1 + v0 - v1 == v0
ग्लेन Teitelbaum

टर्नरी आमतौर पर आधुनिक संकलक पर एक निर्देश maxssया minssनिर्देश में संकलित करता है । लेकिन ऐसे मामलों में जहां यह काम नहीं करता है, स्वैपिंग के अन्य तरीकों का उपयोग किया जा सकता है। :)
vectorized

5

यद्यपि एक नेटवर्क सॉर्ट में छोटे एरेज़ पर तेज़ होने की अच्छी संभावना है, कभी-कभी आप सम्मिलन प्रकार को हरा नहीं सकते हैं यदि ठीक से अनुकूलित किया गया हो। उदाहरण के लिए 2 तत्वों के साथ बैच डालें:

{
    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;
}

यकीन नहीं है कि आप क्यों दोहराते हैं in[y+2]= in[y];, टाइपो?
ग्लेन टेइटेलबौम

वाह, मैंने ऐसा कैसे किया? और किसी को नोटिस करने में इतना समय कैसे लगा? उत्तर: यह टाइपो नहीं है: मैं एक अलग एल्गोरिथ्म को अपना रहा था जिसमें एक कुंजी और एक मूल्य सरणी दोनों थी।
वॉरेन ने

3

आप पूरी तरह से अनियंत्रित हो सकते हैं insertion sort

यह आसान बनाने के लिए, पुनरावर्ती templates का उपयोग बिना किसी फ़ंक्शन के किया जा सकता है। चूंकि यह पहले से ही है template, इसलिए पैरामीटर intभी हो सकता है template। यह भी बनाने के लिए 10 तुच्छ के अलावा अन्य सरणी आकार कोडिंग करता है।

ध्यान दें कि int x[10]कॉल को क्रमबद्ध करने के लिए है insert_sort<int, 9>::sort(x);क्योंकि वर्ग अंतिम आइटम के सूचकांक का उपयोग करता है। यह लपेटा जा सकता है, लेकिन इसके माध्यम से पढ़ने के लिए अधिक कोड होगा।

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
    }
};

मेरे परीक्षण में यह छँटाई नेटवर्क उदाहरणों की तुलना में तेज़ था।


0

उन कारणों के समान जिनके बारे में मैंने यहां बताया , निम्नलिखित छँटाई कार्य, sort6_iterator()और sort10_iterator_local(), अच्छा प्रदर्शन करना चाहिए, जहाँ छँटाई नेटवर्क यहाँ से लिया गया था :

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
}

इस फ़ंक्शन को कॉल करने के लिए मैंने इसे एक std::vectorइट्रेटर पास किया।


0

एक सम्मिलन प्रकार को 9 के सबसे अच्छे मामले के साथ 10 इनपुट को सॉर्ट करने के लिए औसत 29,6 तुलना की आवश्यकता होती है और 45 का सबसे खराब (रिवर्स इनपुट जो रिवर्स ऑर्डर में है)।

एक {9,6,1} के गोले को 10 आदानों को छांटने के लिए औसतन 25.5 तुलनाओं की आवश्यकता होगी। सबसे अच्छा मामला 14 तुलनाओं का है, सबसे खराब 34 है और उलटे इनपुट को छांटने के लिए 22 की आवश्यकता होती है।

इसलिए प्रविष्टि सॉर्ट के बजाय शेल्स्ॉर्ट का उपयोग औसत मामले को 14% कम कर देता है। हालांकि सबसे अच्छे मामले को 56% तक बढ़ा दिया जाता है, लेकिन सबसे खराब स्थिति को 24% तक कम कर दिया जाता है, जो उन अनुप्रयोगों में महत्वपूर्ण है जहां सबसे खराब स्थिति का प्रदर्शन महत्वपूर्ण है। रिवर्स केस 51% तक कम हो जाता है।

चूंकि आप सम्मिलन के प्रकार से परिचित हैं, आप एल्गोरिथ्म को {9,6} के लिए सॉर्टिंग नेटवर्क के रूप में लागू कर सकते हैं और फिर उसके बाद प्रविष्टि प्रकार ({1}) पर व्यवहार कर सकते हैं:

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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.