เรียงลำดับที่เร็วที่สุดของอาร์เรย์ความยาวคงที่ 6 int


401

ตอบคำถาม Stack Overflow (อันนี้ ) ฉันพบปัญหาย่อยที่น่าสนใจ วิธีที่เร็วที่สุดในการจัดเรียงอาร์เรย์ของ 6 จำนวนเต็มคืออะไร?

เนื่องจากคำถามอยู่ในระดับต่ำมาก:

  • เราไม่สามารถสมมติว่ามีไลบรารี (และการโทรเองมีค่าใช้จ่าย) เพียงธรรมดา C
  • เพื่อหลีกเลี่ยงการล้างท่อส่งคำสั่ง (ที่มีค่าใช้จ่ายสูงมาก ) เราควรจะลดกิ่งกระโดดและการควบคุมการไหลอื่น ๆ ทุกชนิด (เช่นที่ซ่อนอยู่หลังจุดลำดับใน&&หรือ||)
  • ห้องมีข้อ จำกัด และการลดการลงทะเบียนและการใช้หน่วยความจำก็เป็นปัญหา

คำถามนี้เป็นคำถามประเภทกอล์ฟที่เป้าหมายไม่ได้ลดความยาวของแหล่งที่มา แต่ลดระยะเวลาดำเนินการลง ผมเรียกรหัสมัน Zening 'ที่ใช้ในชื่อของหนังสือเล่มนี้เซนของการเพิ่มประสิทธิภาพรหัสโดยไมเคิล Abrashและต่อมา

ทำไมมันถึงน่าสนใจมีหลายเลเยอร์:

  • ตัวอย่างนั้นง่ายและเข้าใจง่ายและวัดผลไม่เกี่ยวข้องกับทักษะ C มากนัก
  • มันแสดงผลของการเลือกอัลกอริทึมที่ดีสำหรับปัญหา แต่ยังมีผลกระทบของคอมไพเลอร์และฮาร์ดแวร์พื้นฐาน

นี่คือการดำเนินการอ้างอิงของฉัน (ไร้เดียงสาไม่เหมาะ) และชุดทดสอบของฉัน

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

ผลดิบ

ขณะที่จำนวนของสายพันธุ์กลายเป็นขนาดใหญ่ฉันรวบรวมพวกเขาทั้งหมดในชุดทดสอบที่สามารถพบได้ที่นี่ การทดสอบจริงที่ใช้มีน้อยกว่าที่แสดงไว้ข้างต้นเล็กน้อยขอบคุณ Kevin Stock คุณสามารถรวบรวมและดำเนินการในสภาพแวดล้อมของคุณเอง ฉันค่อนข้างสนใจพฤติกรรมในสถาปัตยกรรม / คอมไพเลอร์เป้าหมายที่ต่างกัน (ตกลงเอาไว้ตอบแล้วฉันจะ +1 ผู้มีส่วนร่วมของชุดผลลัพธ์ใหม่)

ฉันให้คำตอบกับ Daniel Stutzbach (สำหรับการเล่นกอล์ฟ) เมื่อหนึ่งปีก่อนเพราะเขาอยู่ที่แหล่งที่มาของการแก้ปัญหาที่เร็วที่สุดในเวลานั้น (เครือข่ายคัดแยก)

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

  • โทรโดยตรงไปยังฟังก์ชั่นห้องสมุด qsort: 689.38
  • การใช้งานแบบไร้เดียงสา (เรียงลำดับการแทรก): 285.70
  • เรียงลำดับการแทรก (Daniel Stutzbach): 142.12
  • แทรกการเรียงลำดับ Unrolled: 125.47
  • อันดับสั่งซื้อ: 102.26
  • อันดับสั่งซื้อด้วยการลงทะเบียน: 58.03
  • เครือข่ายเรียงลำดับ (Daniel Stutzbach): 111.68
  • เครือข่ายการเรียงลำดับ (Paul R): 66.36
  • การเรียงลำดับเครือข่าย 12 ที่มีการสลับที่รวดเร็ว: 58.86
  • เครือข่ายการเรียงลำดับ 12 Swap เรียงลำดับใหม่: 53.74
  • เครือข่ายการเรียงลำดับ 12 Simple Swap ที่เรียงลำดับใหม่: 31.54
  • จัดเรียงเครือข่ายการเรียงลำดับใหม่ที่มีการสลับที่รวดเร็ว: 31.54
  • เรียงลำดับเครือข่ายใหม่ด้วยการสลับที่รวดเร็ว V2: 33.63
  • Inline Bubble Sort (Paolo Bonzini): 48.85
  • เรียงแทรกที่ไม่ได้ควบคุม (เปาโล Bonzini): 75.30

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

  • โทรโดยตรงไปยังฟังก์ชันไลบรารี qsort: 705.93
  • การใช้งานแบบไร้เดียงสา (เรียงลำดับการแทรก): 135.60
  • เรียงลำดับการแทรก (Daniel Stutzbach): 142.11
  • แทรกการเรียงลำดับ Unrolled: 126.75
  • อันดับสั่งซื้อ: 46.42
  • อันดับสั่งซื้อด้วยการลงทะเบียน: 43.58
  • เครือข่ายเรียงลำดับ (Daniel Stutzbach): 115.57
  • เครือข่ายการเรียงลำดับ (Paul R): 64.44
  • การเรียงลำดับเครือข่าย 12 ที่มีการสลับที่รวดเร็ว: 61.98
  • เครือข่ายการเรียงลำดับ 12 Swap เรียงลำดับใหม่: 54.67
  • เครือข่ายการเรียงลำดับ 12 Simple Swap ที่เรียงลำดับใหม่: 31.54
  • จัดเรียงเครือข่ายการเรียงลำดับใหม่ที่มีการสลับที่รวดเร็ว: 31.24
  • เรียงลำดับเครือข่ายใหม่ด้วยการสลับที่รวดเร็ว V2: 33.07
  • Inline Bubble Sort (Paolo Bonzini): 45.79
  • เรียงแทรกที่ไม่ได้ควบคุม (เปาโล Bonzini): 80.15

ฉันรวมทั้งผลลัพธ์ -O1 และ -O2 เพราะน่าแปลกใจสำหรับหลาย ๆ โปรแกรม O2 มีประสิทธิภาพน้อยกว่า O1 ฉันสงสัยว่าการเพิ่มประสิทธิภาพเฉพาะใดมีผลกระทบนี้

ความคิดเห็นเกี่ยวกับโซลูชันที่เสนอ

เรียงลำดับการแทรก (Daniel Stutzbach)

ตามที่คาดไว้การย่อขนาดสาขาจึงเป็นความคิดที่ดี

เครือข่ายคัดแยก (Daniel Stutzbach)

ดีกว่าการเรียงลำดับการแทรก ฉันสงสัยว่าเอฟเฟกต์หลักไม่ได้มาจากการหลีกเลี่ยงลูปภายนอกหรือไม่ ฉันลองใช้การเรียงลำดับการแทรกที่ไม่ได้ลงทะเบียนเพื่อตรวจสอบและแน่นอนว่าเราได้ตัวเลขที่เหมือนกัน (รหัสอยู่ที่นี่ )

เครือข่ายการเรียงลำดับ (Paul R)

ที่ดีที่สุดจนถึง ฉันรหัสจริงที่ใช้ในการทดสอบที่นี่ ยังไม่รู้ว่าทำไมมันถึงเร็วกว่าการใช้เครือข่ายการเรียงลำดับอื่นเกือบสองเท่า ผ่านพารามิเตอร์หรือไม่ เร็วสุดไหม

การเรียงลำดับเครือข่าย 12 SWAP พร้อมการสลับที่รวดเร็ว

ตามคำแนะนำของ Daniel Stutzbach ฉันได้รวมเครือข่ายการจัดเรียงสลับ 12 รายการของเขาเข้ากับการสลับเร็วแบบไม่มีสาขา (รหัสอยู่ที่นี่ ) มันเร็วกว่ามากที่สุดเท่าที่จะทำได้ด้วยอัตรากำไรขั้นต้นเล็กน้อย (ประมาณ 5%) ตามที่คาดไว้โดยใช้การแลกเปลี่ยนน้อย 1 ครั้ง

นอกจากนี้ยังเป็นที่น่าสนใจที่จะสังเกตเห็นว่าการแลกเปลี่ยนแบบไร้สาขาดูเหมือนจะมีประสิทธิภาพน้อยกว่าแบบง่าย ๆ ที่ใช้หากใช้กับสถาปัตยกรรม PPC

กำลังเรียก Library qsort

หากต้องการให้จุดอ้างอิงอื่นฉันได้ลองตามที่แนะนำให้เพียงเรียกไลบรารี qsort (รหัสอยู่ที่นี่ ) ตามที่คาดไว้มันช้ากว่ามาก: 10 ถึง 30 เท่าช้าลง ... เนื่องจากเห็นได้ชัดกับชุดทดสอบใหม่ปัญหาหลักน่าจะเป็นภาระเริ่มต้นของไลบรารีหลังจากการโทรครั้งแรกและเปรียบเทียบได้ไม่ดีนักเมื่อเทียบกับคนอื่น ๆ รุ่น มันช้าลงระหว่าง 3 และ 20 เท่าบน Linux ของฉัน ในบางสถาปัตยกรรมที่ใช้สำหรับการทดสอบโดยคนอื่น ๆ ดูเหมือนว่าจะเร็วขึ้น (ฉันประหลาดใจจริง ๆ โดยที่ไลบรารี qsort ใช้ API ที่ซับซ้อนมากขึ้น)

อันดับการสั่งซื้อ

เร็กซ์เคอร์เสนอวิธีอื่นที่แตกต่างกันโดยสิ้นเชิง: สำหรับแต่ละรายการของอาเรย์จะคำนวณตำแหน่งสุดท้าย สิ่งนี้มีประสิทธิภาพเพราะลำดับการคำนวณไม่ต้องการสาขา ข้อเสียของวิธีนี้คือจะต้องใช้จำนวนหน่วยความจำของอาเรย์สามเท่า (หนึ่งชุดของอาเรย์และตัวแปรเพื่อเก็บคำสั่งการจัดอันดับ) ผลการดำเนินงานที่น่าแปลกใจมาก (และน่าสนใจ) ในสถาปัตยกรรมอ้างอิงของฉันกับระบบปฏิบัติการ 32 บิตและ Intel Core2 Quad E8300 จำนวนรอบน้อยกว่า 1,000 เล็กน้อย (เช่นเครือข่ายการเรียงลำดับที่มีการแยกแบรนช์) แต่เมื่อรวบรวมและดำเนินการในกล่อง 64 บิตของฉัน (Intel Core2 Duo) มันทำงานได้ดีขึ้นมาก: มันกลายเป็นเร็วที่สุดจนถึงตอนนี้ ในที่สุดฉันก็พบเหตุผลที่แท้จริง กล่อง 32 บิตของฉันใช้ gcc 4.4.1 และกล่อง 64 บิตของฉัน gcc 4.4

อัปเดต :

เมื่อตัวเลขที่เผยแพร่ข้างต้นแสดงให้เห็นว่าเอฟเฟกต์นี้ยังคงได้รับการปรับปรุงโดย gcc รุ่นที่ใหม่กว่าและลำดับการจัดอันดับก็เร็วขึ้นอย่างต่อเนื่องเป็นสองเท่าของทางเลือกอื่น

การเรียงลำดับเครือข่าย 12 ที่มีการเรียงลำดับใหม่

ประสิทธิภาพที่น่าทึ่งของข้อเสนอ Rex Kerr กับ gcc 4.4.3 ทำให้ฉันสงสัยว่า: โปรแกรมที่มีการใช้หน่วยความจำ 3 ครั้งเร็วกว่าเครือข่ายการคัดแยกที่ไม่มีสาขาได้อย่างไร สมมติฐานของฉันคือว่ามันมีการพึ่งพาของการอ่านชนิดน้อยกว่าหลังจากการเขียนอนุญาตให้ใช้ตัวกำหนดตารางเวลาการสอน superscalar ของ x86 ได้ดีขึ้น นั่นทำให้ฉันมีความคิด: เรียงลำดับการแลกเปลี่ยนใหม่เพื่อลดการอ่านหลังจากการเขียนอ้างอิง พูดง่ายกว่า: เมื่อคุณSWAP(1, 2); SWAP(0, 2);ต้องรอให้การแลกเปลี่ยนครั้งแรกเสร็จสิ้นก่อนที่จะทำการสลับครั้งที่สองเพราะทั้งคู่เข้าถึงเซลล์หน่วยความจำทั่วไป เมื่อคุณทำSWAP(1, 2); SWAP(4, 5);โปรเซสเซอร์สามารถดำเนินการทั้งคู่ในแบบคู่ขนาน ฉันลองและใช้งานได้ตามที่คาดหวังเครือข่ายการเรียงลำดับจะทำงานเร็วขึ้นประมาณ 10%

การเรียงลำดับเครือข่าย 12 ด้วยการสลับอย่างง่าย

หนึ่งปีหลังจากโพสต์ดั้งเดิม Steinar H. Gunderson แนะนำว่าเราไม่ควรพยายามที่จะเอาชนะสติปัญญาของคอมไพเลอร์และทำให้รหัสการสลับง่าย เป็นความคิดที่ดีเพราะรหัสที่ได้นั้นเร็วกว่าประมาณ 40% นอกจากนี้เขายังเสนอการแลกเปลี่ยนที่ปรับให้เหมาะสมด้วยมือโดยใช้รหัสการประกอบแบบอินไลน์ x86 ที่ยังคงสามารถสำรองได้มากขึ้น สิ่งที่น่าประหลาดใจที่สุด (มันบอกว่าปริมาณของจิตวิทยาของโปรแกรมเมอร์) คือเมื่อหนึ่งปีที่แล้วไม่มีการทดลองใช้การแลกเปลี่ยนเวอร์ชันนั้น รหัสผมใช้ในการทดสอบที่นี่ คนอื่น ๆ แนะนำวิธีอื่นในการเขียน C แลกเปลี่ยนอย่างรวดเร็ว แต่ให้ผลการแสดงเช่นเดียวกับวิธีที่เรียบง่ายด้วยคอมไพเลอร์ที่ดี

รหัส "ดีที่สุด" ตอนนี้เป็นดังนี้:

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
คุณมีข้อ จำกัด บางอย่างเกี่ยวกับ ints หรือไม่? ตัวอย่างเช่นเราสามารถสมมติว่าสำหรับ 2 x, y x-yและx+yจะไม่ทำให้เกิดอันเดอร์โฟล์หรือโอเวอร์โฟลว์หรือไม่?
Matthieu M.

3
คุณควรลองรวมเครือข่ายการเรียงลำดับ 12-swap ของฉันกับฟังก์ชั่นการแลกเปลี่ยนแบบไม่มีสาขาของ Paul วิธีการแก้ปัญหาของเขาส่งผ่านพารามิเตอร์ทั้งหมดเป็นองค์ประกอบแยกต่างหากบนสแต็กแทนที่จะเป็นตัวชี้เดียวไปยังอาร์เรย์ ที่อาจสร้างความแตกต่าง
Daniel Stutzbach

2
โปรดทราบว่าการนำไปปฏิบัติที่ถูกต้องของ rdtsc บน 64- บิตเป็น__asm__ volatile (".byte 0x0f, 0x31; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");เพราะ rdtsc ทำให้คำตอบใน EDX: EAX ในขณะที่ GCC คาดว่ามันจะอยู่ในการลงทะเบียน 64- บิตเดียว คุณสามารถดูข้อผิดพลาดโดยการรวบรวมที่ -O3 ดูความคิดเห็นด้านล่างของฉันที่มีต่อ Paul R เกี่ยวกับ SWAP ที่เร็วขึ้น
เปาโลบอนซินี

3
@Tyler: คุณนำไปใช้ในระดับการชุมนุมโดยไม่ต้องสาขาได้อย่างไร
Loren Pechtel

4
@ Loren: CMP EAX, EBX; SBB EAX, EAXจะใส่ 0 หรือ 0xFFFFFFFF EAXขึ้นอยู่กับว่าEAXมีขนาดใหญ่กว่าหรือเล็กกว่าEBXตามลำดับ SBBคือ "ลบด้วยการยืม" ซึ่งเป็นคู่ของADC("เพิ่มด้วยการพกพา"); บิตสถานะที่คุณอ้างถึงคือบิตพกพา จากนั้นอีกครั้งฉันจำได้ว่าADCและSBBมีความล่าช้าอย่างมากและปริมาณงานใน Pentium 4 เทียบกับADDและSUBและยังคงช้ากว่า CPU Core เป็นสองเท่า ตั้งแต่ 80386 ยังมีคำแนะนำแบบมีเงื่อนไขSETccและCMOVccการย้ายแบบมีเงื่อนไข แต่พวกมันก็ช้าเช่นกัน
j_random_hacker

คำตอบ:


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 ครั้งมากกว่า 13 รายการในเครือข่ายที่เขียนด้วยมือและได้รับประจักษ์ ฉันจะให้ +1 อีกถ้าคุณสามารถเชื่อมโยงไปยังเว็บไซต์ที่สร้างเครือข่ายสำหรับคุณ - ตอนนี้คั่นหน้า
Paul R

9
นี่เป็นแนวคิดที่ยอดเยี่ยมสำหรับฟังก์ชันการเรียงลำดับวัตถุประสงค์ทั่วไปหากคุณคาดหวังว่าคำขอส่วนใหญ่จะเป็นอาร์เรย์ขนาดเล็ก ใช้คำสั่ง switch สำหรับเคสที่คุณต้องการปรับให้เหมาะสมโดยใช้โพรซีเดอร์นี้ ปล่อยให้กรณีเริ่มต้นใช้ฟังก์ชั่นการจัดเรียงห้องสมุด
Mark Ransom

5
@Mark ฟังก์ชั่นการเรียงลำดับห้องสมุดที่ดีจะมีเส้นทางลัดสำหรับอาร์เรย์ขนาดเล็กอยู่แล้ว ห้องสมุดที่ทันสมัยหลายแห่งจะใช้ QuickSort หรือ MergeSort แบบเรียกซ้ำที่เปลี่ยนเป็น InsertionSort หลังจากเรียกใช้n < SMALL_CONSTANTซ้ำ
Daniel Stutzbach

3
@Mark ดีฟังก์ชั่นการเรียงลำดับไลบรารี C ต้องการให้คุณระบุการดำเนินการเปรียบเทียบผ่านฟังก์ชั่นพนักงานยกกระเป๋า ค่าใช้จ่ายในการโทรฟังก์ชั่นสำหรับการเปรียบเทียบทุกครั้งมีขนาดใหญ่มาก โดยปกติแล้วยังคงเป็นวิธีที่สะอาดที่สุดเนื่องจากนี่เป็นเส้นทางที่สำคัญในโปรแกรม อย่างไรก็ตามหากเป็นเส้นทางที่สำคัญเราสามารถเรียงลำดับได้เร็วขึ้นถ้าเรารู้ว่าเรากำลังจัดเรียงจำนวนเต็มและ 6 อย่างแน่นอน :)
Daniel Stutzbach

7
@tgwh: การแลกเปลี่ยน XOR เป็นความคิดที่ไม่ดี
Paul R

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
สำหรับแฮ็กบิตสำหรับ min / max: graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
Rubys

1
@ พอล: ในบริบทการใช้งานจริงของ CUDA มันเป็นคำตอบที่ดีที่สุดอย่างแน่นอน ฉันจะตรวจสอบว่ามันเป็น (และเท่าไหร่) ในบริบทของ x64 กอล์ฟและเผยแพร่ผล
kriss

1
Sort3จะเร็วกว่า (สำหรับสถาปัตยกรรมส่วนใหญ่) หากคุณระบุว่า(a+b+c)-(min+max)เป็นหมายเลขกลาง
Rex Kerr

1
@Rex: ฉันเห็น - ที่ดูดี สำหรับสถาปัตยกรรม SIMD เช่น AltiVec และ SSE จะเป็นจำนวนรอบของคำสั่งเดียวกัน (สูงสุดและต่ำสุดคือคำสั่งรอบเดียวเช่นเพิ่ม / ลบ) แต่สำหรับ CPU สเกลาร์ปกติวิธีการของคุณดูดีกว่า
พอลอาร์

2
ถ้าฉันปล่อยให้ GCC เพิ่มประสิทธิภาพนาทีพร้อมกับคำแนะนำย้ายเงื่อนไขฉันได้รับ speedup #define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }33%: ที่นี่ฉันไม่ได้ใช้หรือไม่: สำหรับ 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 ต่ำกว่า 1,000 รอบค่อนข้างเร็ว แต่ช้ากว่าการเรียงลำดับเครือข่าย มีความคิดที่จะปรับปรุงโค้ดหรือไม่ บางทีถ้าเราสามารถหลีกเลี่ยงการสำเนาอาร์เรย์ ...
Kriss

@kriss: มันเร็วกว่าเครือข่ายการเรียงลำดับสำหรับฉันด้วย -O2 มีเหตุผลบางอย่างที่ -O2 ไม่เป็นไรหรือช้ากว่าสำหรับคุณใน -O2 ด้วยบ้างไหม? อาจจะเป็นความแตกต่างในสถาปัตยกรรมเครื่อง?
Rex Kerr

1
@Rex: ขอโทษฉันพลาด> vs> = pattern ตั้งแต่แรกเห็น มันทำงานได้ในทุกกรณี
kriss

3
@kriss: Aha นั่นไม่น่าแปลกใจเลย - มีตัวแปรมากมายที่ลอยอยู่รอบ ๆ และพวกเขาจะต้องสั่งอย่างระมัดระวังและแคชในการลงทะเบียนและอื่น ๆ
Rex Kerr

2
@SSpoke 0+1+2+3+4+5=15เนื่องจากหนึ่งในนั้นหายไป 15 ลบผลรวมของส่วนที่เหลือให้หนึ่งหายไป
Glenn Teitelbaum

35

ดูเหมือนว่าฉันจะไปงานเลี้ยงปลายปี แต่ที่นี่เราไป ...

ดูแอสเซมบลีที่สร้างโดย gcc 4.5.2 ฉันสังเกตเห็นว่ามีการโหลดและร้านค้าสำหรับการแลกเปลี่ยนทุกครั้งซึ่งไม่จำเป็นจริงๆ มันจะเป็นการดีกว่าถ้าจะโหลดค่า 6 ค่าลงในรีจิสเตอร์จัดเรียงค่าเหล่านั้นและเก็บไว้ในหน่วยความจำ ฉันสั่งให้โหลดที่ร้านค้าใกล้เคียงที่สุดเท่าที่จะเป็นไปได้มีการลงทะเบียนสิ่งแรกที่จำเป็นและใช้ครั้งสุดท้าย ฉันยังใช้มาโคร SWAP ของ Steinar H. Gunderson อัปเดต: ฉันเปลี่ยนเป็นมาโคร SWAP ของ Paolo Bonzini ซึ่ง gcc แปลงเป็นสิ่งที่คล้ายกับของ Gunderson แต่ gcc สามารถสั่งซื้อคำแนะนำได้ดีกว่าเพราะพวกเขาไม่ได้รับการกำหนดอย่างชัดเจน

ฉันใช้คำสั่งสลับเดียวกันกับเครือข่ายแลกเปลี่ยนที่เรียงลำดับใหม่ซึ่งให้ประสิทธิภาพดีที่สุดถึงแม้ว่าอาจจะมีการเรียงลำดับที่ดีกว่าก็ตาม ถ้าฉันหาเวลาเพิ่มอีกฉันจะสร้างและทดสอบวิธีเรียงสับเปลี่ยน

ฉันเปลี่ยนรหัสการทดสอบเพื่อพิจารณาอาร์เรย์มากกว่า 4000 อาร์เรย์และแสดงจำนวนรอบเฉลี่ยที่จำเป็นในการจัดเรียงแต่ละอัน บน i5-650 ฉันได้รับ ~ 34.1 รอบ / เรียงลำดับ (โดยใช้ -O3) เมื่อเปรียบเทียบกับเครือข่ายการเรียงลำดับที่เรียงลำดับเดิมได้รับ ~ 65.3 รอบ / เรียง (ใช้ -O1, เต้น -O2 และ -O3)

#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 แต่ 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

ความคิดของคุณเกี่ยวกับตัวแปรการลงทะเบียนควรนำไปใช้กับโซลูชัน "อันดับสั่งซื้อ" ของ Rex Kerr นั่นควรจะเร็วที่สุดและบางทีการปรับให้-O3เหมาะสมจะไม่เกิดผล
cdunn2001

1
@ cdunn2001 ฉันเพิ่งทดสอบฉันไม่เห็นการปรับปรุง (ยกเว้นบางรอบที่ -O0 และ -Os) ดู asm ปรากฏว่า gcc มีการจัดการเพื่อใช้ register และกำจัดการเรียกไปยัง memcpy
Kevin Stock

คุณจะช่วยเพิ่มชุดแลกเปลี่ยนอย่างง่าย ๆ ให้กับชุดทดสอบของคุณได้ไหมฉันคิดว่ามันน่าสนใจที่จะเปรียบเทียบกับชุดประกอบการเปลี่ยนอย่างรวดเร็วด้วยมือ
kriss

1
รหัสของคุณยังคงใช้แลกเปลี่ยน Gunderson #define SWAP(x,y) { int oldx = x; x = x < y ? x : y; y ^= oldx ^ x; }ของฉันก็จะเป็น
เปาโลบอนซินี

@ เปาโล Bonzini: ใช่ฉันตั้งใจจะเพิ่มกรณีทดสอบกับคุณ แต่ยังไม่มีเวลา แต่ฉันจะหลีกเลี่ยงการชุมนุมแบบอินไลน์
kriss

15

ฉันสะดุดคำถามนี้จาก Google เมื่อสองสามวันก่อนเพราะฉันยังต้องการเรียงลำดับความยาวคงที่ของจำนวนเต็ม 6 จำนวนอย่างรวดเร็ว ในกรณีของฉัน แต่จำนวนเต็มของฉันมีเพียง 8 บิต (แทน 32) และฉันไม่มีข้อกำหนดที่เข้มงวดเพียงใช้ C. ฉันคิดว่าฉันจะแบ่งปันสิ่งที่ค้นพบของฉันต่อไปในกรณีที่พวกเขาอาจเป็นประโยชน์กับใครบางคน ...

ฉันใช้ตัวแปรของการเรียงลำดับเครือข่ายในแอสเซมบลีที่ใช้ SSE เพื่อ vectorize การเปรียบเทียบและสลับการดำเนินการเท่าที่เป็นไปได้ ใช้เวลาหก "ส่งผ่าน" เพื่อเรียงลำดับอาร์เรย์อย่างสมบูรณ์ ฉันใช้กลไกใหม่เพื่อแปลงผลลัพธ์ของ PCMPGTB โดยตรง (vectorized เปรียบเทียบ) เป็น shuffle พารามิเตอร์สำหรับ PSHUFB (vectorized swap) โดยใช้เพียง PADDB (เพิ่ม vectorized) และในบางกรณีก็เป็นคำสั่ง PAND (bitwise AND)

วิธีการนี้ยังมีผลข้างเคียงของการทำหน้าที่ไร้สาขาอย่างแท้จริง ไม่มีคำแนะนำการกระโดดใด ๆ

ปรากฏว่าการใช้งานนี้ นี้เร็วกว่าการใช้งานประมาณ 38%ซึ่งปัจจุบันถูกทำเครื่องหมายว่าเป็นตัวเลือกที่เร็วที่สุดในคำถาม ("การเรียงลำดับเครือข่าย 12 ที่มีการสลับอย่างง่าย") ฉันปรับเปลี่ยนการใช้งานนั้นเพื่อใช้charองค์ประกอบอาร์เรย์ในระหว่างการทดสอบของฉันเพื่อทำการเปรียบเทียบที่ยุติธรรม

ฉันควรทราบว่าวิธีการนี้สามารถนำไปใช้กับขนาดอาร์เรย์ได้มากถึง 16 องค์ประกอบ ฉันคาดว่าข้อได้เปรียบความเร็วสัมพัทธ์เหนือทางเลือกจะเพิ่มขึ้นสำหรับอาร์เรย์ขนาดใหญ่

รหัสนี้เขียนขึ้นใน MASM สำหรับโปรเซสเซอร์ x86_64 พร้อม SSSE3 ฟังก์ชั่นนี้ใช้หลักการเรียกแบบ "ใหม่" ของ Windows 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 ของคุณ สำหรับคำแนะนำเกี่ยวกับวิธีการทำเช่นนี้ใน Visual Studio คุณสามารถอ่านบทความนี้ คุณสามารถใช้ C ต้นแบบต่อไปนี้เพื่อเรียกใช้ฟังก์ชันจากรหัส C ของคุณ:

void simd_sort_6(char *values);

มันน่าจะเป็นการเปรียบเทียบของคุณกับข้อเสนอระดับการประกอบอื่น ๆ การเปรียบเทียบผลการดำเนินงานเปรียบเทียบไม่รวม การใช้ SSE ฟังดูดีอยู่ดี
kriss

การวิจัยในอนาคตก็คือการประยุกต์ใช้คำแนะนำใหม่ของ Intel AVX กับปัญหานี้ เวกเตอร์ 256 บิตที่ใหญ่กว่านั้นใหญ่พอที่จะใส่ 8 DWORD ได้
Joe Crivello

1
แทนที่จะpxor / pinsrd xmm4, mem, 0ใช้เพียงmovd!
Peter Cordes

14

รหัสทดสอบค่อนข้างแย่ มันล้นอาร์เรย์เริ่มต้น (ไม่ใช่คนที่นี่อ่านคำเตือนคอมไพเลอร์?) printf กำลังพิมพ์องค์ประกอบที่ไม่ถูกต้องใช้. byte สำหรับ rdtsc โดยไม่มีเหตุผลดีมีเพียงหนึ่งรัน (!) ไม่มีอะไรตรวจสอบว่า ผลลัพธ์สุดท้ายนั้นถูกต้องจริง ๆ (ดังนั้นจึงเป็นเรื่องง่ายมากที่จะ "เพิ่มประสิทธิภาพ" ในสิ่งที่ผิดพลาดอย่างละเอียด) การทดสอบที่รวมนั้นเป็นพื้นฐานมาก (ไม่มีตัวเลขติดลบ?) และไม่มีอะไรจะหยุดคอมไพเลอร์ได้

ที่ถูกกล่าวว่ามันค่อนข้างง่ายที่จะปรับปรุงในโซลูชันเครือข่าย bitonic; เพียงแค่เปลี่ยนสิ่งที่ต่ำสุด / สูงสุด / SWAP เป็น

#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% สำหรับฉัน (Debian gcc 4.4.5 ที่มี -O2, amd64, Core i7)


ตกลงรหัสทดสอบไม่ดี อย่าลังเลที่จะปรับปรุง และใช่คุณสามารถใช้รหัสประกอบ ทำไมไม่ลองใช้รหัสอย่างเต็มที่ด้วยแอสเซมเบลอร์ x86 มันอาจจะพกพาได้น้อยลง แต่ทำไมต้องรำคาญ?
kriss

ขอบคุณสำหรับการสังเกตอาร์เรย์ล้นฉันแก้ไขมัน คนอื่นอาจไม่ได้สังเกตเพราะคลิกที่ลิงค์เพื่อคัดลอก / วางรหัสที่ไม่มีล้น
kriss

4
คุณไม่จำเป็นต้องมีแอสเซมเบลอร์ด้วยซ้ำ หากคุณทิ้งเคล็ดลับที่ชาญฉลาดทั้งหมดไว้ GCC จะจดจำลำดับและแทรกการเคลื่อนไหวตามเงื่อนไขให้คุณ: #define min (a, b) ((a <b)? a: b) #define max (a, b) ( (a <b)? b: a) #define SWAP (x, y) {int a = min (d [x], d [y]); int b = สูงสุด (d [x], d [y]); d [x] = a; d [y] = b; } มันอาจจะออกมาช้ากว่าตัวแปรอินไลน์ asm สักสองสามเปอร์เซ็นต์ แต่ก็ยากที่จะบอกได้ว่าขาดการเปรียบเทียบที่เหมาะสม
Steinar H. Gunderson

3
…และสุดท้ายหากตัวเลขของคุณลอยตัวและคุณไม่ต้องกังวลเกี่ยวกับ NaN ฯลฯ GCC สามารถแปลงเป็นคำแนะนำ minss / maxss SSE ซึ่งเร็วกว่า ~ 25% กำลังใจในการทำงาน: ปล่อยเล่ห์เหลี่ยม bitfiddling และปล่อยให้คอมไพเลอร์ทำงานได้ :-)
Steinar H. Gunderson

13

ในขณะที่ฉันชอบมาโคร swap ที่มีให้:

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

เรารับทราบว่า min และ max ทำงานอย่างไรและดึงนิพจน์ย่อยทั่วไปอย่างชัดเจน สิ่งนี้จะช่วยกำจัด min และ max macros อย่างสมบูรณ์


นั่นทำให้พวกมันถอยหลังสังเกตว่า d [y] ได้ค่าสูงสุดซึ่งก็คือ x ^ (นิพจน์ทั่วไป)
Kevin Stock

ฉันสังเกตเห็นสิ่งเดียวกัน; ฉันคิดว่าการใช้งานของคุณจะถูกต้องตามที่คุณต้องการd[x]แทนที่จะเป็นx(แบบเดียวกันy) และd[y] < d[x]เพื่อความไม่เท่าเทียมกันที่นี่ (ใช่แตกต่างจากรหัสต่ำสุด / สูงสุด)
ไทเลอร์

ฉันลองใช้ swap ของคุณแล้ว แต่การเพิ่มประสิทธิภาพในพื้นที่มีผลกระทบในเชิงลบในระดับที่สูงขึ้น และผลลัพธ์จะช้ากว่าการแลกเปลี่ยนอื่น แต่อย่างที่คุณเห็นด้วยโซลูชั่นใหม่ที่เสนอนั้นมีประสิทธิภาพมากมายจริง ๆ เพื่อเพิ่มประสิทธิภาพการแลกเปลี่ยน
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 นี้เร็วขึ้นด้วย GCC และ Clang

คอมไพเลอร์ยังทำงานที่ยอดเยี่ยมในการจัดสรรการลงทะเบียนและการวิเคราะห์นามแฝงย้าย d [x] อย่างมีประสิทธิภาพลงในตัวแปรเฉพาะที่ล่วงหน้าและคัดลอกกลับไปที่หน่วยความจำในตอนท้าย ในความเป็นจริงพวกเขาทำได้ดียิ่งกว่าถ้าคุณทำงานกับตัวแปรท้องถิ่นทั้งหมด (เช่นd0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3], d4 = d[4], d5 = d[5] ) ฉันกำลังเขียนสิ่งนี้เพราะคุณกำลังสมมติว่าการเพิ่มประสิทธิภาพที่แข็งแกร่งและยังพยายามที่จะเอาชนะผู้แปลใน min / max :)

ยังไงก็ตามฉันลอง Clang และ GCC พวกเขาทำการปรับให้เหมาะสมเหมือนกัน แต่เนื่องจากความแตกต่างของการจัดตารางเวลาทั้งสองมีการเปลี่ยนแปลงบางอย่างในผลลัพธ์จึงไม่สามารถพูดได้จริงซึ่งเร็วกว่าหรือช้ากว่า GCC เร็วขึ้นในเครือข่ายการเรียงลำดับเสียงดังกราวในประเภทกำลังสอง

เพื่อความสมบูรณ์การเรียงฟองที่ไม่ได้ควบคุมและการเรียงลำดับก็สามารถทำได้เช่นกัน นี่คือการจัดเรียงฟอง:

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

การเรียงลำดับการแทรกนี้เร็วกว่าของ Daniel Stutzbach และดีเป็นพิเศษสำหรับ GPU หรือคอมพิวเตอร์ที่มีการคาดการณ์ล่วงหน้าเพราะ ITER สามารถทำได้ด้วย 3 คำสั่งเท่านั้น (เทียบกับ 4 สำหรับ SWAP) ตัวอย่างเช่นนี่คือ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 swaps เทียบกับ 15 การวนซ้ำยอด 4 คำแนะนำ / สลับกับ 3 คำแนะนำ / ซ้ำ) แน่นอนว่าการเรียงลำดับฟองช้ากว่า แต่จะไม่เป็นจริงเมื่อขนาดเพิ่มขึ้นเนื่องจากการเรียงลำดับการแทรกคือ O (n ^ 2) ในขณะที่เครือข่ายการเรียงลำดับเป็น O (n log n)


1
ที่เกี่ยวข้องมากขึ้นหรือน้อยลง: ฉันส่งรายงานไปยัง GCC เพื่อให้สามารถใช้การเพิ่มประสิทธิภาพได้โดยตรงในคอมไพเลอร์ ไม่แน่ใจว่ามันจะทำ แต่อย่างน้อยคุณก็สามารถทำตามวิธีการที่มันวิวัฒนาการ
Morwenn

11

ฉันย้ายชุดทดสอบไปยังเครื่องสถาปัตยกรรม PPC ที่ฉันไม่สามารถระบุได้ (ไม่ต้องแตะรหัสเพียงเพิ่มการทดสอบซ้ำใช้ 8 กรณีทดสอบเพื่อหลีกเลี่ยงผลลัพธ์ที่ก่อมลพิษด้วย mods และแทนที่ x86 เฉพาะ rdtsc):

โทรโดยตรงไปยังฟังก์ชั่นห้องสมุด qsort : 101

การใช้งานแบบไร้เดียงสา (เรียงลำดับการแทรก) : 299

เรียงลำดับการแทรก (Daniel Stutzbach) : 108

แทรกการเรียงลำดับ Unrolled : 51

เครือข่ายคัดแยก (Daniel Stutzbach) : 26

เครือข่ายการเรียงลำดับ (Paul R) : 85

การเรียงลำดับเครือข่าย 12 ที่มีการสลับที่รวดเร็ว : 117

เครือข่ายการเรียงลำดับ 12 Swap เรียงลำดับใหม่ : 116

อันดับสั่งซื้อ : 56


1
น่าสนใจจริงๆ. ดูเหมือนว่าการแลกเปลี่ยนแบบไม่มีสาขาเป็นแนวคิดที่ไม่ดีสำหรับ PPC มันอาจเป็นผลที่เกี่ยวข้องกับคอมไพเลอร์ ใช้อันไหน
kriss

มันเป็นสาขาของคอมไพเลอร์ gcc - min, max logic อาจไม่แตกกิ่ง - ฉันจะตรวจสอบการถอดและแจ้งให้คุณทราบ, แต่ถ้าคอมไพเลอร์ฉลาดพอรวมถึงบางสิ่งบางอย่างเช่น x <y โดยไม่มีถ้ายังกลายเป็นสาขา - บน x86 / x64 คำสั่ง CMOV อาจหลีกเลี่ยงสิ่งนี้ แต่ไม่มีคำสั่งดังกล่าวสำหรับค่าจุดคงที่บน PPC เพียงลอย พรุ่งนี้ฉันอาจจะตะลุยกับเรื่องนี้และบอกให้คุณรู้ - ฉันจำได้ว่ามี winless ขั้นต่ำ / สูงสุดในแหล่ง Winamp AVS ที่เรียบง่ายกว่ามาก แต่ iirc มันเหมาะสำหรับการลอยเท่านั้น - แต่อาจเป็นการเริ่มต้นที่ดี
jheriko

4
นี่คือสาขา min max / สำหรับ PPC 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 ได้รับค่าสูงสุด ควรกำหนดเวลาด้วยมืออย่างเหมาะสม ฉันพบกับ GNU superoptimizer เริ่มต้นจาก 4 คำแนะนำขั้นต่ำและลำดับสูงสุดและค้นหาด้วยตนเองสำหรับสองที่สามารถรวมกัน สำหรับอินพุตที่มีลายเซ็นคุณสามารถเพิ่ม 0x80000000 ให้กับองค์ประกอบทั้งหมดที่จุดเริ่มต้นและลบออกอีกครั้งในตอนท้ายจากนั้นทำงานราวกับว่าพวกเขาไม่ได้ลงชื่อ
เปาโลบอนซินี

7

การแลกเปลี่ยน XOR อาจมีประโยชน์ในการแลกเปลี่ยนของคุณ

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

ถ้าอาจทำให้เกิดความแตกต่างมากเกินไปในรหัสของคุณ แต่ถ้าคุณมีการรับประกันว่า ints ของคุณทั้งหมดจะไม่ซ้ำกันนี้อาจมีประโยชน์


1
xor swap ทำงานสำหรับค่าเท่ากันเช่นกัน ... x ^ = y ตั้งค่า x เป็น 0, y ^ = x ทำให้ y เป็น y (== x), x ^ = y ตั้งค่า x เป็น y
jheriko

11
เมื่อมันไม่ทำงานคือเวลาxและyชี้ไปที่ตำแหน่งเดียวกัน
ฮอบส์

อย่างไรก็ตามเมื่อใช้กับเครือข่ายการเรียงลำดับเราไม่เคยเรียกด้วย x และ y ที่ชี้ไปยังตำแหน่งเดียวกัน ยังคงมีวิธีการหลีกเลี่ยงการทดสอบที่ยิ่งใหญ่กว่าเพื่อให้ได้ผลเช่นเดียวกับการสลับแบบไร้สาขา ฉันมีความคิดที่จะทำให้สำเร็จ
kriss

5

รอคอยที่จะลองทำสิ่งนี้และเรียนรู้จากตัวอย่างเหล่านี้ แต่ก่อนอื่นให้กำหนดเวลาจาก Powerbook PPC 1.5 GHz ของฉัน G4 w / 1 GB DDR RAM (ฉันยืมตัวจับเวลาคล้าย rdtsc ที่คล้ายกันสำหรับ PPC จากhttp://www.mcs.anl.gov/~kazutomo/rdtsc.htmlสำหรับการกำหนดเวลา) ฉันรันโปรแกรมสองสามครั้งและผลลัพธ์ที่แน่นอนแตกต่างกันไป แต่อย่างสม่ำเสมอ การทดสอบที่เร็วที่สุดคือ "Sortion Sort (Daniel Stutzbach)" โดยที่ "Sortion Sort Unrolled" เป็นวินาทีปิด

นี่เป็นชุดล่าสุด:

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

นี่คือการมีส่วนร่วมของฉันในหัวข้อนี้: shellsort 1, 4 shell ที่ได้รับการปรับปรุงสำหรับเวกเตอร์ int (สมาชิก valp) 6 สมาชิกที่มีค่าที่ไม่ซ้ำกัน

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

บนแล็ปท็อป HP dv7-3010so ของฉันกับ Athlon M300 แบบ Dual-Core ที่ 2 Ghz (หน่วยความจำ DDR2) มันทำงานในรอบนาฬิกา 165 ครั้ง นี่คือค่าเฉลี่ยที่คำนวณจากการจับเวลาทุกลำดับที่ไม่ซ้ำกัน (6! / 720 ในทั้งหมด) คอมไพล์เป็น Win32 โดยใช้ OpenWatcom 1.8 การวนซ้ำเป็นการเรียงลำดับการแทรกและมีความยาว 16 คำสั่ง / 37 ไบต์

ฉันไม่ได้มีสภาพแวดล้อม 64 บิตที่จะรวบรวม


ดี ฉันจะเพิ่มไปยังเว็บไซต์อีกต่อไปแบบทดสอบอีกต่อไป
kriss

3

หากการเรียงลำดับการแทรกมีการแข่งขันที่นี่ฉันแนะนำให้ลองใช้ shellsort ฉันเกรงว่าองค์ประกอบ 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 องค์ประกอบใครจะรู้ ...

ตามวิกิพีเดียสิ่งนี้สามารถใช้ร่วมกับเครือข่ายการเรียงลำดับ: Pratt, V (1979) Shellsort และเครือข่ายการเรียงลำดับ (วิทยานิพนธ์ที่โดดเด่นในด้านวิทยาศาสตร์คอมพิวเตอร์) พวงมาลัย. ไอ 0-824-04406-1


อย่าลังเลที่จะเสนอการดำเนินการบางอย่าง :-)
kriss

เพิ่มข้อเสนอแล้ว เพลิดเพลินไปกับข้อบกพร่อง
gcp

3

ฉันรู้ว่าฉันสายเกินไป แต่ฉันสนใจที่จะทดลองใช้โซลูชันที่แตกต่างกัน ก่อนอื่นฉันล้างข้อมูลที่วางทำให้คอมไพล์แล้ววางลงในที่เก็บ ฉันยังคงวิธีแก้ปัญหาที่ไม่พึงประสงค์ไว้เป็นทางตันเพื่อที่คนอื่นจะไม่ลอง ในบรรดานี่คือทางออกแรกของฉันซึ่งพยายามทำให้แน่ใจว่า x1> x2 คำนวณครั้งเดียว หลังจากการปรับให้เหมาะสมแล้วจะไม่เร็วกว่าเวอร์ชั่นอื่น ๆ ที่เรียบง่าย

ฉันเพิ่มเวอร์ชันวนลูปของการเรียงลำดับอันดับเนื่องจากแอปพลิเคชันของฉันเองของการศึกษานี้มีไว้สำหรับเรียงลำดับ 2-8 รายการดังนั้นเนื่องจากมีจำนวนอาร์กิวเมนต์ที่ผันแปรจึงจำเป็นต้องวนซ้ำ นี่คือเหตุผลที่ฉันไม่สนใจโซลูชั่นเครือข่ายการเรียงลำดับ

รหัสทดสอบไม่ได้ทดสอบว่ามีการจัดการรายการที่ซ้ำกันอย่างถูกต้องดังนั้นในขณะที่โซลูชันที่มีอยู่ทั้งหมดถูกต้องฉันได้เพิ่มเคสพิเศษลงในรหัสทดสอบเพื่อให้แน่ใจว่ามีการจัดการรายการที่ซ้ำกันอย่างถูกต้อง

จากนั้นฉันเขียนเรียงลำดับการแทรกที่อยู่ในการลงทะเบียน AVX ทั้งหมด บนเครื่องของฉันเร็วกว่าการแทรกประเภทอื่น 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 สิ่งนี้ตรงกับความเร็วของโซลูชันลำดับอื่น ๆ แต่ไม่เร็วกว่า ปัญหาที่นี่คือฉันสามารถคำนวณดัชนีด้วย 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];
}

repo สามารถพบได้ที่นี่: https://github.com/eyepatchParrot/sort6/


1
คุณสามารถใช้vmovmskpsกับเวกเตอร์จำนวนเต็ม (ด้วยการร่ายเพื่อรักษาความสุขภายใน), หลีกเลี่ยงความจำเป็นในการเลื่อนบิตไบค์ ( ffs) ไปทางขวา
Peter Cordes

1
คุณสามารถเพิ่มเงื่อนไข 1 ขึ้นอยู่กับcmpgtผลจากการลบset1(1)มันแทนการกำบังด้วย เช่นindex = _mm256_sub_epi32(index, gt)ทำindex -= -1 or 0;
Peter Cordes

1
eq = _mm256_insert_epi32(eq, 0, I)ไม่ใช่วิธีที่มีประสิทธิภาพในการทำให้องค์ประกอบเป็นศูนย์ถ้าคอมไพล์ตามที่เขียน (โดยเฉพาะอย่างยิ่งสำหรับองค์ประกอบที่อยู่นอกระดับต่ำ 4 เนื่องจากvpinsrdมีเฉพาะที่ปลายทาง XMM เท่านั้นดัชนีที่สูงกว่า 3 จะต้องถูกจำลอง) แทน_mm256_blend_epi32( vpblendd) ด้วยเวกเตอร์ที่มีศูนย์ vpblenddเป็นคำสั่งแบบ uop เดียวที่รันบนพอร์ตใด ๆ กับการสลับที่ต้องการพอร์ต 5 บน Intel CPUs ( agner.org/optimize )
Peter Cordes

1
นอกจากนี้คุณอาจพิจารณาสร้างrotเวกเตอร์ที่มี shuffles ที่แตกต่างจากแหล่งเดียวกันหรืออย่างน้อยก็รัน 2 dep chain ในขนานที่คุณใช้สลับกันแทนที่จะเป็นหนึ่ง dep dep เดียวผ่าน shuffle ข้ามเลน (3 latency รอบ) นั่นจะเพิ่ม ILP ภายในการเรียงลำดับเดียว 2 dep chains จำกัด จำนวนค่าคงที่ของเวกเตอร์เป็นจำนวนที่สมเหตุสมผลเพียง 2: 1 สำหรับการหมุนหนึ่งครั้งและอีกหนึ่งสำหรับการหมุน 2 ขั้นตอนรวมกัน
Peter Cordes

2

คำถามนี้ค่อนข้างเก่า แต่จริง ๆ แล้วฉันต้องแก้ปัญหาเดียวกันในวันนี้: agorithms ที่รวดเร็วในการเรียงลำดับอาร์เรย์ขนาดเล็ก ฉันคิดว่ามันเป็นความคิดที่ดีที่จะแบ่งปันความรู้ของฉัน ในขณะที่ฉันเริ่มต้นครั้งแรกโดยใช้เครือข่ายการเรียงลำดับในที่สุดฉันก็สามารถหาอัลกอริธึมอื่น ๆ ซึ่งจำนวนการเปรียบเทียบทั้งหมดที่ดำเนินการเพื่อเรียงลำดับการเรียงลำดับทุกค่า 6 มีขนาดเล็กกว่าเมื่อเทียบกับเครือข่ายการเรียงลำดับ ฉันไม่ได้นับจำนวนสว็อป; ฉันคาดหวังว่ามันจะเทียบเท่ากันคร่าวๆ (อาจจะสูงขึ้นเล็กน้อยในบางครั้ง)

อัลกอริทึมsort6ใช้อัลกอริทึมsort4ที่ใช้อัลกอริทึมsort3ที่ใช้อัลกอริทึมนี่คือการนำไปใช้ในรูปแบบ C ++ บางรูปแบบ (ต้นฉบับเป็นเทมเพลตที่หนักมากเพื่อให้สามารถทำงานกับตัววนซ้ำแบบเข้าถึงได้แบบสุ่มและฟังก์ชั่นการเปรียบเทียบที่เหมาะสม)

จัดเรียง 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 ค่า

หนึ่งนี้ใช้รุ่นคลี่ของสิ่งที่ฉันเรียกว่าจัดเรียงแทรกคู่ ชื่อนั้นไม่ดี แต่มันค่อนข้างจะอธิบายนี่คือวิธีการ:

  • เรียงลำดับทุกอย่างยกเว้นองค์ประกอบแรกและสุดท้ายของอาร์เรย์
  • สลับรายการแรกและองค์ประกอบของอาร์เรย์ถ้ารายการแรกมากกว่ารายการสุดท้าย
  • แทรกองค์ประกอบแรกลงในลำดับที่เรียงจากด้านหน้าจากนั้นองค์ประกอบสุดท้ายจากด้านหลัง

หลังจากการสลับองค์ประกอบแรกจะเล็กกว่าครั้งสุดท้ายเสมอซึ่งหมายความว่าเมื่อแทรกลงในลำดับที่เรียงลำดับจะไม่มีการเปรียบเทียบ N มากกว่าที่จะแทรกองค์ประกอบทั้งสองในกรณีที่เลวร้ายที่สุดตัวอย่างเช่นหาก องค์ประกอบแรกถูกแทรกในตำแหน่งที่ 3 จากนั้นองค์ประกอบสุดท้ายจะไม่สามารถแทรกต่ำกว่าตำแหน่งที่ 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 ในกรณีที่แย่ที่สุด

ฉันหวังว่าสิ่งนี้จะช่วยได้แม้ว่าคำถามนี้อาจไม่ได้แสดงถึงปัญหาที่แท้จริงอีกต่อไป :)

แก้ไข:หลังจากวางไว้ในเกณฑ์มาตรฐานที่กำหนดไว้แล้วมันจะช้ากว่าตัวเลือกที่น่าสนใจส่วนใหญ่ มันมีแนวโน้มที่จะทำงานได้ดีกว่าการแทรกส่วนแทรกที่ยังไม่ได้หมุน แต่นั่นก็ค่อนข้างดี โดยทั่วไปมันไม่ได้เรียงลำดับที่ดีที่สุดสำหรับจำนวนเต็ม แต่อาจน่าสนใจสำหรับประเภทที่มีการเปรียบเทียบราคาแพง


เหล่านี้เป็นสิ่งที่ดี เมื่อปัญหาได้รับการแก้ไขมีอายุหลายสิบปีอาจจะเหมือนการเขียนโปรแกรม C ซึ่งคำถามนี้มีมาเกือบ 5 ปีแล้วดูเหมือนจะไม่เกี่ยวข้องกันมากนัก
kriss

คุณควรดูวิธีการตั้งเวลาคำตอบอื่น ๆ ประเด็นก็คือด้วยชุดข้อมูลขนาดเล็กเช่นการเปรียบเทียบการนับหรือแม้กระทั่งการเปรียบเทียบและการแลกเปลี่ยนไม่ได้พูดเร็วเท่าไหร่อัลกอริทึม (โดยทั่วไปการเรียงลำดับ 6 ints เสมอ O (1) เพราะ O (6 * 6) เป็น O (1) วิธีที่เร็วที่สุดของโซลูชันที่เสนอก่อนหน้านี้คือการค้นหาตำแหน่งของแต่ละค่าทันทีโดยใช้การเปรียบเทียบขนาดใหญ่ (โดย RexKerr)
kriss

@kriss มันเร็วที่สุดตอนนี้เหรอ? จากการอ่านผลลัพธ์วิธีการเรียงลำดับเครือข่ายเป็นวิธีที่เร็วที่สุดแย่มาก มันเป็นความจริงที่โซลูชันของฉันมาจากไลบรารีทั่วไปของฉันและฉันไม่ได้ทำการเปรียบเทียบจำนวนเต็มเสมอและไม่ได้ใช้operator<สำหรับการเปรียบเทียบเสมอไป นอกเหนือจากการนับเปรียบเทียบและแลกเปลี่ยนอย่างมีวัตถุประสงค์แล้วฉันยังตั้งเวลาอัลกอริทึมของฉัน วิธีนี้เป็นวิธีทั่วไปที่เร็วที่สุด แต่ฉันพลาด @ RexKerr ไปอย่างหนึ่ง ลองดู :)
Morwenn

การแก้ปัญหาโดย RexKerr (ลำดับการสั่งซื้อ) กลายเป็นเร็วที่สุดในสถาปัตยกรรม X86 เนื่องจากคอมไพเลอร์ gcc 4.2.3 (และ gcc 4.9 กลายเป็นเร็วกว่าอันดับสองเกือบสองเท่า) แต่มันขึ้นอยู่กับการเพิ่มประสิทธิภาพของคอมไพเลอร์และอาจไม่เป็นจริงสำหรับสถาปัตยกรรมอื่น ๆ
kriss

@ Kriss นั่นน่าสนใจที่จะรู้ -O3และฉันจะแตกต่างแน่นอนมากขึ้นอีกครั้งกับ ฉันเดาว่าฉันจะนำกลยุทธ์อื่นมาใช้กับไลบรารีการเรียงลำดับของฉัน: การนำเสนออัลกอริทึมสามชนิดเพื่อให้มีการเปรียบเทียบจำนวนต่ำจำนวนการสลับน้อยหรือประสิทธิภาพที่ดีที่สุด อย่างน้อยสิ่งที่เกิดขึ้นจะโปร่งใสสำหรับผู้อ่าน ขอบคุณสำหรับข้อมูลเชิงลึกของคุณ :)
Morwenn

1

ฉันเชื่อว่าคำถามของคุณมีสองส่วน

  • สิ่งแรกคือการกำหนดอัลกอริทึมที่เหมาะสมที่สุด สิ่งนี้ทำ - อย่างน้อยก็ในกรณีนี้ - โดยการวนลูปผ่านการสั่งซื้อที่เป็นไปได้ (มีไม่มาก) ซึ่งช่วยให้คุณคำนวณนาที, สูงสุด, เฉลี่ยและค่าเบี่ยงเบนมาตรฐานของการเปรียบเทียบและการแลกเปลี่ยนที่แน่นอน มีรองชนะเลิศหรือสองมีประโยชน์เช่นกัน
  • ประการที่สองคือการเพิ่มประสิทธิภาพอัลกอริทึม มีหลายสิ่งที่สามารถทำได้เพื่อแปลงตัวอย่างโค้ดตำราเรียนให้เป็นค่าเฉลี่ยและอัลกอริธึมในชีวิตจริง หากคุณรู้ว่าอัลกอริทึมไม่สามารถปรับให้เหมาะสมกับขอบเขตที่ต้องการให้ลองใช้ตัวรองอันดับ

ฉันจะไม่กังวลมากเกินไปเกี่ยวกับการล้างท่อ (สมมติว่า x86 ปัจจุบัน): การทำนายสาขามาไกล สิ่งที่ฉันกังวลเกี่ยวกับการทำให้แน่ใจว่ารหัสและข้อมูลในแคชหนึ่งบรรทัดแต่ละบรรทัด (อาจเป็นสองรหัส) เมื่อมีการดึงข้อมูลค่า Latencies ต่ำซึ่งจะชดเชยแผงลอยใด ๆ มันก็หมายความว่าวงในของคุณอาจจะเป็นสิบคำสั่งหรือที่ถูกต้องที่ควรจะเป็น (มีสองวงภายในที่แตกต่างกันในอัลกอริทึมการเรียงลำดับของฉันพวกเขาคือ 10 คำแนะนำ / 22 ไบต์และ 9/22 ยาวตามลำดับ) สมมติว่ารหัสไม่ได้มี div ใด ๆ ที่คุณมั่นใจได้ว่ามันจะเร็วมาก


ฉันไม่แน่ใจว่าจะเข้าใจคำตอบของคุณอย่างไร ก่อนอื่นฉันไม่เข้าใจเลยว่าอัลกอริธึมที่คุณเสนอคืออะไร? และวิธีที่ดีที่สุดถ้าคุณต้องวนลูปการสั่งซื้อที่เป็นไปได้ 720 แบบ (คำตอบที่มีอยู่นั้นใช้เวลาน้อยกว่า 720 รอบ) หากคุณมีการป้อนข้อมูลแบบสุ่มฉันไม่สามารถจินตนาการได้ (แม้ในระดับทฤษฎี) วิธีการทำนายสาขาสามารถทำงานได้ดีกว่า 50-50 ยกเว้นว่าจะไม่สนใจข้อมูลอินพุตทั้งหมด โซลูชันที่ดีที่สุดที่เสนอไปแล้วมีแนวโน้มที่จะทำงานกับทั้งข้อมูลและรหัสในแคชอย่างสมบูรณ์ แต่บางทีฉันอาจเข้าใจผิดคำตอบของคุณอย่างสมบูรณ์ ต้องการแสดงรหัสหรือไม่
kriss

สิ่งที่ฉันหมายถึงคือมีเพียง 720 (6!) รวมกันที่แตกต่างกันของจำนวนเต็ม 6 จำนวนและโดยการเรียกใช้พวกเขาทั้งหมดผ่านอัลกอริทึมผู้สมัครคุณสามารถกำหนดสิ่งต่างๆมากมายที่ฉันกล่าวถึง - นั่นคือส่วนทางทฤษฎี ส่วนที่ใช้งานได้จริงคือการปรับจูนอัลกอรึทึมเพื่อให้ทำงานในรอบสัญญาณนาฬิกาน้อยที่สุดเท่าที่จะทำได้ จุดเริ่มต้นของฉันสำหรับการเรียงลำดับ 6 จำนวนเต็มเป็น 1, 4 shellsort ช่องว่างทั้ง 4 นั้นปูทางไปสู่การคาดคะเนสาขาที่ดีใน 1 ช่องว่าง
Olof Forshell

อันดับ 1, 4 shellsort สำหรับ 6! ชุดค่าผสมที่ไม่ซ้ำกัน (เริ่มต้นด้วย 012345 และลงท้ายด้วย 543210) จะมีกรณีที่ดีที่สุดของการเปรียบเทียบ 7 รายการและการแลกเปลี่ยน 0 รายการและการเปรียบเทียบที่แย่ที่สุดคือ 14 รายการเปรียบเทียบและ 10 รายการ กรณีเฉลี่ยประมาณ 11.14 การเปรียบเทียบและการแลกเปลี่ยน 6 ครั้ง
Olof Forshell

1
ฉันไม่ได้รับ "การกระจายแบบสุ่มปกติ" - สิ่งที่ฉันทำคือการทดสอบชุดค่าผสมที่เป็นไปได้ทั้งหมดและการกำหนดสถิติขั้นต่ำ / เฉลี่ย / สูงสุด Shellsort เป็นชุดของการแทรกที่เพิ่มขึ้นทีละน้อยเช่นการเพิ่มครั้งสุดท้าย - 1 - ทำงานได้น้อยกว่ามากหากมันถูกดำเนินการเพียงอย่างเดียวเช่นเดียวกับการเรียงลำดับการแทรกบริสุทธิ์ ในการนับเวลาของอัลกอริทึมของฉันต้องใช้วงจรนาฬิกาเฉลี่ย 406 รอบซึ่งรวมถึงการรวบรวมสถิติและทำการโทรสองครั้งเพื่อรูทีนการเรียงตามจริง - หนึ่งครั้งสำหรับแต่ละช่องว่าง นี่คือ Athlon M300 มือถือคอมไพเลอร์ OpenWatcom
Olof Forshell

1
"การแจกแจงแบบสุ่มปกติ" หมายถึงชุดค่าผสมของข้อมูลจริงทั้งหมดที่เรียงลำดับอาจไม่น่าจะเท่ากัน หากชุดค่าผสมทั้งหมดไม่น่าจะเท่ากันสถิติของคุณจะขาดเนื่องจากค่าเฉลี่ยต้องคำนึงถึงจำนวนครั้งที่การแจกแจงที่ให้นั้นมีโอกาสเกิดขึ้น สำหรับจำนวนนาฬิกาถ้าคุณลองใช้การเรียงลำดับนี้ (ลิงก์ที่ให้ไว้ด้านบน) และเรียกใช้บนระบบทดสอบของคุณเราจะมีพื้นฐานสำหรับการเปรียบเทียบและดูว่าคุณเลือกได้ดีเพียงใด
kriss

1

ฉันรู้ว่านี่เป็นคำถามเก่า

แต่ฉันเพิ่งเขียนวิธีแก้ปัญหาแบบอื่นที่ฉันต้องการแบ่งปัน
ไม่ใช้อะไรนอกจากซ้อนกัน MAX MAX

มันไม่เร็วอย่างที่มันใช้กับ 114 ของแต่ละอัน
สามารถลดลงเหลือ 75 สวย ๆ อย่าง -> pastebin

แต่แล้วมันก็ไม่ได้หมดจดสูงสุด

สิ่งที่อาจใช้ได้ผลคือทำ min / max สำหรับจำนวนเต็มหลาย ๆ ตัวพร้อมกันด้วย 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]);
  }
}

แก้ไข:
วิธีการแก้ปัญหาการจัดอันดับแรงบันดาลใจจาก Rex Kerr's เร็วกว่าความยุ่งเหยิงข้างต้น

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) ซ้ำสองสามครั้ง แต่การลดจำนวนมากจะยาก ฉันเพิ่มกรณีทดสอบของคุณ
PrincePolka

pmin / maxsw ทำงานกับจำนวนเต็มที่ลงนาม 16 บิต ( int16_t) แต่ฟังก์ชั่น C ของคุณอ้างว่ามันเรียงลำดับอาร์เรย์ของint(ซึ่งเป็น 32- บิตในการใช้งาน C ทั้งหมดที่สนับสนุนasmไวยากรณ์นั้น) คุณทดสอบด้วยจำนวนเต็มบวกเล็ก ๆ เท่านั้นที่มีเพียง 0 ในส่วนสูงของพวกเขา? มันจะทำงานได้ ... สำหรับintคุณต้องการ SSE4.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วนซ้ำในรหัสเวลาของฉัน

ฉันสงสัยว่า (จากความคิดเห็นเช่นนี้และที่อื่น ๆ ) ว่าการใช้ตัววนซ้ำให้การรับรอง g ++ บางอย่างเกี่ยวกับสิ่งที่สามารถและไม่สามารถเกิดขึ้นกับหน่วยความจำที่ตัววนซ้ำอ้างอิงถึงซึ่งไม่เช่นนั้นจะไม่มี ปรับการเรียงลำดับรหัสให้ดีขึ้น (เช่นด้วยพอยน์เตอร์คอมไพเลอร์ไม่สามารถแน่ใจได้ว่าพอยน์เตอร์ทั้งหมดชี้ไปยังตำแหน่งหน่วยความจำที่แตกต่างกัน) ถ้าฉันจำได้อย่างถูกต้องนี่เป็นส่วนหนึ่งของเหตุผลว่าทำไมอัลกอริธึม STL มากมายเช่นstd::sort()โดยทั่วไปมีประสิทธิภาพที่ดีอย่างลามก

ยิ่งไปกว่านั้นsort6_iterator()มีบางครั้ง (อีกครั้งขึ้นอยู่กับบริบทที่เรียกใช้ฟังก์ชัน) อย่างสม่ำเสมอดีกว่าโดยฟังก์ชันการเรียงลำดับต่อไปนี้ซึ่งคัดลอกข้อมูลลงในตัวแปรท้องถิ่นก่อนที่จะเรียงลำดับ 1โปรดทราบว่าเนื่องจากมีการกำหนดตัวแปรในเครื่องเพียง 6 ตัวหากตัวแปรท้องถิ่นเหล่านี้เป็นแบบดั้งเดิมดังนั้นพวกเขาจึงไม่เคยถูกเก็บไว้ใน RAM จริง ๆ และจะถูกเก็บไว้ในการลงทะเบียนของ CPU แทนจนกระทั่งสิ้นสุดการเรียกใช้ฟังก์ชั่น ฟังก์ชั่นอย่างรวดเร็ว (นอกจากนี้ยังช่วยให้คอมไพเลอร์รู้ว่าตัวแปรท้องถิ่นที่แตกต่างมีตำแหน่งที่แตกต่างในหน่วยความจำ)

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คำสำคัญฟังก์ชันเทมเพลตโดยทั่วไปจะได้รับการปรับให้เหมาะสมยิ่งขึ้นโดย gcc มากกว่าฟังก์ชั่นวานิลลา C (สิ่งนี้เกี่ยวข้องกับ gcc ที่จำเป็นต้องจัดการกับพอยน์เตอร์ของฟังก์ชั่นสำหรับฟังก์ชั่นวานิลลา

  1. ในขณะที่กำหนดเวลาฟังก์ชั่นการเรียงลำดับต่างๆฉันสังเกตเห็นว่าบริบท (เช่นโค้ดโดยรอบ) ซึ่งการเรียกไปยังฟังก์ชันการเรียงลำดับนั้นส่งผลกระทบอย่างมีนัยสำคัญต่อประสิทธิภาพซึ่งน่าจะเกิดจากฟังก์ชั่นอินไลน์แล้วปรับให้เหมาะสม ตัวอย่างเช่นหากโปรแกรมนั้นเรียบง่ายเพียงพอแล้วมักจะมีความแตกต่างในประสิทธิภาพระหว่างการส่งผ่านฟังก์ชั่นการเรียงตัวชี้เมื่อเทียบกับการผ่านมัน iterator; ไม่อย่างนั้นการใช้ตัววนซ้ำมักจะให้ประสิทธิภาพที่ดีกว่าอย่างเห็นได้ชัดและไม่เคย (อย่างน้อยก็ในประสบการณ์ของฉัน) ประสิทธิภาพที่แย่ลงอย่างเห็นได้ชัด ฉันสงสัยว่านี่อาจเป็นเพราะ g ++ สามารถเพิ่มประสิทธิภาพให้กับโค้ดที่เรียบง่ายทั่วโลก

0

ลองเรียงลำดับ 'ผสานรายการที่เรียงไว้' :) ใช้สองอาร์เรย์ เร็วที่สุดสำหรับอาร์เรย์ขนาดเล็กและขนาดใหญ่
หากคุณพูดคุยคุณจะตรวจสอบว่าส่วนแทรกอยู่ที่ใด ค่าที่มากกว่านั้นคุณไม่จำเป็นต้องทำการเปรียบเทียบ (cmp = ab> 0)
สำหรับตัวเลข 4 ตัวคุณสามารถใช้ระบบ 4-5 cmp (~ 4.6) หรือ 3-6 cmp (~ 4.9) การเรียงลำดับฟองใช้ 6 cmp (6) cmp มากมายสำหรับโค้ดขนาดใหญ่ที่ช้ากว่า
รหัสนี้ใช้ 5 cmp (ไม่ใช่ MSL sort):
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);}

อาจารย์ใหญ่ MSL 9 8 7 6 5 4 3 2 1 0 89 67 45 23 01 ... concat two sorted lists, list length = 1 6789 2345 01 ... concat two sorted lists, list length = 2 23456789 01 ... concat two sorted lists, list length = 4 0123456789 ... concat two sorted lists, list length = 8

รหัส js

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

จัดเรียง 4 รายการด้วยการใช้งาน cmp == 0 จำนวน cmp คือ ~ 4.34 (พื้นเมือง FF มี ~ 4.52) แต่ใช้เวลา 3x กว่าการรวมรายชื่อ แต่การดำเนินการ cmp ดีกว่าน้อยกว่าหากคุณมีตัวเลขขนาดใหญ่หรือข้อความใหญ่ แก้ไข: ซ่อมแซมข้อบกพร่อง

ทดสอบออนไลน์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
กรณีการใช้งานแตกต่างจากบริบทเริ่มต้นของคำถามเล็กน้อย ด้วยความยาวที่คงที่จะทำการจัดเรียงรายละเอียดที่สำคัญและการนับจำนวนสว็อปแลกเปลี่ยนไม่เพียงพอ ฉันจะไม่แปลกใจถ้ามันไม่ได้เป็นประเภทจริงที่จะใช้เวลา แต่สิ่งที่แตกต่างอย่างสิ้นเชิงเรียกแสง typeof () ใน init ฉันไม่ทราบวิธีการแสดงเวลานาฬิกาจริงโดยใช้ Javascript อาจจะมีโหนด?
kriss

0

บางทีฉันอาจamปลายไปงานเลี้ยง แต่อย่างน้อยการมีส่วนร่วมของฉันเป็นใหม่วิธีการ

  • รหัสควรจะถูกขีดเส้นใต้จริงๆ
  • แม้ว่าจะมีการเชื่อมโยงมีสาขามากเกินไป
  • ส่วนการวิเคราะห์นั้นเป็นพื้น O (N (N-1)) ซึ่งดูเหมือนว่าตกลงสำหรับ N = 6
  • รหัสอาจมีประสิทธิภาพมากกว่านี้ถ้าค่าใช้จ่ายswapสูงกว่า (ประมาณต้นทุนcompare)
  • ฉันเชื่อมั่นในฟังก์ชั่นคงที่ถูก inline
  • วิธีนี้เกี่ยวข้องกับการจัดอันดับ
    • แทนที่จะใช้อันดับจะใช้อันดับที่สัมพันธ์กัน (ออฟเซ็ต)
    • ผลรวมของอันดับเป็นศูนย์สำหรับทุก ๆรอบในกลุ่มการเปลี่ยนแปลงใด ๆ
    • แทนที่SWAP()องค์ประกอบสององค์ประกอบรอบจะถูกไล่ล่าต้องการเพียงหนึ่ง temp และหนึ่ง (register-> register) swap (new <- old)

อัปเดต: เปลี่ยนรหัสเล็กน้อยบางคนใช้คอมไพเลอร์ 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

@kriss en.wikipedia.org/wiki/Permutation_groupแน่นอนว่ามันไม่ใช่การจัดเรียงฟอง: รหัสตรวจจับรอบในการเปลี่ยนแปลงที่กำหนดและเดินรอบเหล่านี้วางแต่ละองค์ประกอบในสถานที่สุดท้าย wsort6()ฟังก์ชั่นสุดท้ายมีอินเตอร์เฟซที่ถูกต้อง
joop

@joop: ฉันไม่ดีไม่มีฟองเรียงแน่นอน ที่ถูกกล่าวในบริบทฉันยังคงคาดหวังว่ารหัสจะเลวร้ายยิ่งกว่าการใช้งานในปัจจุบันอื่น ๆ โดยวิธีการแก้ปัญหาการจัดอันดับการจัดอันดับที่ดีที่สุดเกี่ยวกับจำนวนของการแลกเปลี่ยนเพราะมันหาตำแหน่งสุดท้ายของทุกรายการโดยตรง มันก็ไม่ชัดเจนเช่นกันหากวอร์ตอร์ทำงานได้ดีเมื่อเราลบสมมุติฐานที่ว่าตัวเลขที่เรียงกันทั้งหมดนั้นแตกต่างกันเช่นที่นี่ เพื่อมาตรฐานรหัสเราควรรหัสติดตาม นอกจากนี้ฉันมักจะคอมไพล์ในคอมไพเลอร์ C ++, โค้ดไม่ทำงานเพราะ OP เรียกว่าตัวแปร "ใหม่" (และนั่นทำให้การเน้นไวยากรณ์ผิดเพี้ยน)
kriss

วิธีการที่อยู่ใกล้กับลำดับเท่านั้นที่ได้รับมอบหมายสุดท้ายจะทำในสถานที่ นอกเหนือจากการจัดอันดับo1..o5แล้วไม่จำเป็นสำหรับe[6]อาร์เรย์ชั่วคราวที่สอง และ: การคอมไพล์รหัส C บนคอมไพเลอร์ C ++ และโทษรหัสหรือไม่
joop

@greybeard: #includeขอบคุณฉันเพิ่มช่องว่างก่อน คงที่
ตัวแทนของ Wildplasser

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

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

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

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

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

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

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

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

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

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

    #undef wire
}

ไม่ว่าความเร็วจะเป็นยังไง ในการจัดเรียงลูปของคุณน่าสงสัย ดูเหมือนว่าพวกเขาจะไม่ทำงานถ้าเรามีศูนย์ในค่าเรียง
kriss

1
t [6] อาร์เรย์ถูกเตรียมใช้งานเป็น 0x0 ดังนั้นมันจึงไม่สำคัญว่าจะเขียนที่ไหนและถ้ามีค่าคีย์ 0x0
FranG

-1

ถ้าเป็นเพียง 6 องค์ประกอบและคุณสามารถใช้ประโยชน์จากความขนานต้องการลดการแยกตามเงื่อนไข ฯลฯ ทำไมคุณไม่สร้างชุดค่าผสมทั้งหมดและทดสอบการสั่งซื้อ ฉันอยากจะลองว่าในบางสถาปัตยกรรมมันก็ค่อนข้างเร็ว (ตราบใดที่คุณมีหน่วยความจำที่จัดสรรไว้ล่วงหน้า)


9
มีการสั่งซื้อ 720 รายการและเวอร์ชั่นเร็วนั้นอยู่ภายใต้ 100 รอบ แม้ว่าการขนานขนาดใหญ่จะสามารถใช้ประโยชน์ได้ในเวลาเพียงเล็กน้อย แต่ต้นทุนของการสร้างและการซิงโครไนซ์เธรดน่าจะเกินค่าใช้จ่ายในการเรียงลำดับอาร์เรย์บนแกนเดียวเท่านั้น
เควินสต็อก

-3

ต่อไปนี้เป็นวิธีการเรียงลำดับทั่วไปสามวิธีที่แสดงคลาสการจัดเรียงอัลกอริทึมที่แตกต่างกันสามคลาส:

Insertion Sort: Θ(n^2)

Heap Sort: Θ(n log n)

Count Sort: Θ(3n)

แต่ลองดูการสนทนาของ Stefan Nelsson เกี่ยวกับอัลกอริทึมการเรียงลำดับที่เร็วที่สุด? ที่ซึ่งเขาพูดถึงวิธีแก้ปัญหาที่ลงไปO(n log log n).. ตรวจสอบการใช้งานใน C

อัลกอริทึมการเรียงลำดับกึ่งเชิงเส้นนี้ถูกนำเสนอโดยกระดาษในปี 1995:

A. Andersson, T. Hagerup, S. Nilsson และ R. Raman การเรียงลำดับในเวลาเชิงเส้น? ในการประชุมวิชาการ ACM ประจำปีครั้งที่ 27 เรื่องทฤษฎีคอมพิวเตอร์หน้า 427-436, 1995


8
นี่เป็นเรื่องที่น่าสนใจ แต่อยู่ข้างๆ Big-intended มีวัตถุประสงค์เพื่อซ่อนปัจจัยคงที่และแสดงแนวโน้มเมื่อขนาดของปัญหา (n) ใหญ่ขึ้น ปัญหาที่นี่เป็นเรื่องเกี่ยวกับขนาดของปัญหาคงที่ (n = 6) และคำนึงถึงปัจจัยคงที่
kriss

@Kriss คุณถูกต้องการเปรียบเทียบของฉันเป็นแบบอะซิมโทติคดังนั้นการเปรียบเทียบในทางปฏิบัติจะแสดงว่ามันเร็วขึ้นหรือไม่สำหรับกรณีนั้น
Khaled.K

4
คุณไม่สามารถสรุปได้เพราะอัลกอริทึมที่แตกต่างกันซ่อนค่าคงที่แบบคูณ K ที่แตกต่างกัน (และค่าคงที่การเพิ่ม C) เช่น: k0, c0 สำหรับการเรียงลำดับการแทรก, k1, c1 สำหรับการจัดเรียงฮีปและอื่น ๆ ค่าคงที่ทั้งหมดนั้นแตกต่างกันจริง ๆ (คุณสามารถพูดในคำศัพท์ทางกายภาพว่าอัลกอริทึมแต่ละตัวมี "สัมประสิทธิ์แรงเสียดทาน" ของตัวเอง) คุณไม่สามารถสรุปได้ว่าอัลกอริทึมนั้นเร็วกว่าในกรณีนี้จริง ๆ
kriss
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.