x86-64 머신 코드 (Linux 시스템 호출) : 78 바이트
RDTSC 스핀 루프 타이밍, Linux sys_write
시스템 호출.
x86-64는 런타임에 RDTSC "기준 클록"주파수를 쿼리하는 편리한 방법을 제공하지 않습니다. MSR을 읽을 수는 있지만 (그것을 기반으로 계산을 수행 할 수는 있지만 ) 커널 모드 또는 root + opening이 필요 /dev/cpu/%d/msr
하므로 빈도를 빌드 타임 상수로 설정하기로 결정했습니다. ( FREQ_RDTSC
필요에 따라 조정 하십시오. 32 비트 상수는 기계 코드의 크기를 변경하지 않습니다)
몇 년 동안 x86 CPU는 RDTSC 주파수를 고정 시켰으므로 시간 소스로 사용할 수는 없습니다. 당신이 비활성화 주파수 변화에 조치를 취할하지 않는 한, 코어 클럭 사이클 성능 카운터. (실제 CPU 사이클을 계산하기위한 실제 성능 카운터가 있습니다.) 일반적으로 터보 또는 절전에 관계없이 공칭 스티커 주파수 (예 : i7-6700k의 경우 4.0GHz)에서 틱합니다. 어쨌든,이 통화 중 대기 타이밍은 (캘리브레이션 된 지연 루프처럼)로드 평균에 의존하지 않으며 CPU 절전에도 민감하지 않습니다.
이 코드는 기준 주파수가 2 ^ 32Hz 미만, 즉 ~ 4.29GHz 인 모든 x86에서 작동합니다. 그 외에도 타임 스탬프의 하위 32 개가 1 초 안에 줄 바꿈되므로edx
. 따라서 결과 상위 32 비트도 .
개요 :
00:00:00\n
스택을 밀어 넣 습니다. 그런 다음 루프에서 :
sys_write
시스템 호출
- (마지막으로 시작) 숫자를 통해 ADC-루프는 취급 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 ° C까지 가열 pause
되고 ~ 9로만 가열 됩니다 pause
. (Skylake에서는 pause
~ 5 대신 ~ 100 사이클 동안 잠을 자고 있습니다.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
또한 1 바이트를 사용 dec edx
합니다. int 0x80
시스템 콜 대 lodsb / stosb의 레지스터 설정이 간단 할 수 있도록 시스템 호출 ABI는 EDI / ESI 사용하지 않을 것입니다.