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サイクルをカウントするための実際のperfカウンターがあります。)通常、ターボや省電力に関係なく、i7-6700kの場合は4.0GHzなどの公称ステッカー周波数で作動します。とにかく、このビジー待機のタイミングは負荷平均に依存せず(キャリブレーションされた遅延ループがそうするように)、CPUの節電にも影響されません。
このコードは、2 ^ 32 Hz未満の基準周波数(つまり最大〜4.29 GHz)のすべてのx86で機能します。それを超えると、タイムスタンプの下位32は1秒で完全にラップするためedx
、結果の上位32ビットも調べる必要があります。
まとめ:
00:00:00\n
スタックをプッシュします。次に、ループ内で:
sys_write
システムコール
- 1.ラッピング/搬出で処理することにより時間を増分する数字(最後から始まる)を介してADCループ
cmp
/ cmov
CF結果は、キャリーインの次の桁のを提供すること、。
rdtsc
開始時間を節約します。
rdtsc
デルタがRDTSC周波数の1秒あたりのティック数以上になるまでスピンオンします。
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
命令のコメントを外して、電力を大幅に節約します。これにより、1つのコアpause
がなしで〜15℃ だけ加熱されますが、で〜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使用することはありません。