Assembly x86_64-为什么使用rdtsc/rdtscp对程序计时会给出不合理的大数字?
我正在尝试使用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
; 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上,从本质上讲,这在所有重要的方面都比较便宜。此外,如果您正在计时的是一个“子例程”,那么如果它遵循标准的调用约定,您可能需要选择一个调用