รหัสเครื่อง x86-16 (BubbleSort int8_t), 20 19 ไบต์
รหัสเครื่อง x86-64 / 32 (JumpDownSort) 21 19 ไบต์
การเปลี่ยนแปลง:
ขอบคุณ @ ped7g สำหรับแนวคิดlodsb
/ cmp [si],al
และวางสิ่งนั้นพร้อมกับตัวชี้เพิ่ม / รีเซ็ตที่ฉันได้ดู ไม่ต้องการal
/ ah
ให้เราใช้รหัสเดียวกันกับเลขจำนวนเต็มที่มากกว่า
อัลกอริทึมใหม่ (แต่เกี่ยวข้อง) การเปลี่ยนแปลงการใช้งานหลายอย่าง: Bubbly SelectionSort ช่วยให้มีการใช้งาน x86-64 น้อยกว่าสำหรับไบต์หรือ dwords; break-even บน x86-16 (ไบต์หรือคำ) หลีกเลี่ยงข้อผิดพลาดใน size = 1 ที่ BubbleSort ของฉันมี ดูด้านล่าง
ปรากฎว่า Sort Bubbly Selection ของฉันพร้อม swaps ทุกครั้งที่คุณพบ min ใหม่เป็นอัลกอริทึมที่รู้จักกันแล้ว JumpDown Sort มันถูกกล่าวถึงในBubble Sort: การวิเคราะห์ขั้นตอนวิธีทางโบราณคดี (เช่น Bubble Sort กลายเป็นที่นิยมแม้จะมีการดูด)
เรียงลำดับ 8 บิตลงนามจำนวนเต็มในสถานที่ (ไม่ได้ลงชื่อคือขนาดรหัสเดียวกันเพียงแค่เปลี่ยนjge
เป็นjae
) รายการซ้ำไม่ใช่ปัญหา เราสลับโดยใช้การหมุน 16 บิต 8 โดยมีปลายทางของหน่วยความจำ
เรียงลำดับฟองเพื่อประสิทธิภาพแต่ฉันได้อ่านมาแล้วว่ามันเป็นหนึ่งในขนาดที่เล็กที่สุดที่จะนำไปใช้กับรหัสเครื่อง เรื่องนี้ดูเหมือนจริงโดยเฉพาะเมื่อมีเทคนิคพิเศษสำหรับการแลกเปลี่ยนองค์ประกอบที่อยู่ติดกัน นี่เป็นข้อได้เปรียบเพียงอย่างเดียว แต่บางครั้ง (ในระบบฝังตัวในชีวิตจริง) ซึ่งมีประโยชน์มากพอที่จะใช้กับรายการที่สั้นมาก
ฉันละเว้นการยกเลิกก่อนเวลาโดยไม่มีสัญญาแลกเปลี่ยนผมมองข้ามเลิกจ้างเริ่มต้นในวันที่ไม่มีสัญญาแลกเปลี่ยนฉันใช้ BubbleSort loop ของ "Wikipedia" เพื่อหลีกเลี่ยงการดูn − 1
รายการสุดท้ายเมื่อเรียกใช้n
ครั้งที่ -th ดังนั้นตัวนับลูปด้านนอกจึงเป็นขอบเขตสูงสุดสำหรับลูปด้านใน
รายชื่อ NASM ( nasm -l /dev/stdout
) หรือแหล่งที่มาธรรมดา
2 address 16-bit bubblesort16_v2:
3 machine ;; inputs: pointer in ds:si, size in in cx
4 code ;; requires: DF=0 (cld)
5 bytes ;; clobbers: al, cx=0
6
7 00000000 49 dec cx ; cx = max valid index. (Inner loop stops 1 before cx, because it loads i and i+1).
8 .outer: ; do{
9 00000001 51 push cx ; cx = inner loop counter = i=max_unsorted_idx
10 .inner: ; do{
11 00000002 AC lodsb ; al = *p++
12 00000003 3804 cmp [si],al ; compare with *p (new one)
13 00000005 7D04 jge .noswap
14 00000007 C144FF08 rol word [si-1], 8 ; swap
15 .noswap:
16 0000000B E2F5 loop .inner ; } while(i < size);
17 0000000D 59 pop cx ; cx = outer loop counter
18 0000000E 29CE sub si,cx ; reset pointer to start of array
19 00000010 E2EF loop .outer ; } while(--size);
20 00000012 C3 ret
22 00000013 size = 0x13 = 19 bytes.
พุช / ป๊อปของ cx
วงรอบด้านในหมายความว่ามันทำงานโดยcx
= outer_cx ลงไปที่ 0
สังเกตได้ว่า rol r/m16, imm8
ไม่ใช่คำสั่ง 8086 มันถูกเพิ่มเข้ามาในภายหลัง (186 หรือ 286) แต่นี่ไม่ได้พยายามเป็นรหัส 8086 เพียงแค่ 16 บิต x86 ถ้า SSE4.1 phminposuw
ช่วยได้ฉันจะใช้มัน
เวอร์ชัน 32 บิตนี้ (ยังคงทำงานกับจำนวนเต็ม 8 บิต แต่ด้วยตัวชี้ / ตัวนับ 32 บิต) คือ 20 ไบต์ (คำนำหน้าของขนาดตัวถูกดำเนินการrol word [esi-1], 8
)
ข้อผิดพลาด: ขนาด = 1 ถือว่าเป็นขนาด = 65536 เพราะไม่มีอะไรหยุดเราจากการเข้าสู่ด้านนอกทำ / ในขณะที่มี cx = 0 (โดยปกติคุณจะใช้jcxz
สำหรับสิ่งนั้น) แต่โชคดีที่การเรียงลำดับ JumpDown ขนาด 19 ไบต์คือ 19 ไบต์และไม่มีปัญหานั้น
x86-16 ต้นฉบับรุ่น 20 ไบต์ (ไม่มีแนวคิดของ Ped7g) เว้นไว้เพื่อประหยัดพื้นที่ดูประวัติการแก้ไขพร้อมคำอธิบาย
ประสิทธิภาพ
การจัดเก็บ / โหลดซ้ำซ้อนบางส่วน (ในการหมุนของหน่วยความจำปลายทาง) ทำให้แผงลอยการส่งต่อร้านค้าบนซีพียู x86 รุ่นใหม่ (ยกเว้นอะตอมตามลำดับ) เมื่อค่าสูงกำลังเดือดปุด ๆ ขึ้นไปความล่าช้าพิเศษนี้เป็นส่วนหนึ่งของห่วงโซ่พึ่งพาห่วง เก็บ / โหลดซ้ำในสถานที่แรก (เช่น 5 รอบแฝงการส่งต่อร้านค้าบน Haswell) แต่แผงส่งต่อนำมันขึ้นไปเช่นมากขึ้น 13 รอบ การดำเนินการที่ไม่เป็นไปตามสั่งจะมีปัญหาในการซ่อนสิ่งนี้
ดูเพิ่มเติมที่: Stack Overflow: การเรียงลำดับฟองสำหรับการเรียงลำดับสตริงสำหรับเวอร์ชันของสิ่งนี้ด้วยการใช้งานที่คล้ายกัน แต่มีการเริ่มต้นเมื่อไม่จำเป็นต้องสลับ มันใช้xchg al, ah
/ mov [si], ax
สำหรับการแลกเปลี่ยนซึ่งมีความยาว 1 ไบต์และทำให้แผงลงทะเบียนบางส่วนบน CPU (แต่อาจยังดีกว่า memory-dst หมุนซึ่งต้องโหลดค่าอีกครั้ง) ความคิดเห็นของฉันมีคำแนะนำ ...
x86-64 / x86-32 JumpDown Sort, 19 ไบต์ (เรียงลำดับ int32_t)
เรียกได้จาก C โดยใช้ x86-64 System V แบบแผนการโทรเป็น
int bubblyselectionsort_int32(int dummy, int *array, int dummy, unsigned long size);
(return value = max (array []))
นี่คือhttps://en.wikipedia.org/wiki/Selection_sortแต่แทนที่จะจดจำตำแหน่งขององค์ประกอบนาทีที่สลับผู้สมัครในปัจจุบันลงในอาร์เรย์ เมื่อคุณพบค่าต่ำสุด (unsorted_region) แล้วให้เก็บไว้ที่ส่วนท้ายของพื้นที่ที่เรียงลำดับเช่นเรียงลำดับการเลือกปกติ สิ่งนี้จะขยายขอบเขตที่เรียงลำดับโดยหนึ่ง (ในรหัสให้rsi
ชี้ไปที่จุดสิ้นสุดจุดหนึ่งของส่วนที่เรียงแล้วเลื่อนไปข้างlodsd
หน้าแล้วmov [rsi-4], eax
เก็บค่า min กลับเข้าไป)
ชื่อกระโดดลงเรียงใช้ในฟองเรียง: การโบราณคดีขั้นตอนการวิเคราะห์ ฉันเดาว่าการเรียงลำดับของฉันเป็นประเภทการกระโดดแบบเร่งด่วนเพราะองค์ประกอบที่สูงกระโดดขึ้นไปปล่อยให้การเรียงลำดับด้านล่างไม่ใช่จุดสิ้นสุด
การออกแบบการแลกเปลี่ยนนี้นำไปสู่ส่วนที่ไม่เรียงลำดับของอาร์เรย์ที่เรียงลำดับแบบย้อนกลับซึ่งส่วนใหญ่นำไปสู่การแลกเปลี่ยนจำนวนมากในภายหลัง (เพราะคุณเริ่มจากผู้สมัครที่มีขนาดใหญ่และเห็นผู้สมัครที่ต่ำกว่าและต่ำกว่าอยู่เรื่อย ๆ ) คุณจึงสลับสับเปลี่ยนกันไปเรื่อย ๆ ) ฉันเรียกมันว่า "bubbly" แม้ว่ามันจะย้ายองค์ประกอบไปในทิศทางอื่น วิธีที่มันเคลื่อนย้ายองค์ประกอบต่าง ๆ ก็เหมือนกับการเรียงลำดับการแทรกถอยหลัง หากต้องการดูมันในทางปฏิบัติให้ใช้ GDB display (int[12])buf
ตั้งค่าเบรกพอยต์บนloop
คำสั่งภายในและใช้c
(ดำเนินการต่อ) กด Return เพื่อทำซ้ำ (คำสั่ง "display" ทำให้ GDB พิมพ์สถานะอาร์เรย์ทั้งหมดทุกครั้งที่เรากดเบรกพอยต์)
xchg
ด้วย mem มีlock
คำนำหน้าโดยนัยซึ่งทำให้ช้าลงเป็นพิเศษ อาจเป็นไปได้เกี่ยวกับลำดับของขนาดที่ช้ากว่าการแลกเปลี่ยนโหลด / ร้านค้าที่มีประสิทธิภาพ xchg m,r
คือหนึ่งต่อ 23c ทรูพุตบน Skylake แต่โหลด / store / mov ด้วย tmp reg สำหรับการแลกเปลี่ยนที่มีประสิทธิภาพ (reg, mem) สามารถเลื่อนองค์ประกอบหนึ่งต่อนาฬิกา มันอาจจะเป็นอัตราส่วนที่แย่กว่าใน CPU ของ AMD ที่loop
คำสั่งนั้นเร็วและจะไม่ทำให้เกิดปัญหาคอขวดภายในมากนัก แต่การขาดสาขาจะยังคงเป็นปัญหาคอขวดขนาดใหญ่เพราะการแลกเปลี่ยนนั้นเป็นเรื่องธรรมดา )
2 Address ;; hybrib Bubble Selection sort
3 machine bubblyselectionsort_int32: ;; working, 19 bytes. Same size for int32 or int8
4 code ;; input: pointer in rsi, count in rcx
5 bytes ;; returns: eax = max
6
7 ;dec ecx ; we avoid this by doing edi=esi *before* lodsb, so we do redundant compares
8 ; This lets us (re)enter the inner loop even for 1 element remaining.
9 .outer:
10 ; rsi pointing at the element that will receive min([rsi]..[rsi+rcx])
11 00000000 56 push rsi
12 00000001 5F pop rdi
13 ;mov edi, esi ; rdi = min-search pointer
14 00000002 AD lodsd
16 00000003 51 push rcx ; rcx = inner counter
17 .inner: ; do {
18 ; rdi points at next element to check
19 ; eax = candidate min
20 00000004 AF scasd ; cmp eax, [rdi++]
21 00000005 7E03 jle .notmin
22 00000007 8747FC xchg [rdi-4], eax ; exchange with new min.
23 .notmin:
24 0000000A E2F8 loop .inner ; } while(--inner);
26 ; swap min-position with sorted position
27 ; eax = min. If it's not [rsi-4], then [rsi-4] was exchanged into the array somewhere
28 0000000C 8946FC mov [rsi-4], eax
29 0000000F 59 pop rcx ; rcx = outer loop counter = unsorted elements left
30 00000010 E2EE loop .outer ; } while(--unsorted);
32 00000012 C3 ret
34 00000013 13 .size: db $ - bubblyselectionsort_int32
0x13 = 19 bytes long
รหัสขนาดเดียวกันสำหรับint8_t
ใช้งาน: ใช้lodsb
/ scasb
, AL
และเปลี่ยนไป[rsi/rdi-4]
-1
รหัสเครื่องเดียวกันทำงานในโหมด 32 บิตสำหรับองค์ประกอบ 8/32 บิต โหมด 16 บิตสำหรับองค์ประกอบ 8/16 บิตจะต้องสร้างขึ้นใหม่โดยมีการเปลี่ยนแปลงออฟเซ็ต (และโหมดการกำหนดแอดเดรส 16 บิตใช้การเข้ารหัสที่แตกต่างกัน) แต่ยังคง 19 ไบต์สำหรับทุกคน
มันหลีกเลี่ยงการเริ่มต้นdec ecx
ด้วยการเปรียบเทียบกับองค์ประกอบที่เพิ่งโหลดก่อนที่จะย้าย ในการวนซ้ำครั้งสุดท้ายของลูปด้านนอกมันจะโหลดอิลิเมนต์สุดท้ายตรวจสอบว่ามันน้อยกว่าตัวมันเองหรือไม่ สิ่งนี้ช่วยให้มันทำงานกับขนาด = 1 ที่ BubbleSort ของฉันล้มเหลว (ถือว่าเป็นขนาด = 65536)
ฉันทดสอบรุ่นนี้ (ใน GDB) โดยใช้ผู้โทรนี้: ลองออนไลน์! . คุณสามารถเรียกใช้งานได้จาก TIO แต่แน่นอนว่าไม่มีการดีบั๊กหรือการพิมพ์ ยังคงสิ่ง_start
ที่เรียกว่ามันออกด้วย exit-status = องค์ประกอบที่ใหญ่ที่สุด = 99 เพื่อให้คุณสามารถดูการทำงาน
[7 2 4 1] -> [4 2 3 1]
. นอกจากนี้รายการ CSV สามารถอยู่ในเครื่องหมายวงเล็บได้หรือไม่ นอกจากนี้รูปแบบการป้อนข้อมูลเฉพาะนั้นเหมาะสมมากสำหรับบางภาษาและไม่ดีสำหรับผู้อื่น สิ่งนี้ทำให้การแยกวิเคราะห์เป็นส่วนสำคัญสำหรับการส่งบางส่วนและไม่จำเป็นสำหรับผู้อื่น