วิธีที่เร็วที่สุดในการคำนวณลำดับของขนาดในชุดประกอบ x86


12

ภารกิจนั้นง่าย: เขียนชุดคำสั่งที่คำนวณลำดับความสำคัญของจำนวนเต็มโดยใช้วงจรนาฬิกาน้อยที่สุด

  • ลำดับความสำคัญถูกกำหนดให้เป็นไม่ได้log10log2
  • ช่วงของการป้อนข้อมูลที่ถูกต้องคือ0การรวม พฤติกรรมสำหรับอินพุตภายนอกช่วงนั้นไม่ได้กำหนดไว้1012
  • ค่าควรจะปัดเศษลงเลขที่ใกล้ที่สุด, มีข้อยกเว้นว่าการป้อนข้อมูลได้รับการส่งออกที่ควรจะเป็น0 0(คุณสามารถพิจารณาว่านี่เป็นตัวแทนที่ดีที่สุดของอินฟินิตี้ลบที่เป็นไปได้ในจำนวนเต็มที่ไม่ได้ลงชื่อ)
  • ต้องเป็นชุดประกอบ x86
  • จำนวนเต็มต้องเป็นค่ารันไทม์ไม่ใช่จำนวนเต็มแบบสแตติก / อินไลน์ ดังนั้นเราจึงไม่รู้ว่ามันคืออะไรในเวลารวบรวม
  • สมมติว่าคุณมีจำนวนเต็มโหลดลงทะเบียนแล้ว (แต่รวมถึงการตั้งค่าในการลงทะเบียนในคำตอบเพื่อความชัดเจน)
  • ไม่สามารถเรียกไลบรารีหรือฟังก์ชันภายนอกใด ๆ
  • อิสระในการใช้ใด ๆ ของคำแนะนำที่มีอยู่ในเอกสารของอินเทล
  • ไม่ค
  • สถาปัตยกรรม Intel Core ~ ​​7 ใด ๆ ก็ได้รับการยอมรับ (แสดงอยู่ในหน้า 10 ) Nehalem ในอุดมคติ (Intel Core i7)

คำตอบที่ชนะคือคำตอบที่ใช้รอบสัญญาณนาฬิกาน้อยที่สุดเท่าที่จะเป็นไปได้ นั่นคือมันสามารถมีการดำเนินงานมากที่สุดต่อวินาที นาฬิกาโดยประมาณสรุปวงจร are here: http://www.agner.org/optimize/instruction_tables.pdf การคำนวณรอบสัญญาณนาฬิกาสามารถเกิดขึ้นได้หลังจากโพสต์คำตอบแล้ว


อนุญาตให้ 'FYL2X' และคำแนะนำ FPU อื่น ๆ ได้หรือไม่
บาดเจ็บทางดิจิตอล

1
ผลลัพธ์ควรเป็นจำนวนเต็มหรือไม่ ถ้าเป็นเช่นนั้นควรปัดเศษอย่างไร?
บาดเจ็บทางดิจิตอล

3
ดังนั้นอินพุทและเอาท์พุทเป็นทั้งจำนวนเต็มใช่ไหม แต่อินพุตอาจมีค่ามากถึง 10 ^ 12 ดังนั้นมันจึงใหญ่เกินไปสำหรับบิต 32 บิต ดังนั้นเราจะสมมติว่าอินพุตจำนวนเต็ม 64 บิตหรือไม่
Paul R

3
เกณฑ์การชนะจะขึ้นอยู่กับจำนวนรอบสูงสุดหรือเฉลี่ย? และถ้าเป็นค่าเฉลี่ยการกระจายตัวของอินพุทคืออะไร?
Runer112

2
โปรเซสเซอร์เป้าหมายใด เอกสารที่เชื่อมโยงจะแสดงรายการกระบวนการที่แตกต่างกันมากกว่า 20 กระบวนการ (AMD, Intel, อื่น ๆ ) และมีความแตกต่างอย่างมากในเวลาแฝง
Digital Trauma

คำตอบ:


8

7 รอบเวลาคงที่

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

    .intel_syntax noprefix
    .globl  main
main:
    mov rdi, 1000000000000              #;your value here
    bsr rax, rdi
    movzx   eax, BYTE PTR maxdigits[1+rax]
    cmp rdi, QWORD PTR powers[0+eax*8]
    sbb al, 0
    ret
maxdigits:
    .byte   0
    .byte   0
    .byte   0
    .byte   0
    .byte   1
    .byte   1
    .byte   1
    .byte   2
    .byte   2
    .byte   2
    .byte   3
    .byte   3
    .byte   3
    .byte   3
    .byte   4
    .byte   4
    .byte   4
    .byte   5
    .byte   5
    .byte   5
    .byte   6
    .byte   6
    .byte   6
    .byte   6
    .byte   7
    .byte   7
    .byte   7
    .byte   8
    .byte   8
    .byte   8
    .byte   9
    .byte   9
    .byte   9
    .byte   9
    .byte   10
    .byte   10
    .byte   10
    .byte   11
    .byte   11
    .byte   11
    .byte   12
powers:
    .quad   0
    .quad   10
    .quad   100
    .quad   1000
    .quad   10000
    .quad   100000
    .quad   1000000
    .quad   10000000
    .quad   100000000
    .quad   1000000000
    .quad   10000000000
    .quad   100000000000
    .quad   1000000000000

คอมไพล์บน GCC 4.6.3 สำหรับ Ubuntu และส่งคืนค่าในรหัสออก

ฉันไม่มั่นใจที่จะตีความว่ารอบตารางสำหรับโปรเซสเซอร์ที่ทันสมัย ​​แต่ใช้วิธีการของ @ DigitalTrauma บนโปรเซสเซอร์ Nehalim ฉันได้รับ7หรือไม่

ins        uOps Latency
---        -    - 
BSR r,r  : 1    3
MOVZX r,m: 1    -
CMP m,r/i: 1    1 
SBB r,r/i: 2    2

โอ้ - เห็น DigitalTrauma's หลังจากที่ฉันเริ่มเขียนของฉัน ความคิดที่คล้ายกัน ใช้วิธีการนับของเขาฉันคิดว่าจะได้รับ 3,1,1,1 = 6 สำหรับ BSR, MOV, CMP, SBB
AShelly

ใช่ฉันคิดว่าคุณเต้นเป็นของฉัน เพียงแค่ไปที่จะแสดงก) ฉันไม่ได้เป็นโปรแกรมเมอร์ประกอบและข) การชุมนุมเป็นสิ่งที่ดีที่สุดที่เหลืออยู่คนเดียวโดยเราเพียงมนุษย์ ;-)
บาดเจ็บดิจิตอล

The integer must be a runtime value, not a static/inline integer. So we don't know what it is at compile time.
แมว

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

แทนที่ movzx eax ด้วย mov al eax 24 บิตอันดับแรกจะเป็นศูนย์อยู่แล้วดังนั้น zx จึงซ้ำซ้อน (และมีราคาแพง)
เตอร์เฟอร์รี

6

กรณีที่ดีที่สุด 8 รอบกรณีที่เลวร้ายที่สุด 12 รอบ

เนื่องจากมันยังไม่ชัดเจนในคำถามนี้ฉันจึงใช้วิธีนี้ในการคำนวณเวลาแฝงของ Ivy Bridge

วิธีการที่นี่คือการใช้คำสั่งbsr(สแกนบิตย้อนกลับ) เป็น log2 ของคนยากจน ผลที่ได้ถูกนำมาใช้เป็นดัชนีในตารางกระโดดซึ่งมีรายการสำหรับบิต 0 ถึง 42 ฉันกำลังสมมติว่ากำหนดว่าการดำเนินการกับข้อมูล 64bit เป็นสิ่งจำเป็นโดยปริยายแล้วใช้ของbsrการเรียนการสอนเป็น OK

ในอินพุตกรณีที่ดีที่สุดรายการ jumptable สามารถแมปbsrผลลัพธ์กับขนาดได้โดยตรง เช่นสำหรับอินพุตในช่วง 32-63 bsrผลลัพธ์จะเป็น 5 ซึ่งถูกแมปกับขนาด 1 ในกรณีนี้เส้นทางคำสั่งคือ:

Instruction    Latency

bsrq                 3
jmp                  2
movl                 1
jmp                  2

total                8

ในที่เลวร้ายที่สุดปัจจัยการผลิตกรณีที่bsrผลแผนที่จะถึงสองขนาดที่เป็นไปได้เพื่อให้รายการ jumptable ไม่อีกหนึ่งcmpเพื่อตรวจสอบว่าเข้าเป็น> 10 n เช่นสำหรับอินพุตในช่วง 64-127 bsrผลลัพธ์จะเป็น 6 รายการ jumptable ที่สอดคล้องกันจากนั้นตรวจสอบว่าอินพุต> 100 และตั้งค่าขนาดเอาต์พุตให้สอดคล้องกัน

นอกจากนี้สำหรับเส้นทางกรณีที่เลวร้ายที่สุดเรามีคำสั่ง mov เพิ่มเติมเพื่อโหลดค่า 64 บิตทันทีเพื่อใช้ในcmpดังนั้นเส้นทางคำแนะนำกรณีที่เลวร้ายที่สุดคือ:

Instruction    Latency

bsrq                 3
jmp                  2
movabsq              1
cmpq                 1
ja                   2
movl                 1
jmp                  2

total               12

นี่คือรหัส:

    /* Input is loaded in %rdi */
    bsrq    %rdi, %rax
    jmp *jumptable(,%rax,8)
.m0:
    movl    $0, %ecx
    jmp .end
.m0_1:
    cmpq    $9, %rdi
    ja  .m1
    movl    $0, %ecx
    jmp .end
.m1:
    movl    $1, %ecx
    jmp .end
.m1_2:
    cmpq    $99, %rdi
    ja  .m2
    movl    $1, %ecx
    jmp .end
.m2:
    movl    $2, %ecx
    jmp .end
.m2_3:
    cmpq    $999, %rdi
    ja  .m3
    movl    $2, %ecx
    jmp .end
.m3:
    movl    $3, %ecx
    jmp .end
.m3_4:
    cmpq    $9999, %rdi
    ja  .m4
    movl    $3, %ecx
    jmp .end
.m4:
    movl    $4, %ecx
    jmp .end
.m4_5:
    cmpq    $99999, %rdi
    ja  .m5
    movl    $4, %ecx
    jmp .end
.m5:
    movl    $5, %ecx
    jmp .end
.m5_6:
    cmpq    $999999, %rdi
    ja  .m6
    movl    $5, %ecx
    jmp .end
.m6:
    movl    $6, %ecx
    jmp .end
.m6_7:
    cmpq    $9999999, %rdi
    ja  .m7
    movl    $6, %ecx
    jmp .end
.m7:
    movl    $7, %ecx
    jmp .end
.m7_8:
    cmpq    $99999999, %rdi
    ja  .m8
    movl    $7, %ecx
    jmp .end
.m8:
    movl    $8, %ecx
    jmp .end
.m8_9:
    cmpq    $999999999, %rdi
    ja  .m9
    movl    $8, %ecx
    jmp .end
.m9:
    movl    $9, %ecx
    jmp .end
.m9_10:
    movabsq $9999999999, %rax
    cmpq    %rax, %rdi
    ja  .m10
    movl    $9, %ecx
    jmp .end
.m10:
    movl    $10, %ecx
    jmp .end
.m10_11:
    movabsq $99999999999, %rax
    cmpq    %rax, %rdi
    ja  .m11
    movl    $10, %ecx
    jmp .end
.m11:
    movl    $11, %ecx
    jmp .end
.m11_12:
    movabsq $999999999999, %rax
    cmpq    %rax, %rdi
    ja  .m12
    movl    $11, %ecx
    jmp .end
.m12:
    movl    $12, %ecx
    jmp .end

jumptable:
    .quad   .m0
    .quad   .m0
    .quad   .m0
    .quad   .m0_1
    .quad   .m1
    .quad   .m1
    .quad   .m1_2
    .quad   .m2
    .quad   .m2
    .quad   .m2_3
    .quad   .m3
    .quad   .m3
    .quad   .m3
    .quad   .m3_4
    .quad   .m4
    .quad   .m4
    .quad   .m4_5
    .quad   .m5
    .quad   .m5
    .quad   .m5_6
    .quad   .m6
    .quad   .m6
    .quad   .m6
    .quad   .m6_7
    .quad   .m7
    .quad   .m7
    .quad   .m7_8
    .quad   .m8
    .quad   .m8
    .quad   .m8_9
    .quad   .m9
    .quad   .m9
    .quad   .m9
    .quad   .m9_10
    .quad   .m10
    .quad   .m10
    .quad   .m10_11
    .quad   .m11
    .quad   .m11
    .quad   .m11_12
    .quad   .m12
    .quad   .m12
    .quad   .m12

.end:
/* output is given in %ecx */

นี้ส่วนใหญ่เกิดจากการส่งออกประกอบ GCC สำหรับหลักฐานของแนวคิดรหัส C ที่ผมเขียน หมายเหตุรหัส C ใช้ goto ที่คำนวณได้เพื่อสร้างตารางการกระโดด นอกจากนี้ยังใช้__builtin_clzll()gcc builtin ซึ่งคอมไพล์กับbsrคำสั่ง (บวกxor)


ฉันพิจารณาวิธีแก้ปัญหาหลายอย่างก่อนที่จะมาถึงที่นี่:

  • FYL2Xเพื่อคำนวณล็อกธรรมชาติจากนั้นFMULตามค่าคงที่ที่จำเป็น สิ่งนี้น่าจะชนะได้หากเป็นการประกวด [tag: คำแนะนำ: กอล์ฟ] แต่FYL2Xมีเวลาแฝงอยู่ที่ 90-106 สำหรับ Ivy bridge

  • การค้นหาไบนารี่แบบตายตัว นี่อาจเป็นการแข่งขัน - ฉันจะฝากไว้กับคนอื่นเพื่อนำไปใช้ :)

  • ทำตารางผลลัพธ์การค้นหาให้สมบูรณ์ นี่ฉันแน่ใจว่าเร็วที่สุดในทางทฤษฎี แต่จะต้องใช้ตารางการค้นหาขนาด 1TB - ยังไม่สามารถใช้งานได้ - อาจใช้เวลาไม่กี่ปีถ้ากฎของมัวร์ยังคงมีอยู่


หากจำเป็นฉันสามารถคำนวณเวลาแฝงเฉลี่ยสำหรับอินพุตที่ได้รับอนุญาตทั้งหมด
บาดเจ็บทางดิจิตอล

jmpและjccไม่มีความหน่วงแฝงมีเพียงต้นทุนในการรับส่งข้อมูล การคาดคะเนสาขา + การดำเนินการเก็งกำไรหมายถึงการพึ่งพาการควบคุมไม่ใช่ส่วนหนึ่งของเครือข่ายการพึ่งพาข้อมูล
ปีเตอร์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.