Kod maszynowy x86-64 (wywołanie systemowe Linux): 78 bajtów
Czas taktowania pętli RDTSC , Linuxsys_write
wywołanie systemowe .
x86-64 nie zapewnia wygodnego sposobu sprawdzania częstotliwości „zegara referencyjnego” RDTSC w czasie wykonywania. Możesz odczytać MSR (i wykonać na tej podstawie obliczenia) , ale wymaga to trybu jądra lub otwarcia roota + /dev/cpu/%d/msr
, więc postanowiłem, że częstotliwość będzie stała. (DostosowaćFREQ_RDTSC
odpowiednio: dowolna 32-bitowa stała nie zmieni rozmiaru kodu maszynowego)
Zauważ, że procesory x86 od kilku lat mają stałą częstotliwość RDTSC, więc można je wykorzystać jako źródło czasu, a nie a licznik wydajności taktowania rdzenia, chyba że podejmiesz kroki w celu wyłączenia zmian częstotliwości. (Istnieją rzeczywiste liczniki perf do zliczania rzeczywistych cykli procesora.) Zwykle tyka przy nominalnej częstotliwości naklejki, np. 4,0 GHz dla mojego i7-6700k, niezależnie od turbo lub oszczędzania energii. W każdym razie ten czas oczekiwania na zajęcie nie zależy od średniego obciążenia (jak skalibrowana pętla opóźniająca), a także nie jest wrażliwy na oszczędność energii procesora.
Ten kod będzie działał dla każdego x86 o częstotliwości odniesienia poniżej 2 ^ 32 Hz, tj. Do ~ 4,29 GHz. Poza tym niski 32 znacznik czasu zawinąłby się do końca w ciągu 1 sekundy, więc musiałbym też spojrzeć na edx
wysokie 32 bity wyniku.
Podsumowanie :
pchnij 00:00:00\n
na stosie. Następnie w pętli:
sys_write
wywołanie systemowe
- Pętla ADC nad cyframi (zaczynająca się od ostatniej) w celu zwiększenia czasu o 1. Owijanie / przeprowadzanie obsługiwane za pomocą a
cmp
/ cmov
, przy czym wynik CF zapewnia wprowadzanie następnej cyfry.
rdtsc
i oszczędzaj czas rozpoczęcia.
- spin na
rdtsc
momentu delta> = kleszczy na sekundę częstotliwości RDTSC.
Lista 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.
Odkomentuj pause
instrukcję oszczędzania znacznej mocy: podgrzewa jeden rdzeń o ~ 15 stopni C bez pause
, ale tylko o ~ 9 z pause
. (Na Skylake, gdzie pause
śpi przez ~ 100 cykli zamiast ~ 5. Myślę, że zaoszczędziłoby więcej, gdyby rdtsc
nie było również spowolnienie, więc procesor nie zajmuje dużo czasu).
Wersja 32-bitowa byłaby o kilka bajtów krótsza, np. Przy użyciu 32-bitowej wersji do wypchnięcia początkowego ciągu 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
A także przy użyciu 1-bajtu dec edx
. int 0x80
Wywołanie systemowe ABI nie użyłby ESI / edi, więc konfiguracja rejestr dla syscall vs. lodsb / stosb może być prostsze.