निश्चित लंबाई 6 इंट सरणी का सबसे तेज़ प्रकार


401

एक और स्टैक ओवरफ्लो प्रश्न का उत्तर देना ( यह एक ) मैं एक दिलचस्प उप-समस्या पर ठोकर खाई। 6 पूर्णांक की एक सरणी को सॉर्ट करने का सबसे तेज़ तरीका क्या है?

चूंकि प्रश्न बहुत निम्न स्तर का है:

  • हम यह नहीं मान सकते कि पुस्तकालय उपलब्ध हैं (और कॉल की अपनी लागत है), केवल सादे सी
  • निर्देश पाइपलाइन (एक है कि खाली करने से बचने के लिए बहुत उच्च लागत) हम शायद, शाखाओं को कम करना चाहिए कूदता है, और नियंत्रण के हर दूसरे प्रकार तोड़ने प्रवाह (में अनुक्रम अंक पीछे छिपा उन जैसे &&या ||)।
  • कमरा विवश है और रजिस्टरों को कम करना और मेमोरी का उपयोग एक मुद्दा है, आदर्श रूप में जगह की तरह संभवतः सबसे अच्छा है।

वास्तव में यह प्रश्न एक प्रकार का गोल्फ है जहां लक्ष्य स्रोत की लंबाई को कम करना नहीं है बल्कि निष्पादन समय है। मैं माइकल ज़्राश और उसके सीक्वल द्वारा कोड ऑप्टिमाइज़ेशन की पुस्तक ज़ेन के शीर्षक में उपयोग के रूप में इसे 'ज़िंगन' कोड कहता हूं ।

जैसा कि क्यों यह दिलचस्प है, कई परतें हैं:

  • उदाहरण सरल और समझने में आसान है, जिसमें सी कौशल शामिल नहीं है
  • यह समस्या के लिए एक अच्छे एल्गोरिदम की पसंद के प्रभावों को दिखाता है, लेकिन कंपाइलर और अंतर्निहित हार्डवेयर के प्रभावों को भी दर्शाता है।

यहां मेरा संदर्भ (भोली, अनुकूलित नहीं) कार्यान्वयन और मेरा परीक्षण सेट है।

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

कच्चे परिणाम

जैसा कि कई प्रकार के संस्करण बड़े होते जा रहे हैं, मैंने उन सभी को एक परीक्षण सूट में इकट्ठा किया जो यहां पाया जा सकता है । उपयोग किए गए वास्तविक परीक्षण केविन स्टॉक की बदौलत ऊपर दिखाए गए लोगों की तुलना में थोड़े कम भोले हैं। आप इसे अपने परिवेश में संकलित और निष्पादित कर सकते हैं। मैं अलग-अलग लक्ष्य वास्तुकला / संकलक के व्यवहार से काफी दिलचस्पी रखता हूं। (ठीक है दोस्तों, इसे उत्तर में रखिए, मैं एक नए परिणाम के प्रत्येक योगदानकर्ता को +1 करूँगा)।

मैंने एक साल पहले डैनियल स्टुट्ज़बाक (गोल्फ के लिए) को जवाब दिया था क्योंकि वह उस समय सबसे तेज़ समाधान के स्रोत पर था (नेटवर्क को छांटना)।

लिनक्स 64 बिट्स, gcc 4.6.1 64 बिट्स, इंटेल कोर 2 डुओ E8400, -ओ 2

  • Qsort लाइब्रेरी फ़ंक्शन के लिए सीधे कॉल: 689.38
  • Naive कार्यान्वयन (प्रविष्टि प्रकार): 285.70
  • निवेशन सॉर्ट (डैनियल स्टुट्ज़बेक): 142.12
  • सम्मिलन क्रमबद्ध अनियंत्रित: 125.47
  • रैंक ऑर्डर: 102.26
  • रजिस्टरों के साथ रैंक ऑर्डर: 58.03
  • सॉर्टिंग नेटवर्क (डैनियल स्टुट्ज़बेक): 111.68
  • सॉर्टिंग नेटवर्क (पॉल आर): 66.36
  • फास्ट स्वैप के साथ छंटनी नेटवर्क 12: 58.86
  • सॉर्टिंग नेटवर्क 12 ने स्वैप्ट को पुनः व्यवस्थित किया: 53.74
  • सॉर्टिंग नेटवर्क 12 ने सरल स्वैप को फिर से चलाया: 31.54
  • पुन: क्रमबद्ध सॉर्टिंग नेटवर्क w / तेज स्वैप: 31.54
  • पुन: क्रमबद्ध नेटवर्क W / तेज स्वैप V2: 33.63
  • इनबिल्ड बबल सॉर्ट (पाओलो बोन्जिनी): 48.85
  • अनियंत्रित प्रविष्टि सॉर्ट (पाओलो बोन्जिनी): 75.30

लिनक्स 64 बिट्स, gcc 4.6.1 64 बिट्स, इंटेल कोर 2 डुओ E8400, -ओ 1

  • Qsort लाइब्रेरी फ़ंक्शन के लिए सीधे कॉल: 705.93
  • Naive कार्यान्वयन (प्रविष्टि प्रकार): 135.60
  • निवेशन सॉर्ट (डैनियल स्टुट्ज़बेक): 142.11
  • सम्मिलन क्रमबद्ध अनियंत्रित: 126.75
  • रैंक क्रम: 46.42
  • रजिस्टर आदेश के साथ रजिस्टर: 43.58
  • सॉर्टिंग नेटवर्क (डैनियल स्टुट्ज़बेक): 115.57
  • सॉर्टिंग नेटवर्क (पॉल आर): 64.44
  • फास्ट स्वैप के साथ छंटनी नेटवर्क 12: 61.98
  • सॉर्टिंग नेटवर्क 12 ने स्वैप्ट को फिर से शुरू किया: 54.67
  • सॉर्टिंग नेटवर्क 12 ने सरल स्वैप को फिर से चलाया: 31.54
  • पुन: क्रमबद्ध छँटाई नेटवर्क w / तेज स्वैप: 31.24
  • सॉर्ट किए गए सॉर्टिंग नेटवर्क w / तेज स्वैप V2: 33.07
  • इनबिल्ड बबल सॉर्ट (पाओलो बोन्जिनी): 45.79
  • अनियंत्रित प्रविष्टि सॉर्ट (पाओलो बोन्जिनी): 80.15

मैंने -O1 और -O2 दोनों परिणामों को शामिल किया क्योंकि आश्चर्यजनक रूप से कई कार्यक्रमों के लिए O2 O1 से कम कुशल है। मुझे आश्चर्य है कि इस विशिष्ट अनुकूलन का क्या प्रभाव है?

प्रस्तावित समाधान पर टिप्पणियाँ

सम्मिलन क्रमबद्ध करें (डैनियल स्टुटज़बक)

जैसा कि अपेक्षित न्यूनतम शाखाओं वास्तव में एक अच्छा विचार है।

सॉर्टिंग नेटवर्क (डैनियल स्टुट्ज़बाक)

प्रविष्टि प्रकार से बेहतर है। मुझे आश्चर्य है कि बाहरी लूप से बचने से मुख्य प्रभाव नहीं मिला। मैंने इसे जांचने के लिए अनियंत्रित सम्मिलन के प्रकार की कोशिश की और वास्तव में हमें लगभग समान आंकड़े मिलते हैं (कोड यहाँ है )।

सॉर्टिंग नेटवर्क (पॉल आर)

अब तक का सबसे अच्छा। मैं जिस वास्तविक कोड का परीक्षण करता था वह यहाँ है । अभी तक नहीं पता कि यह अन्य छँटाई नेटवर्क कार्यान्वयन के रूप में लगभग दो गुना क्यों तेज है। पैरामीटर पासिंग? तेजी से अधिकतम?

फास्ट स्वैप के साथ छंटनी नेटवर्क 12 SWAP

जैसा कि डैनियल स्टुट्ज़बैक ने सुझाव दिया था, मैंने उनके 12 स्वैप छँटाई नेटवर्क को शाखा रहित तेज़ स्वैप (कोड यहाँ है ) के साथ जोड़ा । यह वास्तव में तेज़ है, एक छोटे से मार्जिन के साथ अब तक का सबसे अच्छा (लगभग 5%) जैसा कि 1 कम स्वैप का उपयोग करके उम्मीद की जा सकती है।

यह देखना भी दिलचस्प है कि अगर पीपीसी आर्किटेक्चर पर उपयोग करने वाले सरल की तुलना में शाखाहीन स्वैप बहुत अधिक (4 गुना) कम कुशल लगता है।

लाइब्रेरी क्यूसोर्ट कह रहे हैं

एक और संदर्भ बिंदु देने के लिए मैंने भी कोशिश की जैसा कि लाइब्रेरी कैसोर्ट को कॉल करने का सुझाव दिया गया था (कोड यहाँ है )। जैसा कि अपेक्षित था यह बहुत धीमा है: 10 से 30 गुना धीमा ... जैसा कि नए टेस्ट सूट के साथ स्पष्ट हो गया है, पहली कॉल के बाद मुख्य समस्या पुस्तकालय के शुरुआती भार की लगती है, और यह अन्य के साथ इतनी खराब तुलना नहीं करता है संस्करण। यह मेरे लिनक्स पर 3 से 20 गुना धीमा है। दूसरों द्वारा परीक्षण के लिए उपयोग किए जाने वाले कुछ आर्किटेक्चर पर यह और भी तेज लगता है (मैं वास्तव में उस एक से आश्चर्यचकित हूं, जैसा कि लाइब्रेरी क्युसर्ट एक अधिक महत्वपूर्ण एपीआई का उपयोग करता है)।

रैंक आदेश

रेक्स केर ने एक और पूरी तरह से अलग विधि प्रस्तावित की: सरणी के प्रत्येक आइटम के लिए सीधे अपनी अंतिम स्थिति की गणना करें। यह कुशल है क्योंकि कंप्यूटिंग रैंक ऑर्डर को शाखा की आवश्यकता नहीं है। इस पद्धति का दोष यह है कि यह सरणी की मेमोरी की तीन गुना (रैंक ऑर्डर को संग्रहीत करने के लिए सरणी और चर की एक प्रति) लेता है। प्रदर्शन के परिणाम बहुत ही आश्चर्यजनक (और दिलचस्प) हैं। 32 बिट्स ओएस और इंटेल कोर 2 क्वाड E8300 के साथ मेरे संदर्भ आर्किटेक्चर पर, साइकिल की गिनती 1000 से थोड़ी कम थी (जैसे कि ब्रांचिंग स्वैप के साथ नेटवर्क सॉर्ट करना)। लेकिन जब मेरे 64 बिट्स बॉक्स (Intel Core2 Duo) को संकलित और निष्पादित किया गया तो इसने बहुत बेहतर प्रदर्शन किया: यह अब तक का सबसे तेज़ बना। मुझे आखिरकार सही वजह का पता चला। मेरा 32 बिट्स बॉक्स gcc 4.4.1 और मेरे 64 बिट्स बॉक्स gcc 4.4 का उपयोग करता है।

अपडेट :

जैसा कि ऊपर प्रकाशित आंकड़ों से पता चलता है कि यह प्रभाव अभी भी बाद के संस्करणों के द्वारा बढ़ाया गया था और रैंक ऑर्डर किसी भी अन्य विकल्प की तुलना में लगातार दोगुना हो गया।

सॉर्ट किए गए नेटवर्क 12 को पुन: क्रमबद्ध स्वैप के साथ

4.4.3 gcc के साथ रेक्स केर प्रस्ताव की अद्भुत दक्षता ने मुझे आश्चर्यचकित कर दिया: कैसे 3 बार के साथ एक प्रोग्राम हो सकता है जितना कि मेमोरी उपयोग ब्रांचलेस सॉर्टिंग नेटवर्क की तुलना में तेज हो? मेरी परिकल्पना यह थी कि इसमें लिखने के बाद पढ़ी जाने वाली तरह की निर्भरता कम थी, जिससे x86 के सुपरसक्लेर इंस्ट्रक्शन शेड्यूलर के बेहतर उपयोग की अनुमति मिली। इससे मुझे अंदाजा हो गया: लिखने की निर्भरता के बाद रीड को छोटा करने के लिए रिकॉर्डर स्वैप होता है। और अधिक सीधे शब्दों में कहें: जब आप करते SWAP(1, 2); SWAP(0, 2);हैं तो आपको पहले स्वैप के लिए दूसरा एक प्रदर्शन करने से पहले समाप्त होने का इंतजार करना होगा क्योंकि दोनों एक आम मेमोरी सेल तक पहुंचते हैं। जब आप SWAP(1, 2); SWAP(4, 5);प्रोसेसर करते हैं तो दोनों समानांतर में निष्पादित हो सकते हैं। मैंने इसकी कोशिश की और यह उम्मीद के मुताबिक काम करता है, सॉर्टिंग नेटवर्क लगभग 10% तेजी से चल रहा है।

सरल स्वैप के साथ नेटवर्क 12 छंटनी

मूल पद के एक साल बाद स्टीनर एच। गुंडरसन ने सुझाव दिया, कि हमें संकलक को बाहर करने और स्वदेशी कोड को सरल रखने की कोशिश नहीं करनी चाहिए। यह वास्तव में एक अच्छा विचार है क्योंकि परिणामस्वरूप कोड लगभग 40% तेज है! उन्होंने x86 इनलाइन असेंबली कोड का उपयोग करके हाथ से अनुकूलित एक स्वैप भी प्रस्तावित किया जो अभी भी कुछ और चक्रों को छोड़ सकता है। सबसे आश्चर्यजनक (यह प्रोग्रामर के मनोविज्ञान पर संस्करणों का कहना है) यह है कि एक साल पहले स्वैप के उस संस्करण का कोई भी उपयोग नहीं किया था। मैं परीक्षण करने के लिए इस्तेमाल किया कोड यहाँ है । अन्य लोगों ने सी फास्ट स्वैप लिखने के अन्य तरीके सुझाए, लेकिन यह समान प्रदर्शन करता है जो एक सभ्य संकलक के साथ सरल है।

"सर्वश्रेष्ठ" कोड अब इस प्रकार है:

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
}

यदि हम मानते हैं कि हमारा परीक्षण सेट (और, हाँ, यह काफी घटिया है, तो यह मात्र लाभ है, जो हम माप रहे हैं, उसे समझना सरल और आसान है), एक प्रकार के लिए परिणामी कोड के चक्रों की औसत संख्या 40 चक्रों से कम है ( 6 परीक्षण निष्पादित किए जाते हैं)। प्रत्येक स्वैप को औसतन 4 चक्रों में रखा जाता है। मैं उस आश्चर्यजनक रूप से तेज कहता हूं। कोई अन्य सुधार संभव है?


2
क्या आपके पास किलों पर कुछ अड़चनें हैं? उदाहरण के लिए, क्या हम यह मान सकते हैं कि किसी भी 2 x, y के लिए x-yऔर x+yअतिप्रवाह या अतिप्रवाह का कारण नहीं होगा?
मैथ्यू एम।

3
आपको पॉल के शाखा रहित स्वैप फ़ंक्शन के साथ मेरे 12-स्वैप छँटाई नेटवर्क के संयोजन की कोशिश करनी चाहिए। उसका समाधान सभी मापदंडों को एकल सूचक के बजाय ढेर पर अलग-अलग तत्वों के रूप में देता है। इससे भी फर्क पड़ सकता है।
डैनियल स्टुटज़बेक

2
ध्यान दें कि 64-बिट पर rdtsc का सही कार्यान्वयन है __asm__ volatile (".byte 0x0f, 0x31; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");क्योंकि rdtsc EDX: EAX में उत्तर डालता है जबकि GCC इसे 64-बिट रजिस्टर में रखता है। आप -O3 पर संकलन करके बग को देख सकते हैं। इसके अलावा पॉल तेज के बारे में मेरी टिप्पणी के नीचे एक तेज स्वैप देखें।
पाओलो बोन्जिनी

3
@ टायलर: आप इसे बिना शाखा के विधानसभा स्तर पर कैसे लागू करेंगे?
लोरेन Pechtel

4
@ लोरेन: क्रमशः CMP EAX, EBX; SBB EAX, EAX0 या 0xFFFFFFFF के EAXआधार पर EAXबड़ा या छोटा होने पर निर्भर करेगा EBXSBB"उधार के साथ घटाना" है, का प्रतिरूप ADC("कैरी के साथ जोड़ें"); स्थिति थोड़ा तुम उल्लेख करने के लिए है कैरी बिट। फिर से, मुझे याद है कि ADCऔर SBBपेंटियम 4 बनाम ADDऔर पर भयानक विलंबता और थ्रूपुट था SUB, और अभी भी कोर सीपीयू पर दो बार धीमा था। 80386 के बाद से SETccसशर्त-स्टोर और CMOVccसशर्त-चाल निर्देश भी हैं, लेकिन वे भी धीमी हैं।
j_random_hacker 5

जवाबों:


162

किसी भी अनुकूलन के लिए, परीक्षण, परीक्षण, परीक्षण करना हमेशा सबसे अच्छा होता है। मैं कम से कम छँटाई नेटवर्क और प्रविष्टि सॉर्ट करने की कोशिश करूँगा। अगर मैं सट्टेबाजी कर रहा था, तो मैं पिछले अनुभव के आधार पर अपने पैसे प्रविष्टि प्रकार पर डालूंगा।

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

आपके द्वारा पोस्ट किया गया एल्गोरिथ्म एक प्रविष्टि सॉर्ट के समान है, लेकिन ऐसा लगता है कि आपने अधिक तुलना की कीमत पर स्वैप की संख्या कम कर दी है। तुलनाएं स्वैप की तुलना में कहीं अधिक महंगी हैं, हालांकि, क्योंकि शाखाएं निर्देश पाइपलाइन को स्टाल करने का कारण बन सकती हैं।

यहाँ एक प्रविष्टि सॉर्ट कार्यान्वयन है:

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

यहाँ बताया गया है कि मैं एक छँटाई नेटवर्क कैसे बनाऊँगा। सबसे पहले, उपयुक्त लंबाई के नेटवर्क के लिए SWAP मैक्रोज़ का एक न्यूनतम सेट उत्पन्न करने के लिए इस साइट का उपयोग करें । एक समारोह में लपेटकर मुझे देता है:

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: अच्छा, आपने इसे मेरे हाथ से कोडित और आनुभविक रूप से ऊपर दिए गए नेटवर्क के बजाय 12 एक्सचेंजों के साथ किया। अगर मैं आपके लिए नेटवर्क उत्पन्न करने वाली साइट के लिंक के लिए आपको एक और +1 देता, तो अब बुकमार्क किया जाता।
पॉल आर

9
यदि आप छोटे आकार के सरणियों के अधिकांश अनुरोधों की अपेक्षा करते हैं, तो यह एक सामान्य उद्देश्य के लिए एक शानदार विचार है। इस प्रक्रिया का उपयोग करके उन मामलों के लिए स्विच स्टेटमेंट का उपयोग करें जिन्हें आप अनुकूलित करना चाहते हैं; डिफ़ॉल्ट केस को लाइब्रेरी सॉर्ट फ़ंक्शन का उपयोग करने दें।
मार्क रैनसम

5
@ मर्क एक अच्छा पुस्तकालय सॉर्ट फ़ंक्शन पहले से ही छोटे सरणियों के लिए एक तेज़-पथ होगा। कई आधुनिक पुस्तकालयों में एक पुनरावर्ती क्विकॉर्ट या मर्जसॉर्ट का उपयोग किया जाएगा जो नीचे उतरने के बाद सम्मिलन में बदल जाता है n < SMALL_CONSTANT
डैनियल स्टुटज़बेक

3
@ मर्क खैर, एक सी लाइब्रेरी सॉर्ट फ़ंक्शन के लिए आवश्यक है कि आप फंक्शन पोर्टर के माध्यम से तुलना ऑपरेशन को निर्दिष्ट करें। प्रत्येक तुलना के लिए एक फ़ंक्शन को कॉल करने का ओवरहेड बहुत बड़ा है। आमतौर पर, यह अभी भी सबसे साफ रास्ता है, क्योंकि यह शायद ही कभी कार्यक्रम में एक महत्वपूर्ण मार्ग है। हालांकि, अगर यह महत्वपूर्ण मार्ग है, तो हम वास्तव में बहुत तेजी से क्रमबद्ध कर सकते हैं यदि हम जानते हैं कि हम पूर्णांक और उनमें से 6 को क्रमबद्ध कर रहे हैं। :)
डैनियल स्टटज़बक

7
@tgwh: XOR स्वैप लगभग हमेशा एक बुरा विचार है।
पॉल आर

63

यहां एक क्रमबद्ध नेटवर्क का उपयोग करते हुए :

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

इसके लिए आपको वास्तव में बहुत कुशल शाखाहीन minऔर maxकार्यान्वयन की आवश्यकता है , क्योंकि यह प्रभावी रूप से इस कोड को उबालता है - एक अनुक्रम minऔर maxसंचालन (प्रत्येक का कुल मिलाकर, 13)। मैं इसे पाठक के लिए एक अभ्यास के रूप में छोड़ता हूं।

ध्यान दें कि यह कार्यान्वयन स्वयं को आसानी से वैश्वीकरण के लिए उधार देता है (उदाहरण के लिए SIMD - अधिकांश SIMD ISAs में वेक्टर मिन / अधिकतम निर्देश हैं) और GPU कार्यान्वयन (जैसे CUDA - शाखा रहित होने के नाते ताना विचलन आदि के साथ कोई समस्या नहीं है)।

यह भी देखें: बहुत छोटी सूची को सॉर्ट करने के लिए फास्ट एल्गोरिदम कार्यान्वयन


1
: के लिए न्यूनतम / अधिकतम कुछ बिट हैक्स के लिए graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
Rubys

1
@Paul: वास्तविक CUDA उपयोग के संदर्भ में, यह निश्चित रूप से सबसे अच्छा जवाब है। मैं जाँच करूँगा कि क्या यह भी (और कितना) गोल्फ x64 संदर्भ में है और परिणाम प्रकाशित करें।
क्रिश

1
Sort3यदि आप (a+b+c)-(min+max)केंद्रीय संख्या है, तो तेजी से (अधिकांश आर्किटेक्चर पर, वैसे भी) ।
रेक्स केर

1
@Rex: मैं देख रहा हूँ - जो अच्छा लग रहा है। AltiVec और SSE जैसे SIMD आर्किटेक्चर के लिए यह निर्देश चक्रों की अधिकतम संख्या होगी (अधिकतम और मिनट एकल चक्र निर्देश हैं जैसे ऐड / घटाना), लेकिन एक सामान्य स्केलर सीपीयू के लिए आपका तरीका बेहतर दिखता है।
पॉल आर। Paul

2
यदि मैं GCC को सशर्त चाल निर्देशों के साथ मिनट का अनुकूलन करने देता हूं तो मुझे 33% स्पीडअप मिलता है #define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }:। यहाँ मैं उपयोग नहीं कर रहा हूँ ?: d [y] के लिए क्योंकि यह थोड़ा खराब प्रदर्शन देता है, लेकिन यह लगभग शोर में है।
पाओलो बोन्जिनी

45

चूंकि ये पूर्णांक हैं और तुलना तेजी से होती है, इसलिए प्रत्येक के रैंक क्रम की गणना सीधे क्यों न करें:

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 के साथ यह 1000 चक्र से नीचे है, छँटाई नेटवर्क की तुलना में काफी तेज़ लेकिन धीमी है। कोड में सुधार करने के लिए कोई विचार? शायद अगर हम सरणी कॉपी से बच सकते हैं ...
kriss

@kriss: यह मेरे लिए -O2 के साथ सॉर्टिंग नेटवर्क से तेज है। क्या कुछ कारण है -O2 ठीक नहीं है, या क्या यह आपके लिए धीमा है -O2 भी? शायद यह मशीन वास्तुकला में अंतर है?
रेक्स केर

1
@Rex: क्षमा करें, मुझे पहली नज़र में> बनाम> पैटर्न याद आ गया। यह हर मामले में काम करता है।
6:10 बजे kriss

3
@ क्रेस: ​​अहा। यह पूरी तरह से आश्चर्य की बात नहीं है - वहाँ बहुत सारे चर चल रहे हैं, और उन्हें सावधानी से आदेश दिया जाना चाहिए और रजिस्टरों में कैश्ड किया जाएगा इत्यादि।
रेक्स केर

2
@SSpoke 0+1+2+3+4+5=15चूंकि उनमें से एक लापता है, 15 मिनट बाकी की पैदावार का योग गायब है
ग्लेन टीइटेलबाम

35

लगता है कि मुझे पार्टी में आने में एक साल की देर हो गई, लेकिन यहां हम जाते हैं ...

४.५.२ से उत्पन्न विधानसभा को देखते हुए मैंने देखा कि हर स्वैप के लिए लोड और स्टोर किए जा रहे हैं, जिनकी वास्तव में आवश्यकता नहीं है। 6 मानों को रजिस्टरों में लोड करना, उन्हें क्रमबद्ध करना और उन्हें वापस मेमोरी में स्टोर करना बेहतर होगा। मैंने दुकानों पर लोड का आदेश दिया कि जितना संभव हो उतना करीब हो सकता है, जहां रजिस्टरों की पहली आवश्यकता होती है और अंतिम उपयोग किया जाता है। मैंने स्टीनर एच। गुंडरसन के SWAP मैक्रो का भी उपयोग किया। अद्यतन: मैंने पाओलो बोन्जिनी के स्वैप मैक्रो पर स्विच किया जो gcc, Gunderson's के समान कुछ में परिवर्तित होता है, लेकिन gcc निर्देशों को बेहतर ढंग से व्यवस्थित करने में सक्षम है क्योंकि उन्हें स्पष्ट असेंबली के रूप में नहीं दिया गया है।

मैंने उसी स्वैग ऑर्डर का इस्तेमाल किया, जैसा कि सबसे अच्छा प्रदर्शन के रूप में दिया गया रिडैप्ड स्वैप नेटवर्क है, हालांकि एक बेहतर ऑर्डर हो सकता है। अगर मुझे कुछ और समय मिलता है तो मैं क्रमपरिवर्तन का एक समूह उत्पन्न करूँगा और परीक्षण करूँगा।

मैंने 4000 से अधिक सरणियों पर विचार करने और प्रत्येक को सॉर्ट करने के लिए आवश्यक चक्रों की औसत संख्या दिखाने के लिए परीक्षण कोड को बदल दिया। एक i5-650 पर मैं ~ 34.3 चक्र / सॉर्ट (-O3) प्राप्त कर रहा हूं, मूल पुन: सॉर्ट किए गए नेटवर्क की तुलना में ~ 65.3 चक्र / सॉर्ट (-O1, बीट्स -ओ 2 और -ओ 3) प्राप्त कर रहा है।

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

मैंने परीक्षण सूट को संशोधित करके घड़ियों को प्रति सॉर्ट रिपोर्ट करने और अधिक परीक्षण चलाने के लिए भी बदल दिया (cmp फ़ंक्शन को पूर्णांक ओवरफ़्लो के साथ-साथ हैंडल करने के लिए अद्यतन किया गया था), यहां कुछ अलग आर्किटेक्चर पर परिणाम हैं। मैंने AMD cpu पर परीक्षण का प्रयास किया, लेकिन rdtsc मेरे पास उपलब्ध X6 1100T पर विश्वसनीय नहीं है।

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

रजिस्टर चर का आपका विचार रेक्स केर के "रैंक ऑर्डर" समाधान पर लागू होना चाहिए। यह सबसे तेज़ होना चाहिए, और शायद तब -O3अनुकूलन काउंटर-उत्पादक नहीं होगा।
cdunn2001

1
@ cdunn2001 मैंने अभी इसका परीक्षण किया है, मैं सुधार नहीं देख रहा हूं (-ओ0 और -ओएस पर कुछ चक्रों को छोड़कर)। एएसएम को देखते हुए यह प्रतीत होता है कि रजिस्टरों का उपयोग करने और कॉल को समाप्त करने के लिए पहले ही पता लगाने में कामयाब हो गया है।
केविन स्टॉक

क्या आप सूट के परीक्षण के लिए सरल स्वैप संस्करण को जोड़ने का मन बना लेंगे, मुझे लगता है कि हाथ से अनुकूलित विधानसभा तेज स्वैप के साथ इसकी तुलना करना दिलचस्प हो सकता है।
kriss

1
आपका कोड अभी भी गुंडरसन की स्वैप का उपयोग करता है, मेरा होगा #define SWAP(x,y) { int oldx = x; x = x < y ? x : y; y ^= oldx ^ x; }
पाओलो बोन्जिनी

@ पाओलो बोन्जिनी: हां, मैं आपके साथ एक टेस्ट केस जोड़ना चाहती हूं, अभी समय नहीं था। लेकिन मैं इनलाइन असेंबली से बचूंगा।
kriss

15

मैं कुछ दिनों पहले Google के इस सवाल पर अड़ गया क्योंकि मुझे भी 6 पूर्णांकों की एक निश्चित लंबाई सरणी को जल्दी से हल करने की आवश्यकता थी। मेरे मामले में, हालांकि, मेरे पूर्णांक केवल 8 बिट (32 के बजाय) हैं और मुझे केवल सी का उपयोग करने की सख्त आवश्यकता नहीं है। मुझे लगा कि मैं अपने निष्कर्षों को वैसे भी साझा करूंगा, यदि वे किसी के लिए सहायक हो सकते हैं ...

मैंने असेंबली में एक नेटवर्क सॉर्ट का एक संस्करण लागू किया, जो एसएसई का उपयोग तुलना और स्वैप संचालन को संभव करने के लिए, हद तक करने के लिए करता है। यह सरणी को पूरी तरह से सॉर्ट करने के लिए छह "पास" लेता है। मैंने केवल PADDB (वेक्टरड ऐड) का उपयोग करते हुए PSHUFB (वेक्टरड स्वैप) के लिए मापदंडों को फेरबदल करने के लिए PCMPGTB (वेक्टर की तुलना) के परिणामों को सीधे रूप से परिवर्तित करने के लिए एक उपन्यास तंत्र का उपयोग किया और कुछ मामलों में एक पांडा (बिटवाइड और) निर्देश भी।

इस दृष्टिकोण का भी उपज के साइड इफेक्ट था वास्तव में शाखा रहित कार्य । जो भी निर्देश हैं, वे नहीं हैं।

ऐसा प्रतीत होता है कि यह कार्यान्वयन की तुलना में लगभग 38% तेज है जो वर्तमान में प्रश्न में सबसे तेज विकल्प के रूप में चिह्नित है ("सरल स्वैप के साथ नेटवर्क सॉर्ट करना")। charतुलनात्मक निष्पक्ष बनाने के लिए मैंने अपने परीक्षण के दौरान सरणी तत्वों का उपयोग करने के लिए उस कार्यान्वयन को संशोधित किया ।

मुझे ध्यान देना चाहिए कि इस दृष्टिकोण को 16 तत्वों तक किसी भी सरणी आकार पर लागू किया जा सकता है। मैं बड़ी सरणियों के लिए बड़ा होने के लिए विकल्पों पर सापेक्ष गति लाभ की अपेक्षा करता हूं।

कोड S86E3 के साथ x86_64 प्रोसेसर के लिए MASM में लिखा गया है। फ़ंक्शन "नया" विंडोज x64 कॉलिंग कन्वेंशन का उपयोग करता है। यह रहा...

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

आप इसे एक निष्पादन योग्य वस्तु पर संकलित कर सकते हैं और इसे अपनी सी परियोजना में जोड़ सकते हैं। विज़ुअल स्टूडियो में यह कैसे किया जाए, इस निर्देश के लिए, आप इस लेख को पढ़ सकते हैं । आप अपने C कोड से फ़ंक्शन को कॉल करने के लिए निम्न C प्रोटोटाइप का उपयोग कर सकते हैं:

void simd_sort_6(char *values);

यह आपकी विधानसभा के अन्य प्रस्तावों के साथ तुलना करने के लिए इंटररस्टिंग होगा। कार्यान्वयन के तुलनात्मक प्रदर्शनों में उन्हें शामिल नहीं किया गया है। SSE का उपयोग करना वैसे भी अच्छा लगता है।
kriss

भविष्य के अनुसंधान का एक अन्य क्षेत्र इस समस्या के लिए नए Intel AVX निर्देशों का अनुप्रयोग होगा। बड़े 256-बिट वैक्टर 8 DWORD फिट करने के लिए पर्याप्त बड़े हैं।
जो क्रिवलो

1
इसके बजाय pxor / pinsrd xmm4, mem, 0, बस उपयोग करें movd!
पीटर कॉर्ड्स

14

परीक्षण कोड बहुत बुरा है; यह प्रारंभिक सरणी को ओवरफ्लो करता है (यहां के लोग कंपाइलर चेतावनियों को नहीं पढ़ते हैं?), प्रिंटफ़ गलत प्रिंट कर रहा है, इसका उपयोग करता है। यह आरडीटीएस के लिए बिना किसी अच्छे कारण के लिए उपयोग करता है, केवल एक रन (!) है, इसमें कोई जाँच नहीं है! अंतिम परिणाम वास्तव में सही होते हैं (इसलिए यह बहुत आसानी से "गलत तरीके से" अनुकूलित करना) है, इसमें शामिल परीक्षण बहुत ही अल्पविकसित (कोई नकारात्मक संख्याएं नहीं हैं?) और कंपाइलर को पूरे फ़ंक्शन को मृत कोड के रूप में त्यागने से रोकने के लिए कुछ भी नहीं है।

यह कहा जा रहा है, बिटोनिक नेटवर्क समाधान में सुधार करना बहुत आसान है; बस मिनट / अधिकतम / स्वैप सामान को बदल दें

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

और यह मेरे लिए लगभग 65% तेजी से निकलता है (डेबियन जीसीसी 4.4.5 के साथ -O2, amd64, कोर i7)।


ठीक है, टेस्ट कोड खराब है। इसे सुधारने के लिए स्वतंत्र महसूस करें। और हाँ, आप असेंबली कोड का उपयोग कर सकते हैं। क्यों नहीं सभी तरह से जा रहे हैं और पूरी तरह से यह कोड x86 कोडांतरक का उपयोग कर? यह थोड़ा कम पोर्टेबल हो सकता है लेकिन परेशान क्यों?
kriss

सरणी अतिप्रवाह को ध्यान देने के लिए धन्यवाद, मैंने इसे सही किया। अन्य लोगों ने इस पर ध्यान नहीं दिया होगा क्योंकि कॉपी / पेस्ट कोड के लिंक पर क्लिक किया गया है, जहां कोई अतिप्रवाह नहीं है।
kriss

4
तुम भी वास्तव में कोडांतरक की जरूरत नहीं है; यदि आप सभी चतुर चालें छोड़ देते हैं, तो जीसीसी अनुक्रम को पहचान लेगा और आपके लिए सशर्त चालें डाल देगा: #define min (a, b) ((a <b)? a: b) #define max (a, b)? (a <b); b: a) # अल्पाइन SWAP (x, y) {int a = min (d [x], d [y]); int b = max (d [x], d [y]); d [x] = a; d [y] = b; } यह इनलाइन asm वेरिएंट की तुलना में कुछ प्रतिशत धीमा हो सकता है, लेकिन यह कहना मुश्किल है कि उचित बेंचमार्किंग की कमी है।
स्टाइनर एच। गुंडरसन

3
… और अंत में, यदि आपके नंबर फ्लोट हैं, और आपको NaN आदि के बारे में चिंता करने की ज़रूरत नहीं है, तो GCC इसे minss / maxss SSE निर्देशों में परिवर्तित कर सकता है, जो अभी ~ 25% तेज है। मनोबल: चतुर बिटफिडलिंग चालें छोड़ें और संकलक को अपना काम करने दें। :-)
स्टीनर एच। गुंडरसन

13

जबकि मुझे वास्तव में स्वैप मैक्रो प्रदान करना पसंद है:

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

मुझे एक सुधार दिखाई दे रहा है (जो एक अच्छा संकलक बना सकता है):

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

हम इस बात पर ध्यान देते हैं कि न्यूनतम और अधिकतम कैसे काम करते हैं और सामान्य उप-अभिव्यक्ति को स्पष्ट रूप से खींचते हैं। यह न्यूनतम और अधिकतम मैक्रोज़ को पूरी तरह से समाप्त कर देता है।


जो उन्हें पीछे की ओर ले जाता है, ध्यान दें कि d [y] को अधिकतम मिलता है, जो x ^ (सामान्य सबप्रेसेशन) है।
केविन स्टॉक

मैंने भी ठीक यही चीज गौर की; मुझे लगता है कि आपके कार्यान्वयन के लिए आपको सही होना चाहिए ( d[x]इसके xलिए समान y), और d[y] < d[x]यहां असमानता के लिए (हां, न्यूनतम / अधिकतम कोड से अलग)।
टायलर

मैंने आपके स्वैप के साथ प्रयास किया, लेकिन स्थानीय अनुकूलन का बड़े स्तर पर नकारात्मक प्रभाव है (मुझे लगता है कि यह निर्भरता का परिचय देता है)। और परिणाम अन्य स्वैप की तुलना में धीमा है। लेकिन जैसा कि आप प्रस्तावित नए समाधान के साथ देख सकते हैं कि स्वैप का अनुकूलन करने के लिए वास्तव में बहुत प्रदर्शन था।
kriss

12

बेंचमार्किंग और वास्तविक संकलक उत्पन्न विधानसभा को देखे बिना कभी भी न्यूनतम / अधिकतम का अनुकूलन न करें। अगर मैं GCC को सशर्त चाल निर्देशों के साथ मिनट का अनुकूलन करने देता हूं तो मुझे 33% स्पीडअप मिलता है:

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

(टेस्ट कोड में 280 बनाम 420 चक्र)। के साथ अधिकतम कर रहे हैं ?: कम या ज्यादा समान है, लगभग शोर में खो गया है, लेकिन ऊपर थोड़ा तेज है। यह SWAP जीसीसी और क्लैंग दोनों के साथ तेज है।

कंपाइलर रजिस्टर आवंटन और उर्फ ​​विश्लेषण पर एक असाधारण काम कर रहे हैं, प्रभावी रूप से d [x] को स्थानीय चर में आगे बढ़ा रहे हैं, और अंत में केवल स्मृति में कॉपी कर रहे हैं। वास्तव में, वे इससे भी बेहतर करते हैं यदि आपने पूरी तरह से स्थानीय चर (जैसे d0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3], d4 = d[4], d5 = d[5]) के साथ काम किया हो । मैं यह इसलिए लिख रहा हूं क्योंकि आप मजबूत अनुकूलन मान रहे हैं और फिर भी न्यूनतम / अधिकतम पर कंपाइलर को बाहर करने की कोशिश कर रहे हैं। :)

वैसे, मैंने क्लैंग और जीसीसी की कोशिश की। वे समान अनुकूलन करते हैं, लेकिन शेड्यूलिंग अंतरों के कारण दोनों के परिणामों में कुछ भिन्नता है, वास्तव में यह नहीं कह सकते हैं कि कौन सा तेज या धीमा है। जीसीसी छँटाई नेटवर्क पर तेज है, द्विघात प्रकार पर क्लैंग।

बस पूर्णता के लिए, अनियंत्रित बुलबुला प्रकार और सम्मिलन प्रकार भी संभव हैं। यहाँ बुलबुला प्रकार है:

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

और यहाँ प्रविष्टि प्रकार है:

//#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);

यह प्रविष्टि सॉर्ट डैनियल स्टुट्ज़बेक की तुलना में तेज़ है, और विशेष रूप से GPU या कंप्यूटर पर पूर्वानुमान के साथ अच्छा है क्योंकि ITER को केवल 3 निर्देशों (SWAP के लिए 4) के साथ किया जा सकता है। उदाहरण के लिए, यहाँ t = d[2]; ITER(1); ITER(0);ARM असेंबली में लाइन है:

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

छह तत्वों के लिए प्रविष्टि सॉर्टिंग नेटवर्क के साथ प्रतिस्पर्धात्मक है (12 स्वैप बनाम 15 पुनरावृत्तियों 4 निर्देशों / स्वैप बनाम 3 निर्देशों / पुनरावृत्ति) को संतुलित करता है; बुलबुला प्रकार की धीमी है। लेकिन यह तब सही नहीं होगा जब आकार बढ़ता है, क्योंकि सम्मिलन सॉर्ट O (n ^ 2) है जबकि सॉर्टिंग नेटवर्क O (n log n) हैं।


1
अधिक या कम संबंधित: मैंने जीसीसी को एक रिपोर्ट सौंपी ताकि वह सीधे कंपाइलर में अनुकूलन को लागू कर सके। यह सुनिश्चित नहीं है कि यह किया जाएगा, लेकिन कम से कम आप इसका अनुसरण कर सकते हैं कि यह कैसे विकसित होता है।
मॉरवे

11

मैंने परीक्षण सूट को एक पीपीसी आर्किटेक्चर मशीन में पोर्ट किया है जिसे मैं पहचान नहीं सकता (कोड को छूने की ज़रूरत नहीं है, बस परीक्षण की पुनरावृत्तियों को बढ़ाएं, मॉड्स के साथ प्रदूषणकारी परिणामों से बचने और x86 विशिष्ट rdtsc को बदलने के लिए 8 परीक्षण मामलों का उपयोग करें):

Qsort लाइब्रेरी फ़ंक्शन के लिए सीधे कॉल : 101

Naive कार्यान्वयन (प्रविष्टि प्रकार) : 299

निवेशन सॉर्ट (डैनियल स्टुट्ज़बेक) : 108

सम्मिलन क्रमबद्ध अनियंत्रित : 51

सॉर्टिंग नेटवर्क (डैनियल स्टुट्ज़बाक) : 26

सॉर्टिंग नेटवर्क (पॉल आर) : 85

फास्ट स्वैप के साथ छंटनी नेटवर्क 12 : 117

सॉर्टिंग नेटवर्क 12 ने स्वैप्ट को पुन : व्यवस्थित किया : 116

रैंक क्रम : 56


1
बहुत दिलचस्प। ऐसा लगता है कि पीपीसी पर शाखाविहीन स्वैप एक बुरा विचार है। यह एक कंपाइलर संबंधित प्रभाव भी हो सकता है। कौन सा इस्तेमाल किया गया था?
kriss

इसकी gcc संकलक की एक शाखा है - न्यूनतम, अधिकतम तर्क शायद शाखाहीन नहीं है - मैं disassembly का निरीक्षण करूंगा और आपको बता दूंगा, लेकिन जब तक संकलक x <y जैसी किसी चीज के साथ पर्याप्त चालाक नहीं है, अगर बिना एक शाखा के भी - अभी भी x86 पर है / x64 CMOV निर्देश इससे बच सकता है, लेकिन PPC पर निश्चित बिंदु मानों के लिए ऐसा कोई निर्देश नहीं है, केवल तैरता है। मैं इस कल के साथ छेड़छाड़ कर सकता हूं और आपको बता दूं - मुझे याद है कि विंम्प एवीएस स्रोत में एक बहुत सरल शाखा रहित मिनट / अधिकतम था, लेकिन iirc यह केवल तैरने के लिए था - लेकिन वास्तव में शाखाहीन दृष्टिकोण की दिशा में एक अच्छी शुरुआत हो सकती है।
जेरिको

4
यहाँ एक शाखा मिनट / पीपीसी के लिए अधिकतम अहस्ताक्षरित जानकारी के साथ है: subfc r5,r4,r3; subfe r6,r6,r6; andc r6,r5,r6; add r4,r6,r4; subf r3,r6,r3। r3 / r4 इनपुट हैं, r5 / r6 स्क्रैच रजिस्टर हैं, आउटपुट पर r3 को न्यूनतम और r4 को अधिकतम मिलता है। यह शालीनता से हाथ से तय किया जाना चाहिए। मैंने इसे जीएनयू सुपरोप्टाइज़र के साथ पाया, जो 4-निर्देशों के मिनट और अधिकतम अनुक्रमों से शुरू होता है और दो के लिए मैन्युअल रूप से देखता है जिसे संयुक्त किया जा सकता है। हस्ताक्षरित आदानों के लिए, आप शुरुआत में सभी तत्वों में 0x80000000 जोड़ सकते हैं और अंत में इसे फिर से घटा सकते हैं, और फिर काम कर सकते हैं जैसे कि वे अहस्ताक्षरित थे।
पाओलो बोन्जिनी

7

एक XOR स्वैप आपके स्वैपिंग कार्यों में उपयोगी हो सकता है।

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

यदि आपके कोड में बहुत अधिक विचलन हो सकता है, लेकिन यदि आपके पास गारंटी है कि आपके सभी ints अद्वितीय हैं तो यह आसान हो सकता है।


1
xor स्वैप समान मान के लिए भी काम करता है ... x ^ = y सेट x से 0, y ^ = x पत्ते y के रूप में y (== x), x ^ = y सेट x से y
jheriko

11
जब यह काम नहीं करता है तो उसी स्थान पर कब xऔर yकिस बिंदु पर है।
हॉब्स

वैसे भी जब छँटाई नेटवर्क के साथ प्रयोग किया जाता है तो हम कभी भी x और y दोनों को एक ही स्थान पर इंगित नहीं करते हैं। अभी भी परीक्षण करने से बचने का एक तरीका खोजना है जो शाखा रहित स्वैप के समान प्रभाव प्राप्त करने के लिए अधिक से अधिक हो। मुझे यह हासिल करने का विचार है।
15

5

इस पर मेरे हाथ की कोशिश करने और इन उदाहरणों से सीखने की उम्मीद है, लेकिन मेरी 1.5 गीगाहर्ट्ज़ पीपीसी पॉवरबुक जी 4 डब्ल्यू / 1 जीबी डीडीआर रैम से कुछ समय पहले। (मैंने http://www.mcs.anl.gov/~kazutomo/rdtsc.html से PPC के लिए एक समान rdtsc जैसा टाइमर उधार लिया टाइमिंग के लिए ।) मैंने कार्यक्रम को कुछ बार चलाया और निरपेक्ष परिणाम विविध थे, लेकिन लगातार। सबसे तेज़ परीक्षा "इंसर्शन सॉर्ट (डैनियल स्टुट्ज़बाक)" थी, जिसमें "इंसर्शन सॉर्ट अनियंत्रित" एक करीबी सेकंड था।

यहाँ समय का अंतिम सेट है:

**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

इस सूत्र में मेरा योगदान है: एक 6-सदस्य इंट वेक्टर (वैलप) के लिए एक अनुकूलित 1, 4 गैप शेल्सोर्ट, जिसमें अद्वितीय मान हैं।

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

डुअल-कोर Athlon M300 @ 2 Ghz (DDR2 मेमोरी) के साथ मेरे HP DV7-3010so लैपटॉप पर यह 165 क्लॉक साइकिल में निष्पादित होता है। यह हर अद्वितीय अनुक्रम (सभी में 6! / 720) के समय से गणना की गई एक औसत है। Win32 को OpenWatcom 1.8 का उपयोग करके संकलित किया गया। लूप अनिवार्य रूप से एक सम्मिलन प्रकार है और 16 निर्देश / 37 बाइट्स लंबा है।

मेरे पास संकलन करने के लिए 64-बिट वातावरण नहीं है।


अच्छा। मैं इसे अब तक के परीक्षण के लिए जोड़ दूंगा
kriss

3

यदि सम्मिलन क्रम यहां काफी प्रतिस्पर्धी है, तो मैं एक गोले की कोशिश करने की सलाह दूंगा। मुझे डर है कि 6 तत्व शायद सबसे अच्छा होने के लिए बहुत कम हैं, लेकिन यह एक कोशिश के लायक हो सकता है।

उदाहरण कोड, अप्रमाणित, पूर्वनिर्धारित आदि। आप इष्टतम खोजने के लिए inc = 4 और inc - = 3 अनुक्रम को ट्यून करना चाहते हैं (उदाहरण के लिए inc = 2, inc - = 1 का प्रयास करें)।

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

मुझे नहीं लगता कि यह जीत पाएगी, लेकिन अगर कोई 10 तत्वों को छांटने के बारे में कोई सवाल करे, तो कौन जानता है ...

विकिपीडिया के अनुसार इसे छँटाई नेटवर्क के साथ भी जोड़ा जा सकता है: प्रैट, वी (1979)। शेलसॉर्ट और सॉर्टिंग नेटवर्क (कंप्यूटर विज्ञान में बकाया शोध प्रबंध)। फूलों का हार। आईएसबीएन 0-824-04406-1


कुछ कार्यान्वयन का प्रस्ताव करने के लिए स्वतंत्र महसूस करें :-)
kriss

प्रस्ताव जोड़ा गया। कीड़े का आनंद लें।
19

3

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

मैंने रैंक ऑर्डर सॉर्ट का एक लूपिंग संस्करण जोड़ा, क्योंकि इस अध्ययन का मेरा अपना आवेदन 2-8 आइटमों को छांटने के लिए है, इसलिए चूंकि एक चर संख्या के तर्क हैं, एक लूप आवश्यक है। यही कारण है कि मैंने सॉर्टिंग नेटवर्क समाधानों को नजरअंदाज कर दिया।

परीक्षण कोड ने परीक्षण नहीं किया कि डुप्लिकेट को सही तरीके से संभाला गया था, इसलिए जब मौजूदा समाधान सभी सही थे, तो मैंने यह सुनिश्चित करने के लिए परीक्षण कोड में एक विशेष मामला जोड़ा कि डुप्लिकेट को सही तरीके से संभाला गया था।

फिर, मैंने एक प्रविष्टि प्रकार लिखा जो पूरी तरह से एवीएक्स रजिस्टरों में है। मेरी मशीन पर यह अन्य प्रविष्टि प्रकारों की तुलना में 25% तेज है, लेकिन रैंक क्रम से 100% धीमी है। मैंने इसे शुद्ध रूप से प्रयोग के लिए किया था और सम्मिलन के क्रम में शाखा के कारण इसके बेहतर होने की कोई उम्मीद नहीं थी।

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
}

फिर, मैंने AVX का उपयोग करके रैंक ऑर्डर सॉर्ट लिखा। यह अन्य रैंक-ऑर्डर समाधानों की गति से मेल खाता है, लेकिन तेज नहीं है। यहां मुद्दा यह है कि मैं केवल एवीएक्स के साथ सूचकांकों की गणना कर सकता हूं, और फिर मुझे सूचकांकों की एक तालिका बनानी होगी। ऐसा इसलिए है क्योंकि गणना स्रोत-आधारित होने के बजाय गंतव्य-आधारित है। स्रोत-आधारित सूचकांकों से गंतव्य-आधारित सूचकांकों में परिवर्तित होते देखें

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

रेपो यहां पाया जा सकता है: https://github.com/eyepatchParrot/sort6/


1
आप vmovmskpsबिट्स ( ffs) परिणाम को राइट-शिफ्ट करने की आवश्यकता को टालते हुए पूर्णांक वैक्टर (आंतरिक कलाकारों को खुश रखने के लिए कलाकारों के साथ) पर उपयोग कर सकते हैं ।
पीटर कॉर्डेस

1
आप सशर्त रूप से इसे जोड़ने के बजाय, इसे घटाकरcmpgt परिणाम के आधार पर 1 जोड़ सकते हैं । जैसे करता हैset1(1)index = _mm256_sub_epi32(index, gt)index -= -1 or 0;
पीटर कॉर्डेस

1
eq = _mm256_insert_epi32(eq, 0, I)किसी तत्व को शून्य करने का एक प्रभावी तरीका नहीं है यदि वह लिखित रूप में संकलित करता है (विशेष रूप से निम्न 4 से बाहर के तत्वों के लिए, क्योंकि vpinsrdकेवल एक एक्सएमएम गंतव्य के साथ उपलब्ध है, 3 से अधिक सूचकांकों का अनुकरण किया जाना है)। इसके बजाय, _mm256_blend_epi32( vpblendd) एक शून्य वेक्टर के साथ। vpblenddएक एकल-यूओपी निर्देश है जो किसी भी पोर्ट पर चलता है, बनाम एक फेरबदल जिसमें इंटेल सीपीयू पर पोर्ट 5 की आवश्यकता होती है। ( agner.org/optimize )।
पीटर कॉर्डेस

1
इसके अलावा, आप rotएक ही स्रोत से अलग-अलग फेरबदल के साथ वैक्टर उत्पन्न करने पर विचार कर सकते हैं , या कम से कम 2 dep श्रृंखलाओं को समानांतर में चला सकते हैं जो कि आप एक लेन-क्रॉसिंग फेरबदल (3 चक्र विलंबता) के माध्यम से एक एकल dep श्रृंखला के बजाय वैकल्पिक रूप से उपयोग करते हैं। यह ILP को एक ही प्रकार में बढ़ाएगा। 2 डीईएस श्रृंखलाएं वेक्टर स्थिरांक की संख्या को एक उचित संख्या तक सीमित करती हैं, केवल 2: 1 एक रोटेट के लिए, और एक 2 रोटेटिंग स्टेप्स के लिए।
पीटर कॉर्डेस

2

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

एल्गोरिथ्म एल्गोरिथ्म sort6का उपयोग करता है जो एल्गोरिथ्म sort4का उपयोग करता हैsort3 । यहां कुछ प्रकाश सी ++ फॉर्म में कार्यान्वयन है (मूल टेम्पलेट-भारी है ताकि यह किसी भी यादृच्छिक-पहुंच इट्रेटर और किसी भी उपयुक्त तुलना फ़ंक्शन के साथ काम कर सके)।

3 मानों को छाँटना

निम्नलिखित एल्गोरिथ्म एक अनियंत्रित सम्मिलन प्रकार है। जब दो स्वैप (6 असाइनमेंट) करने होते हैं, तो इसके बजाय 4 असाइनमेंट का उपयोग करता है:

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

यह थोड़ा जटिल दिखता है क्योंकि सरणी में 2 ~ 3 तुलनाओं और तीन मानों को क्रमबद्ध करने के लिए अधिकतम 4 असाइनमेंट का उपयोग करके सरणी के हर संभव क्रमचय के लिए अधिक या कम एक शाखा है।

4 मानों को छाँटना

यह एक कॉल sort3तब सरणी के अंतिम तत्व के साथ एक अनियंत्रित प्रविष्टि प्रकार करता है:

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

यह एल्गोरिदम 3 से 6 तुलना करता है और अधिकतम 5 स्वैप करता है। एक प्रविष्टि प्रकार को अनियंत्रित करना आसान है, लेकिन हम अंतिम क्रम के लिए एक अन्य एल्गोरिथ्म का उपयोग करेंगे ...

6 मानों को छाँटना

यह एक अनियंत्रित संस्करण का उपयोग करता है जिसे मैंने एक डबल प्रविष्टि प्रकार कहा था । नाम इतना महान नहीं है, लेकिन यह काफी वर्णनात्मक है, यहां बताया गया है कि यह कैसे काम करता है:

  • सरणी के पहले और अंतिम तत्वों पर सब कुछ क्रमबद्ध करें।
  • यदि पहले से अंतिम से अधिक है, तो पहले और सरणी के तत्वों को स्वैप करें।
  • पहले तत्व को आगे से क्रमबद्ध अनुक्रम में डालें फिर पीछे से अंतिम तत्व।

स्वैप के बाद, पहला तत्व हमेशा अंतिम से छोटा होता है, जिसका अर्थ है कि, जब उन्हें क्रमबद्ध अनुक्रम में सम्मिलित किया जाता है, तो सबसे खराब स्थिति में दो तत्वों को सम्मिलित करने के लिए एन तुलना से अधिक नहीं होगा: उदाहरण के लिए, यदि पहले तत्व को तीसरे स्थान पर डाला गया है, फिर अंतिम को 4 वें स्थान से कम नहीं डाला जा सकता है।

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 मानों के हर क्रमपरिवर्तन पर मेरे परीक्षण कभी भी बताते हैं कि यह एल्गोरिदम हमेशा 6 और 13 तुलनाओं के बीच प्रदर्शन करता है। मैंने प्रदर्शन किए गए स्वैप की संख्या की गणना नहीं की है, लेकिन मुझे उम्मीद है कि यह सबसे खराब स्थिति में 11 से अधिक नहीं होगा।

मुझे उम्मीद है कि यह मदद करता है, भले ही यह सवाल एक वास्तविक समस्या का प्रतिनिधित्व नहीं कर सकता है :)

EDIT: इसे दिए गए बेंचमार्क में डालने के बाद, यह दिलचस्प विकल्पों में से अधिकांश की तुलना में धीमा है। यह अनियंत्रित प्रविष्टि प्रकार की तुलना में थोड़ा बेहतर प्रदर्शन करता है, लेकिन यह बहुत ज्यादा है। मूल रूप से, यह पूर्णांकों के लिए सबसे अच्छा प्रकार नहीं है, लेकिन एक महंगी तुलना ऑपरेशन के साथ प्रकारों के लिए दिलचस्प हो सकता है।


ये अच्छे हैं। जैसा कि हल की गई समस्या कई दशकों पुरानी है, शायद उतनी ही पुरानी सी प्रोग्रामिंग है, कि अब लगभग 5 साल का सवाल उतना प्रासंगिक नहीं है।
क्रिश

आप जिस तरह से अन्य जवाब समयबद्ध हैं पर एक नज़र रखना चाहिए। मुद्दा यह है कि इस तरह के छोटे डेटासेट की तुलना या यहां तक ​​कि तुलना और स्वैप भी वास्तव में यह नहीं कहते हैं कि एल्गोरिथ्म कितनी तेजी से है (मूल रूप से 6 इनट्स को छांटना हमेशा ओ (1) है क्योंकि ओ) (6 * 6) ओ (1) है। पहले से प्रस्तावित समाधानों में से सबसे तेज़ वर्तमान तुरंत एक बड़ी तुलना (रेक्सकेर द्वारा) का उपयोग करके प्रत्येक मूल्य की स्थिति का पता लगा रहा है।
क्रिश

@kriss क्या यह अब सबसे तेज़ है? परिणामों के मेरे पढ़ने से, सॉर्टिंग नेटवर्क दृष्टिकोण सबसे तेज़ था, मेरा बुरा। यह भी सच है कि मेरा समाधान मेरे जेनेरिक लाइब्रेरी से आता है और मैं हमेशा पूर्णांक की तुलना नहीं कर रहा हूं, न ही हमेशा operator<तुलना के लिए उपयोग कर रहा हूं । तुलना और स्वैप के वस्तुनिष्ठ गणना के अलावा, मैंने अपने एल्गोरिदम को भी ठीक से समय दिया; यह समाधान सबसे तेज़ सामान्य था, लेकिन मैं वास्तव में @ रेक्सकेर एक से चूक गया। चला गया यह प्रयास करें :)
मोरवेन

RexKerr (ऑर्डर रैंक) द्वारा समाधान gcc कंपाइलर 4.2.3 के बाद से X86 आर्किटेक्चर पर सबसे तेज हो गया (और gcc 4.9 के रूप में दूसरे सर्वश्रेष्ठ की तुलना में लगभग दो गुना तेज हो गया)। लेकिन यह कंपाइलर ऑप्टिमाइज़ेशन पर बहुत अधिक निर्भर है और अन्य आर्किटेक्चर पर सच नहीं हो सकता है।
क्रिश

@kriss यह जानना दिलचस्प है। और मैं वास्तव में फिर से अधिक अंतर कर सकता था -O3। मुझे लगता है कि मैं अपनी छँटाई लाइब्रेरी के लिए फिर एक और रणनीति अपनाऊंगा: तीन तरह के एल्गोरिदम प्रदान करना, जिसमें या तो तुलना की कम संख्या, स्वैप की कम संख्या या संभावित रूप से सर्वश्रेष्ठ प्रदर्शन हो। कम से कम, जो होता है वह पाठक के लिए पारदर्शी होगा। आपकी अंतर्दृष्टि के लिए धन्यवाद :)
मोरवेन बारह

1

मेरा मानना ​​है कि आपके प्रश्न के दो भाग हैं।

  • सबसे पहले इष्टतम एल्गोरिदम का निर्धारण करना है। यह किया जाता है - कम से कम इस मामले में - हर संभव आदेश के माध्यम से पाशन करके (वहां बहुत सारे नहीं हैं) जो आपको तुलना और स्वैप के सटीक न्यूनतम, अधिकतम, औसत और मानक विचलन की गणना करने की अनुमति देता है। साथ ही एक रनर-अप या दो काम करें।
  • दूसरा एल्गोरिथ्म का अनुकूलन करना है। पाठ्यपुस्तक कोड के उदाहरणों को बदलने और वास्तविक जीवन के एल्गोरिदम को दुबला करने के लिए बहुत कुछ किया जा सकता है। यदि आप महसूस करते हैं कि एक एल्गोरिथ्म को आवश्यक सीमा तक अनुकूलित नहीं किया जा सकता है, तो रनर-अप का प्रयास करें।

मैं पाइपलाइनों को खाली करने के बारे में बहुत ज्यादा चिंता नहीं करूंगा (वर्तमान x86 मानकर): शाखा की भविष्यवाणी एक लंबा रास्ता तय कर चुकी है। मुझे इस बात की चिंता होगी कि कोड और डेटा एक-एक कैश लाइन में फिट होंगे (शायद कोड के लिए दो)। एक बार वहाँ लाने के लिए विलंबता तुरंत कम होती है जो किसी भी स्टाल के लिए क्षतिपूर्ति करेगी। इसका मतलब यह भी है कि आपका आंतरिक लूप शायद दस निर्देश हो सकता है या जो सही है, जहां यह होना चाहिए (मेरी छँटाई एल्गोरिथ्म में दो अलग-अलग आंतरिक लूप हैं, वे क्रमशः 10 निर्देश / 22 बाइट्स और 9/22 लंबे हैं)। यह मानते हुए कि कोड में कोई divs नहीं है आप यह सुनिश्चित कर सकते हैं कि यह तेजी से अंधाधुंध होगा।


मुझे यकीन नहीं है कि आपके उत्तर को कैसे समझा जाए। पहले मुझे बिल्कुल समझ नहीं आया कि आप किस एल्गोरिदम का प्रस्ताव कर रहे हैं? और यह कैसे इष्टतम हो सकता है यदि आपको 720 संभावित ऑर्डर के माध्यम से लूप करना है (मौजूदा उत्तर 720 चक्र से बहुत कम लगते हैं)। यदि आपके पास यादृच्छिक इनपुट है तो मैं कल्पना भी नहीं कर सकता (सैद्धांतिक स्तर पर भी) कि शाखा भविष्यवाणी 50-50 से बेहतर कैसे प्रदर्शन कर सकती है, सिवाय इसके कि वह इनपुट डेटा पर ध्यान न दे। साथ ही पहले से प्रस्तावित अधिकांश अच्छे समाधान पहले ही पूरी तरह से कैश में डेटा और कोड दोनों के साथ काम करने की संभावना रखते हैं। लेकिन शायद मैंने आपके जवाब को पूरी तरह गलत समझा। कुछ कोड दिखाने का मन?
क्राइस

मेरा मतलब था कि केवल ६२० (६!) में ६ पूर्णांकों के अलग-अलग संयोजन हैं और उन सभी को उम्मीदवार एल्गोरिदम के माध्यम से चलाकर आप बहुत सारी चीजें निर्धारित कर सकते हैं जैसा कि मैंने उल्लेख किया है - यह सैद्धांतिक हिस्सा है। व्यावहारिक भाग ठीक ट्यूनिंग है कि एल्गोरिथ्म संभव के रूप में कुछ घड़ी चक्र में चलाने के लिए। 6 पूर्णांकों को छांटने के लिए मेरा प्रारंभिक बिंदु एक 1, 4 गैप शेल्टर है। 4 अंतर 1 अंतराल में अच्छी शाखा भविष्यवाणी के लिए मार्ग प्रशस्त करता है।
ओलोफ फोर्सशेल

6 के लिए 1, 4 गैप गोले! अद्वितीय संयोजनों (012345 के साथ शुरुआत और 543210 के साथ समाप्त) में 7 तुलनाओं और 0 एक्सचेंजों का सबसे अच्छा मामला होगा और 14 तुलनाओं और 10 एक्सचेंजों का सबसे खराब मामला होगा। औसत मामला लगभग 11.14 तुलनाओं और 6 एक्सचेंजों का है।
ओलोफ़ फोर्शेल

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

1
"नियमित यादृच्छिक वितरण" का अर्थ है कि सॉर्ट किए गए वास्तविक डेटा के प्रत्येक संयोजन समान संभावना नहीं हो सकते हैं। यदि प्रत्येक संयोजन समान संभावना के नहीं हैं, तो आपके आँकड़े टूट गए हैं क्योंकि औसत रूप से इस बात का ध्यान रखना है कि किसी दिए गए वितरण के कितनी बार होने की संभावना है। घड़ी की गिनती के लिए, यदि आप इस प्रकार के किसी भी अन्य कार्यान्वयन (ऊपर दिए गए लिंक) को आजमाते हैं और इसे अपने परीक्षण प्रणाली पर चलाते हैं, तो हमारे पास तुलना के लिए एक आधार होगा और देखें कि आपका चुना गया प्रदर्शन कितना अच्छा है।
क्रिश

1

मैं जानता हूं कि यह एक पुराना सवाल है।

लेकिन मैंने सिर्फ एक अलग तरह का समाधान लिखा है जिसे मैं साझा करना चाहता हूं।
कुछ भी नहीं का उपयोग करते हुए न्यूनतम MAX,

यह तेजी से नहीं है क्योंकि यह प्रत्येक के 114 का उपयोग करता है,
इसे 75 सुंदर तक कम कर सकता है जैसे -> पास्टबिन

लेकिन तब यह विशुद्ध रूप से न्यूनतम अधिकतम नहीं है।

AVX के साथ एक साथ कई पूर्णांकों पर मिनट / अधिकतम क्या काम हो सकता है

PMINSW संदर्भ

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

संपादित करें:
रेक्स केर द्वारा प्रेरित रैंक ऑर्डर समाधान, ऊपर की गंदगी की तुलना में बहुत तेज है

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
हमेशा नए समाधान देखने के लिए अच्छा है। ऐसा लगता है कि कुछ आसान अनुकूलन संभव हैं। अंत में यह सॉर्टिंग नेटवर्क से अलग साबित नहीं हो सकता है।
kriss

हां, MIN और MAX की संख्या संभवतः कम की जा सकती है, उदाहरण के लिए MIN (AB, CD) कुछ बार दोहराता है, लेकिन उन्हें कम करना कठिन होगा, मुझे लगता है। मैंने आपके परीक्षण मामलों को जोड़ा।
प्रिंसपालका

pmin / मैक्स पैक किए गए 16-बिट हस्ताक्षरित पूर्णांक ( int16_t) पर काम करता है। लेकिन आपका सी फ़ंक्शन दावा करता है कि यह एक सरणी की तरह है int(जो सभी सी कार्यान्वयन में 32-बिट है जो कि asmसिंटैक्स का समर्थन करता है)। क्या आपने इसे केवल छोटे धनात्मक पूर्णांक के साथ परीक्षण किया है जिनके पास अपने उच्च हिस्सों में केवल 0 है? यह काम करेगा ... आपके लिए intSSE4.1 pmin/maxsd(d = dword) की आवश्यकता है। felixcloutier.com/x86/pminsd:pminsq या के pminusdलिए uint32_t
पीटर कॉर्डेस

1

मैंने पाया कि कम से कम मेरे सिस्टम पर, फ़ंक्शन sort6_iterator()और sort6_iterator_local()नीचे परिभाषित दोनों, कम से कम उपवास के रूप में और ऊपर दिए गए वर्तमान रिकॉर्ड धारक की तुलना में अधिक तेज़ी से ध्यान देने योग्य हैं:

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

मैंने इस फंक्शन को पास किया std::vector अपने टाइमिंग कोड में इट्रेटर पास किया।

मुझे संदेह है ( इस तरह की टिप्पणियों और अन्य जगहों से) जो पुनरावृत्तियों का उपयोग करके जी ++ को इस बात के बारे में कुछ आश्वासन देता है कि क्या हो सकता है और यह मेमोरी के लिए नहीं हो सकता है जो कि इटिटर संदर्भित करता है, जो कि यह अन्यथा नहीं होगा और यह ये आश्वासन हैं जो जी ++ की अनुमति देता है बेहतर सॉर्टिंग कोड को अनुकूलित करें (जैसे संकेत के साथ, संकलक यह सुनिश्चित नहीं कर सकता है कि सभी बिंदु अलग-अलग मेमोरी स्थानों की ओर इशारा कर रहे हैं)। अगर मुझे सही से याद है, तो यह भी इस कारण का हिस्सा है कि इतने सारे एसटीएल एल्गोरिदम, जैसे कि क्यों हैंstd::sort() , आमतौर पर ऐसा अश्लील प्रदर्शन अच्छा है।

इसके अलावा, sort6_iterator()है कुछ बार (फिर से, संदर्भ में जो समारोह कहा जाता है के आधार पर) लगातार निम्नलिखित छँटाई समारोह, द्वारा बेहतर प्रदर्शन किया है जो उन्हें छँटाई से पहले प्रतियां स्थानीय चर में डेटा। 1 ध्यान दें कि चूंकि केवल 6 स्थानीय चर परिभाषित हैं, अगर ये स्थानीय चर आदिम हैं, तो वे संभवतः रैम में कभी संग्रहीत नहीं होते हैं और इसके बजाय केवल फ़ंक्शन कॉल के अंत तक सीपीयू के रजिस्टरों में संग्रहीत होते हैं, जो इस छँटाई में मदद करता है तेजी से कार्य करें। (यह भी मदद करता है कि संकलक जानता है कि अलग-अलग स्थानीय चर स्मृति में अलग-अलग स्थान हैं)।

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
}

ध्यान दें कि परिभाषित करने SWAP()के रूप में इस प्रकार कुछ थोड़ा बेहतर प्रदर्शन में कई बार परिणाम हालांकि समय यह थोड़ा खराब प्रदर्शन या प्रदर्शन में एक नगण्य अंतर में जो परिणाम के सबसे।

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

यदि आप केवल एक छँटाई एल्गोरिथ्म चाहते हैं कि आदिम डेटा प्रकारों पर, gcc -O3 लगातार इस बात को अनुकूलित करने में अच्छा है कि आप किस प्रकार के कॉल के संदर्भ में कॉल 1 में प्रकट होते हैं , तो आप कैसे इनपुट पास करते हैं, इस आधार पर निम्न दो में से एक का प्रयास करें। एल्गोरिदम:

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
}

या यदि आप संदर्भ द्वारा चर को पास करना चाहते हैं तो इसका उपयोग करें (नीचे की फ़ंक्शन इसकी पहली 5 पंक्तियों में ऊपर से अलग है):

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
}

registerकीवर्ड का उपयोग करने का कारण यह है क्योंकि यह उन कुछ समयों में से एक है जो आप जानते हैं कि आप रजिस्टरों में इन मूल्यों को चाहते हैं। इसके बिना register, कंपाइलर अधिकांश समय यह पता लगाएगा, लेकिन कभी-कभी ऐसा नहीं होता है। registerकीवर्ड का उपयोग इस समस्या को हल करने में मदद करता है। आम तौर पर, हालांकि, registerकीवर्ड का उपयोग न करें क्योंकि यह आपके कोड को गति की तुलना में धीमा करने की अधिक संभावना है।

इसके अलावा, टेम्प्लेट के उपयोग पर ध्यान दें। यह उद्देश्य से किया जाता है, यहां तक ​​कि inlineकीवर्ड के साथ भी , टेम्प्लेट फ़ंक्शंस आम तौर पर वेनिला सी फ़ंक्शंस की तुलना में जीसीसी द्वारा अधिक आक्रामक रूप से अनुकूलित किए जाते हैं (यह वेनिला सी फ़ंक्शंस के लिए फ़ंक्शन पॉइंटर्स से निपटने के लिए ज़रूरी है लेकिन टेम्प्लेट फ़ंक्शंस के साथ नहीं)।

  1. विभिन्न छँटाई कार्यों को समय पर करते हुए मैंने देखा कि संदर्भ (अर्थात आस-पास का कोड) जिसमें छँटाई समारोह के लिए कॉल किया गया था, प्रदर्शन पर एक महत्वपूर्ण प्रभाव पड़ा, जो संभवतः फ़ंक्शन के झुकाव और फिर अनुकूलित होने के कारण है। उदाहरण के लिए, यदि प्रोग्राम पर्याप्त रूप से सरल था, तो आमतौर पर छँटाई फ़ंक्शन को एक पॉइंटर बनाम एक इट्रेटर पास करने के बीच प्रदर्शन में बहुत अंतर नहीं था; अन्यथा पुनरावृत्तियों का उपयोग करने के परिणामस्वरूप आमतौर पर बेहतर प्रदर्शन होता है और कभी नहीं (मेरे अनुभव में अब तक कम से कम) किसी भी उल्लेखनीय प्रदर्शन से। मुझे संदेह है कि ऐसा इसलिए हो सकता है क्योंकि जी ++ विश्व स्तर पर पर्याप्त सरल कोड का अनुकूलन कर सकता है।

0

'सॉर्टिंग सॉर्ट की गई सूची' सॉर्ट का प्रयास करें। :) दो सरणी का उपयोग करें। छोटे और बड़े सरणी के लिए सबसे तेज़।
यदि आप संक्षिप्त कर रहे हैं, तो आप केवल यह जांचते हैं कि कहां डालें। अन्य बड़े मूल्यों की आपको तुलना करने की आवश्यकता नहीं है (cmp = ab> 0)।
4 नंबर के लिए, आप सिस्टम 4-5 सीएमपी (~ 4.6) या 3-6 सीएमपी (~ 4.9) का उपयोग कर सकते हैं। बबल सॉर्ट 6 सीएमपी (6) का उपयोग करते हैं। बड़ी संख्या में धीमी कोड के लिए बहुत सीएमपी।
यह कोड 5 cmp (MSL सॉर्ट नहीं) का उपयोग करता है:
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);}

प्रिंसिपल एम.एस.एल. 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

जेएस कोड

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 के साथ 4 आइटम सॉर्ट करें। सीएमपी की संख्या ~ 4.34 (एफएफ मूल निवासी ~ 4.52) है, लेकिन सूची को विलय करने की तुलना में 3x समय लगता है। लेकिन बेहतर सीएमपी संचालन, अगर आपके पास बड़ी संख्या या बड़ा पाठ है। संपादित करें: मरम्मत बग

ऑनलाइन परीक्षा 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
उपयोग का मामला सवाल के शुरुआती संदर्भ से थोड़ा अलग है। निश्चित लंबाई के साथ विवरण के मामले और स्वैप की गिनती सीएमपी पर्याप्त नहीं है। मैं भी आश्चर्यचकित नहीं होता अगर यह वास्तविक प्रकार नहीं होता जो कि समय का उपभोग करता, लेकिन कुछ पूरी तरह से अलग तरह का लाइट टाइपिंग () इनिट में होता। मुझे पता नहीं है कि जावास्क्रिप्ट का उपयोग करके वास्तविक घड़ी का समय प्रदर्शन कैसे किया जाता है। शायद नोड के साथ?
क्रि

0

हो सकता है कि मुझे पार्टी में देरी हो, लेकिन कम से कम मेरा योगदान एक नया दृष्टिकोण है।

  • कोड वास्तव में इनलेट होना चाहिए
  • यहां तक ​​कि अगर झुका हुआ है, तो बहुत अधिक शाखाएं हैं
  • विश्लेषण करने वाला हिस्सा मूल रूप से O (N (N-1)) है जो N = 6 के लिए ठीक लगता है
  • कोड अधिक प्रभावी हो सकता है यदि लागतswap अधिक होगी (irt की लागत compare)
  • मैं स्थिर कार्यों पर भरोसा किया जा रहा है।
  • विधि रैंक-सॉर्ट से संबंधित है
    • रैंकों के बजाय, सापेक्ष रैंकों (ऑफसेट) का उपयोग किया जाता है।
    • किसी भी क्रमचय समूह में प्रत्येक चक्र के लिए रैंकों का योग शून्य है ।
    • SWAP()आईएनजी के बजाय दो तत्वों, चक्रों का पीछा किया जाता है, केवल एक अस्थायी की जरूरत होती है, और एक (रजिस्टर-> रजिस्टर) स्वैप (नया <- पुराना)।

अपडेट: कोड को थोड़ा बदल दिया, कुछ लोग C कोड को संकलित करने के लिए C ++ कंपाइलर का उपयोग करते हैं ...

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

एक बुलबुले की तरह दिखता है। संभावित रूप से सबसे धीमी कार्यान्वयन के लिए एक अच्छा दावेदार है, लेकिन यह जानने के लिए अभी भी कुछ रुचि हो सकती है कि क्या कोड पर काम करने से बहुत अंतर पड़ता है। कृपया अपना कोड दूसरों के समान प्रारूप में रखें, इस प्रकार हम इस पर बेंचमार्क चला सकते हैं।
क्रिश

@kriss en.wikipedia.org/wiki/Permutation_group यह निश्चित रूप से बुलबुला प्रकार नहीं है : कोड दिए गए क्रमचय में चक्रों का पता लगाता है, और इन चक्रों को चलाता है, प्रत्येक तत्व को अपने अंतिम स्थान पर रखता है। अंतिम wsort6()फ़ंक्शन का सही इंटरफ़ेस है।
जॉफ

@ हज़ॉप: मेरा बुरा, कोई बुलबुला नहीं वास्तव में। इस संदर्भ में कहा जा रहा है कि मैं अभी भी किसी अन्य मौजूदा कार्यान्वयन की तुलना में कोड को बहुत खराब होने की उम्मीद कर रहा हूं। वैसे रैंक ऑर्डर समाधान स्वैप की संख्या के बारे में इष्टतम है क्योंकि यह सीधे प्रत्येक आइटम की अंतिम स्थिति को ढूंढता है। यह भी स्पष्ट नहीं है कि जब हम इस परिकल्पना को हटाते हैं तो वॉकॉर्ट भी काम करता है कि सभी क्रमबद्ध संख्याएं यहां की तरह अलग हैं। कोड को बेंचमार्क करने के लिए हमें ट्रेस कोड चाहिए। जैसा कि मैं आमतौर पर एक सी ++ कंपाइलर पर संकलन कर रहा हूं, कोड काम नहीं करेगा क्योंकि ओपी एक चर "नया" (और thats सिंटैक्स हाइलाइटिंग को तोड़ता है) कहलाता है।
क्रिश

विधि रैंक क्रम के बहुत करीब है, केवल अंतिम असाइनमेंट जगह में किए जाते हैं । रैंकों के अलावा o1..o5, दूसरे अस्थायी e[6]सरणी की कोई आवश्यकता नहीं है । और: एक C ++ कंपाइलर पर C कोड संकलित करना, और कोड को दोष देना?
जूप

@greybeard: धन्यवाद, मैंने पहले एक स्थान जोड़ा #include। फिक्स्ड
वाइल्डपलासर 23

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
}

गति के बावजूद क्या आपको यकीन है कि यह काम करता है? Bruteforce तरह में अपने छोरों संदिग्ध हैं। लगता है कि अगर हम क्रमबद्ध मानों में शून्य हैं तो वे काम नहीं करेंगे।
क्रि

1
t [6] सरणी को 0x0 से आरंभ किया गया है। तो यह कोई फर्क नहीं पड़ता कि कहाँ और अगर 0x0 मूल्यवान कुंजी लिखी जाएगी।
फ्रांज

-1

ठीक है, अगर यह केवल 6 तत्व हैं और आप समानता का लाभ उठा सकते हैं, सशर्त ब्रांचिंग को कम करना चाहते हैं, आदि। आप सभी संयोजनों और आदेश के लिए परीक्षण क्यों नहीं बनाते हैं? मैं कहता हूं कि कुछ आर्किटेक्चर में, यह बहुत तेज़ हो सकता है (जब तक आपके पास मेमोरी उपदेशित है)


9
720 आदेश हैं, और तेजी से संस्करण 100 चक्रों के तहत अच्छी तरह से हैं। यहां तक ​​कि अगर बड़े पैमाने पर समानता का लाभ उठाया जा सकता है, तो इतने कम समय में थ्रेड बनाने और सिंक्रनाइज़ करने की लागत संभवतः एक कोर पर सरणियों को छांटने की लागत से अधिक होगी।
केविन स्टॉक

-3

यहां तीन विशिष्ट सॉर्टिंग विधियां हैं जो सॉर्टिंग एल्गोरिदम के तीन अलग-अलग वर्गों का प्रतिनिधित्व करती हैं:

Insertion Sort: Θ(n^2)

Heap Sort: Θ(n log n)

Count Sort: Θ(3n)

लेकिन सबसे तेज छँटाई एल्गोरिथ्म पर स्टीफन नेल्सन चर्चा की जाँच करें ? जहां वह एक समाधान पर चर्चा करता है, जो सी में इसके कार्यान्वयन कीO(n log log n) जांच करता है

यह अर्ध-रैखिक छंटाई एल्गोरिथ्म 1995 में एक पेपर द्वारा प्रस्तुत किया गया था:

ए। एंडरसन, टी। हैगरअप, एस। निल्सन और आर। रमन। रैखिक समय में छंटनी? कम्प्यूटिंग के सिद्धांत पर 27 वें वार्षिक एसीएम संगोष्ठी की कार्यवाही में, पृष्ठ 427-436, 1995।


8
यह दिलचस्प है लेकिन बिंदु के बगल में है। बिग--का उद्देश्य निरंतर कारकों को छिपाना और प्रवृत्ति को दिखाना है जब समस्या का आकार (n) बड़ा हो जाता है। यहाँ समस्या पूरी तरह से एक निश्चित समस्या आकार (n = 6) के बारे में है और लगातार कारकों को ध्यान में रखते हुए है।
क्रिश

@ आप सही कह रहे हैं, मेरी तुलना स्पर्शोन्मुख है, इसलिए व्यावहारिक तुलना यह दिखाएगी कि यह तेज है या नहीं, इस मामले के लिए
खालिद। के।

4
आप निष्कर्ष नहीं निकाल सकते हैं, क्योंकि प्रत्येक अलग एल्गोरिथ्म एक अलग कश्मीर गुणक स्थिरांक छुपाता है (और एक सी योजक स्थिरांक भी)। अर्थात: प्रविष्टि प्रकार के लिए k0, c0, ढेर प्रकार के लिए k1, c1 और इसी तरह। उन सभी स्थिरांक वास्तव में अलग-अलग हो रहे हैं (आप भौतिक टर्मिनस में कह सकते हैं कि प्रत्येक एल्गोरिथ्म का अपना "घर्षण गुणांक" है) आप निष्कर्ष नहीं निकाल सकते हैं कि एल्गोरिथ्म वास्तव में इस मामले (या किसी भी निश्चित n मामले) में तेज है।
क्रिश
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.