Assembly x86_64-为什么使用rdtsc/rdtscp对程序计时会给出不合理的大数字?

Assembly x86_64-为什么使用rdtsc/rdtscp对程序计时会给出不合理的大数字?,assembly,x86-64,rdtsc,Assembly,X86 64,Rdtsc,我正在尝试使用rdtscp对子程序计时。这是我的程序: ; Setting up time rdtscp ; Getting time push rax ; Saving timestamp ; for(r9=0; r9<LOOP_SIZE; r9++) mov r9, 0 lup0: call subr inc r9 cmp r9, LOOP_SIZE jnz lup0 ; Calculating tim

我正在尝试使用rdtscp对子程序计时。这是我的程序:

; Setting up time
rdtscp                      ; Getting time
push rax                    ; Saving timestamp

; for(r9=0; r9<LOOP_SIZE; r9++)
mov r9, 0
lup0:
call subr
inc r9
cmp r9, LOOP_SIZE
jnz lup0

; Calculating time taken
pop rbx                     ; Loading old time
rdtscp                      ; Getting time
sub rax, rbx                ; Calculating difference
我用来显示数字的方法将它们显示为无符号,因此我认为显示的大数字实际上是负数,并且发生了溢出。但是,
971597237
甚至不接近64位整数限制,因此,假设问题是溢出,为什么会发生?

问题是,
rdtscp
的值不是存储在
rax
上,而是存储在
edx:eax
(这意味着高位在
edx
上,低位在
eax
上)即使在64位模式下也是如此

因此,如果要在
rax
上使用完整的64位值,则必须从
edx
移动较高的位:

; Setting up time
rdtscp                      ; Getting time
shl rdx, 32                 ; Shifting rdx to the correct bit position
add rax, rdx                ; Adding both to make timestamp
push rax                    ; Saving timestamp

; [...stuff...]

; Calculating time taken
rdtscp                      ; Getting time
pop rbx                     ; Loading old time (below rdtscp)
shl rdx, 32                 ; Shifting rdx to the correct bit position
add rax, rdx                ; Adding both to make timestamp
sub rax, rbx                ; Calculating difference
编辑:向下移动
弹出rbx
一行,在
rdtscp
下面。正如Peter指出的,一些寄存器(rax、rdx和rcx)可能会被
rdtscp
覆盖。在您的示例中,这不是问题,但是如果您决定在那里
pop rcx
,那么它可能会被
rdtscp
覆盖,因此最好只在它之后弹出堆栈


此外,通过将旧时间戳保存在子例程不使用的寄存器中,可以避免对堆栈的两次调用:

; Setting up time
rdtscp                      ; Getting time
shl rdx, 32                 ; Shifting rdx to the correct bit position
lea r12, [rdx + rax]        ; Adding both to make timestamp, and saving it

; [...stuff (that doesn't use r12)...]

; Calculating time taken
rdtscp                      ; Getting time
shl rdx, 32                 ; Shifting rdx to the correct bit position
add rax, rdx                ; Adding both to make timestamp
sub rax, r12                ; Calculating difference
问题是,即使在64位模式下,
rdtscp
的值也不存储在
rax
上,而是存储在
edx:eax
上(这意味着高位在
edx
上,低位在
eax

因此,如果要在
rax
上使用完整的64位值,则必须从
edx
移动较高的位:

; Setting up time
rdtscp                      ; Getting time
shl rdx, 32                 ; Shifting rdx to the correct bit position
add rax, rdx                ; Adding both to make timestamp
push rax                    ; Saving timestamp

; [...stuff...]

; Calculating time taken
rdtscp                      ; Getting time
pop rbx                     ; Loading old time (below rdtscp)
shl rdx, 32                 ; Shifting rdx to the correct bit position
add rax, rdx                ; Adding both to make timestamp
sub rax, rbx                ; Calculating difference
编辑:向下移动
弹出rbx
一行,在
rdtscp
下面。正如Peter指出的,一些寄存器(rax、rdx和rcx)可能会被
rdtscp
覆盖。在您的示例中,这不是问题,但是如果您决定在那里
pop rcx
,那么它可能会被
rdtscp
覆盖,因此最好只在它之后弹出堆栈


此外,通过将旧时间戳保存在子例程不使用的寄存器中,可以避免对堆栈的两次调用:

; Setting up time
rdtscp                      ; Getting time
shl rdx, 32                 ; Shifting rdx to the correct bit position
lea r12, [rdx + rax]        ; Adding both to make timestamp, and saving it

; [...stuff (that doesn't use r12)...]

; Calculating time taken
rdtscp                      ; Getting time
shl rdx, 32                 ; Shifting rdx to the correct bit position
add rax, rdx                ; Adding both to make timestamp
sub rax, r12                ; Calculating difference

rdtsc
即使在64位模式下,也会令人烦恼地将其结果放入EDX:EAX。您只保存/使用TSC的低32位,并获得32位无符号差,符号扩展到64位,因为您是在零扩展32位值上使用
sub-rax,rbx
计算它,而不是
sub-EAX,ebx
rdtsc
即使在64位模式下,也会令人烦恼地将其结果放入EDX:EAX。您只保存/使用TSC的低32位,并获得32位无符号差,符号扩展到64位,因为您是在零扩展32位值上使用
sub-rax,rbx
计算它,而不是
sub-EAX,ebx
。是的,这是正确的。正常情况下您可以使用调用缓冲寄存器(如rsi或r8)代替RBX,而无需在函数周围保存/恢复。(
rdtscp
clobbers RCX)。或者,您可以使用
shl rdx,32
/
lea r8[rdx+rax]将一半合并到另一个寄存器,而不是堆栈中
,选择一个寄存器,您的定时区域不会修改。无需猜测寄存器所涉及的内容。
rdtscp
会写入RDX、RAX和RCX。但是,是的,因为您不需要(或想要)在定时区域中,如果你坚持使用堆栈而不是寄存器来保存旧时间,你可以直接弹出RCX。然后你不需要触摸RDTSCP尚未销毁的任何寄存器。但是如果你决定弹出RCX,那么它可能会被RDTSCP覆盖-不,如果你是
pop
rdtscp
之后,就像你现在所做的那样。我的评论是基于原始版本的,它在定时区域内弹出,不幸的是,我没有想到只是在后面弹出。
add rax,rdx
/
mov r8,rax
可以被
lea r8[rdx+rax]代替
就像我最初建议的那样,这在所有CPU上基本上在所有可能重要的方面都更便宜。此外,如果您正在计时的是一个“子例程”,如果它遵循标准的调用约定,您可能希望选择一个保留调用的寄存器,如
r12
。您陈述它的方式(就在后面,在与“编辑:将rbx向下移动一行”)会导致一个看似毫无意义的说法,即对建议的工作答案进行进一步改进。是的,这是正确的。通常情况下,您会使用一个调用中断寄存器,如rsi或r8,而不需要在函数周围保存/恢复它,而不是rbx。(
rdtscp
clobbers-RCX)。或者,您可以使用
shl rdx,32
/
lear8,[rdx+rax]
将一半合并到另一个寄存器中,而不是堆栈,选择一个定时区域不修改的寄存器。无需猜测什么寄存器
rdtscp
接触。它编写rdx、rax和RCX。。但是的,因为您不需要(或想要)在定时区域中,如果你坚持使用堆栈而不是寄存器来保存旧时间,你可以直接弹出RCX。然后你不需要触摸RDTSCP尚未销毁的任何寄存器。但是如果你决定弹出RCX,那么它可能会被RDTSCP覆盖-不,如果你是
pop
rdtscp
之后,就像你现在所做的那样。我的评论是基于原始版本的,它在定时区域内弹出,不幸的是,我没有想到只是在后面弹出。
add rax,rdx
/
mov r8,rax
可以被
lea r8[rdx+rax]代替
就像我最初建议的那样,在所有CPU上,从本质上讲,这在所有重要的方面都比较便宜。此外,如果您正在计时的是一个“子例程”,那么如果它遵循标准的调用约定,您可能需要选择一个调用