รหัสเครื่อง x86-64 (การเรียกระบบ Linux): 78 ไบต์
RDTSCปั่นวน , Linuxsys_write
เรียกระบบ
x86-64 ไม่ได้ให้วิธีที่สะดวกในการค้นหาความถี่ "นาฬิกาอ้างอิง" ของ RDTSC ในขณะใช้งาน คุณสามารถอ่าน MSR (และทำการคำนวณตามนั้น)แต่ต้องใช้โหมดเคอร์เนลหรือรูท + เปิด/dev/cpu/%d/msr
ดังนั้นฉันตัดสินใจที่จะทำให้ความถี่เป็นค่าคงที่เวลาการสร้าง (ปรับFREQ_RDTSC
ตามความจำเป็น: ค่าคงที่ 32 บิตใด ๆ จะไม่เปลี่ยนขนาดของรหัสเครื่อง)
โปรดทราบว่า x86 CPUs เป็นเวลาหลายปีมีความถี่ RDTSC คงที่ดังนั้นจึงสามารถใช้งานเป็นแหล่งเวลาได้ ได้ตัวนับประสิทธิภาพรอบนาฬิกาหลักยกเว้นว่าคุณทำตามขั้นตอนเพื่อปิดการเปลี่ยนแปลงความถี่ (มีตัวนับที่สมบูรณ์แบบจริงสำหรับการนับรอบ CPU จริง) โดยปกติแล้วมันจะทำเครื่องหมายที่ความถี่สติกเกอร์เล็กน้อยเช่น 4.0GHz สำหรับ i7-6700k ของฉันโดยไม่คำนึงถึงเทอร์โบหรือการประหยัดพลังงาน อย่างไรก็ตามเวลารอไม่ว่างนี้ไม่ได้ขึ้นอยู่กับค่าเฉลี่ยของการโหลด (เช่นการวนรอบล่าช้าที่ปรับเทียบแล้ว) และยังไม่ไวต่อการประหยัดพลังงานของ CPU
รหัสนี้จะทำงานกับ x86 ใด ๆ ที่มีความถี่อ้างอิงต่ำกว่า 2 ^ 32 Hz หรือมากถึง ~ 4.29 GHz นอกเหนือจากนั้นการบันทึกเวลาต่ำ 32 รอบจะห่อหุ้มตลอดเวลาใน 1 วินาทีดังนั้นฉันต้องดูedx
ผลลัพธ์ 32 บิตสูงเช่นกัน
สรุป :
ดัน00:00:00\n
สแต็ค จากนั้นในวง:
sys_write
การเรียกระบบ
- ADC-loop เหนือตัวเลข (เริ่มต้นด้วยครั้งสุดท้าย) เพื่อเพิ่มเวลาโดย 1 การตัด / ดำเนินการจัดการด้วย
cmp
/ cmov
ด้วยผลลัพธ์ CF ที่ให้การพกพาสำหรับหลักถัดไป
rdtsc
และประหยัดเวลาเริ่มต้น
- หมุน
rdtsc
จนกระทั่งเดลต้าคือ> = ติ๊กต่อวินาทีของความถี่ RDTSC
รายชื่อ NASM:
1 Address ; mov %1, %2 ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
2 Machine code %macro MOVE 2
3 bytes push %2
4 pop %1
5 %endmacro
6
7 ; frequency as a build-time constant because there's no easy way detect it without root + system calls, or kernel mode.
8 FREQ_RDTSC equ 4000000000
9 global _start
10 _start:
11 00000000 6A0A push 0xa ; newline
12 00000002 48BB30303A30303A3030 mov rbx, "00:00:00"
13 0000000C 53 push rbx
14 ; rsp points to `00:00:00\n`
20
21 ; rbp = 0 (Linux process startup. push imm8 / pop is as short as LEA for small constants)
22 ; low byte of rbx = '0'
23 .print:
24 ; edx potentially holds garbage (from rdtsc)
25
26 0000000D 8D4501 lea eax, [rbp+1] ; __NR_write = 1
27 00000010 89C7 mov edi, eax ; fd = 1 = stdout
28 MOVE rsi, rsp
28 00000012 54 <1> push %2
28 00000013 5E <1> pop %1
29 00000014 8D5008 lea edx, [rax-1 + 9] ; len = 9 bytes.
30 00000017 0F05 syscall ; sys_write(1, buf, 9)
31
32 ;; increment counter string: least-significant digits are at high addresses (in printing order)
33 00000019 FD std ; so loop backwards from the end, wrapping each digit manually
34 0000001A 488D7E07 lea rdi, [rsi+7]
35 MOVE rsi, rdi
35 0000001E 57 <1> push %2
35 0000001F 5E <1> pop %1
36
37 ;; edx=9 from the system call
38 00000020 83C2FA add edx, -9 + 3 ; edx=3 and set CF (so the low digit of seconds will be incremented by the carry-in)
39 ;stc
40 .string_increment_60: ; do {
41 00000023 66B93902 mov cx, 0x0200 + '9' ; saves 1 byte vs. ecx.
42 ; cl = '9' = wrap limit for manual carry of low digit. ch = 2 = digit counter
43 .digitpair:
44 00000027 AC lodsb
45 00000028 1400 adc al, 0 ; carry-in = cmp from previous iteration; other instructions preserve CF
46 0000002A 38C1 cmp cl, al ; manual carry-out + wrapping at '9' or '5'
47 0000002C 0F42C3 cmovc eax, ebx ; bl = '0'. 1B shorter than JNC over a MOV al, '0'
48 0000002F AA stosb
49
50 00000030 8D49FC lea ecx, [rcx-4] ; '9' -> '5' for the tens digit, so we wrap at 59
51 00000033 FECD dec ch
52 00000035 75F0 jnz .digitpair
53 ; hours wrap from 59 to 00, so the max count is 59:59:59
54
55 00000037 AC lodsb ; skip the ":" separator
56 00000038 AA stosb ; and increment rdi by storing the byte back again. scasb would clobber CF
57
58 00000039 FFCA dec edx
59 0000003B 75E6 jnz .string_increment_60
60
61 ; busy-wait for 1 second. Note that time spent printing isn't counted, so error accumulates with a bias in one direction
62 0000003D 0F31 rdtsc ; looking only at the 32-bit low halves works as long as RDTSC freq < 2^32 = ~4.29GHz
63 0000003F 89C1 mov ecx, eax ; ecx = start
64 .spinwait:
65 ; pause
66 00000041 0F31 rdtsc ; edx:eax = reference cycles since boot
67 00000043 29C8 sub eax, ecx ; delta = now - start. This may wrap, but now we have the delta ready for a normal compare
68 00000045 3D00286BEE cmp eax, FREQ_RDTSC ; } while(delta < counts_per_second)
69 ; cmp eax, 40 ; fast count to test printing
70 0000004A 72F5 jb .spinwait
71
72 0000004C EBBF jmp .print
next address = 0x4E = size = 78 bytes.
บรรทัดเหล่าpause
คำแนะนำเพื่อประหยัดพลังงานอย่างมีนัยสำคัญ: ร้อนนี้หลักเพิ่มขึ้น ~ 15 องศาเซลเซียสโดยไม่ต้องpause
แต่เพียงโดย ~ 9 pause
ด้วย (บน Skylake ซึ่งpause
นอนประมาณ ~ 100 รอบแทนที่จะเป็น ~ 5 ฉันคิดว่ามันจะประหยัดได้มากกว่าหากrdtsc
ไม่ช้าเช่นกันดังนั้น CPU จึงไม่ได้ทำอะไรมากมาย)
รุ่น 32 บิตจะสั้นกว่าสองสามไบต์เช่นใช้เวอร์ชั่น 32 บิตเพื่อดันสตริง 00: 00: 00 \ n เริ่มต้น
16 ; mov ebx, "00:0"
17 ; push rbx
18 ; bswap ebx
19 ; mov dword [rsp+4], ebx ; in 32-bit mode, mov-imm / push / bswap / push would be 9 bytes vs. 11
และยังมีการใช้ dec edx
1 int 0x80
สายระบบ ABI จะไม่ใช้ ESI / EDI เพื่อให้การตั้งค่าลงทะเบียนสำหรับ syscall กับ lodsb / stosb อาจจะง่าย