【发布时间】:2011-10-25 06:14:42
【问题描述】:
如何计算指令的执行时间?是否只是通过检查芯片制造商所说的一个动作可能需要多少时钟周期来完成?还有什么我应该知道的吗?感觉好像错过了什么......
【问题讨论】:
标签: performance assembly execution instructions
如何计算指令的执行时间?是否只是通过检查芯片制造商所说的一个动作可能需要多少时钟周期来完成?还有什么我应该知道的吗?感觉好像错过了什么......
【问题讨论】:
标签: performance assembly execution instructions
据我所知,RDTSC 指令非常准确。
我认为,如果您正在寻找确切的循环计数,那么在较短的可加速部分的情况下,您可能会遇到 Mysticial 提到的同时性问题......
但如果超超超超精度不是障碍...也就是说,如果您知道在某些情况下您的结果会偏离...我不知道...比如说 9到 80 个周期...那么我很确定您仍然可以使用 RDTSC 获得非常准确的结果...尤其是当人们认为 9 到 80 除以 32 亿是一个非常小的数字时:)
数字 9 和 80 的选择有点随意(也许你的 cpu 速度也不是 3.2ghz),因为我不知道错误量到底是多少......但我很确定它在那个范围内:)
这是我使用的计时器函数的 RDTSC 摘录:
//High-Rez Setup
__asm
{
push eax
push edx
rdtsc
mov [AbsoluteLow],eax
mov [AbsoluteHigh],edx
pop edx
pop eax
}
实际上我会继续发布整个内容...此代码假定类型“double”是 64 位浮点数...这可能不是通用编译器/架构假设:
double AbsoluteTime;
double AbsoluteResolution;
ulong AbsoluteLow;
ulong AbsoluteHigh;
void Get_AbsoluteTime (double *time)
{
//Variables
double current, constant;
double lower, upper;
ulong timelow, timehigh;
//Use the Intel RDTSC
__asm
{
push eax
push edx
rdtsc
sub eax, [AbsoluteLow]
sbb edx, [AbsoluteHigh]
mov [timelow], eax
mov [timehigh], edx
pop edx
pop eax
}
//Convert two 32bit registers to a 64-bit floating point
//Multiplying by 4294967296 is similar to left-shifting by 32 bits
constant = 4294967296.0;
lower = (double) timelow;
upper = (double) timehigh;
upper *= constant;
current = lower + upper;
current /= AbsoluteResolution;
current += AbsoluteTime;
*time = current;
}
void Set_AbsoluteTime (double time, double scale)
{
//Variables
double invScale;
//Setup
AbsoluteTime = time;
//High-Rez Setup
__asm
{
push eax
push edx
rdtsc
mov [AbsoluteLow],eax
mov [AbsoluteHigh],edx
pop edx
pop eax
}
//Fetch MHZ
if (1)
{
//Local Variables
int nv;
ulong mhz;
char keyname[2048];
//Default assumption of 3.2ghz if registry functions fail
mhz = 3200;
//Registry Key
sprintf (keyname, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
nv = Reg_Get_ValueDW (keyname, "~MHz", (ulong *)&mhz);
//Transform into cycles per second
mhz *= 1000000;
//Calculate Speed Stuff
AbsoluteResolution = (double) mhz;
invScale = 1.0;
invScale /= scale;
AbsoluteResolution *= invScale;
}
}
您想在使用 Get 函数之前在某处调用 Set_AbsoluteTime...如果没有第一次初始调用 Set,Get 将返回错误结果...但是一旦进行了一次调用,您就可以开始了...
这是一个例子:
void Function_to_Profile (void)
{
//Variables
double t1, t2, TimeElapsed;
//Profile operations
Get_AbsoluteTime (&t1);
...do stuff here...
Get_AbsoluteTime (&t2);
//Calculate Elapsed Time
TimeElapsed = (t2 - t1);
//Feedback
printf ("This function took %.11f seconds to run\n", TimeElapsed);
}
void main (void)
{
Set_AbsoluteTime (0.000, 1.000);
Function_to_Profile();
}
如果出于某种原因您希望时间测量以半速倒流(可能对游戏编程很方便),那么初始调用将是: Set_AbsoluteTime (0.000, -0.500);
Set 的第一个参数是添加到所有结果中的基准时间
我很确定这些函数比目前公开存在的最高分辨率的 Windows API 计时器更准确...我认为在快速处理器上它们的误差小于 1 纳秒,但我不是 100% 确定在那:)
它们对于我的目的来说足够准确,但请注意 40 个前导字节的标准初始化(由 'current'、'constant'、'lower'、'upper'、'timelow'、'timehigh ') 大多数 C 编译器将设置为 0xCC 或 0xCD 会占用一些周期...就像在每个 Get_AbsoluteTime 调用底部执行的数学运算...
因此,为了获得真正原始的准确性,您最好在 RDTSC“内联”中构建您想要分析的任何内容...我会利用扩展的 x64 寄存器来存储答案以供以后的减法运算使用,而不是乱七八糟内存访问速度较慢...
例如像这样的......顺便说一句,这主要是概念,因为技术上 VC2010 不允许您通过 __asm 关键字发出 x64-Assembly :( ...但我认为它将为您提供概念性的旅行之路:
typedef unsigned long long ulonglong;
ulonglong Cycles;
__asm
{
push rax
push rdx
rdtsc
mov r9, edx
shl r9, 32
and rax, 0xFFFFFFFF
or r9, rax
pop rdx
pop rax
}
...Perform stuff to profile here
__asm
{
push rax
push rdx
rdtsc
mov r10, edx
shl r10, 32
and rax, 0xFFFFFFFF
or r10, rax
sub r10, r9
mov qword ptr [Cycles], r10
pop rdx
pop rax
}
printf ("The code took %s cycles to execute\n", ULONGLONG_TO_STRING (Cycles));
使用该代码,我认为经过的周期数的最终答案将在 r10 中,一个 64 位寄存器......或在 Cycles 中,一个 64 位无符号整数......只有少数几个周期的错误引起位移和堆栈操作...前提是要分析的代码不会破坏 r9 和 r10 呵呵...我忘记了最稳定的扩展 x64 寄存器是什么...
“and rax, 0xFFFFFFFF”也可能是无关紧要的,因为我不记得 RDTSC 是否将 RAX 的高 32 位清零...所以我包含了 AND 操作以防万一:)
【讨论】:
rdtsc 的非常好的答案。虽然我想补充一点,rdtsc 并不总是以 CPU 周期来衡量。虽然rdtsc 必须保持不变,但由于省电功能和涡轮增压,CPU 频率可以动态变化。 (例如,在我的机器上,rdtsc 提供 34 亿周期/秒,而处理器实际主频为 4.6 GHz)因此,当您使用 rdtsc 计算周期时,您需要进行适当的调整。跨度>
这是一项不平凡的任务。最简单的方法是查看其他人发现的结果。
例如,Agner Fog 作为当前 x86/x64 处理器的此信息的重要参考:http://www.agner.org/optimize/instruction_tables.pdf
如果您真的想自己测量指令延迟和吞吐量,则需要非常深入地了解处理器的工作原理。然后你必须深入研究汇编编码。编写微基准来衡量这些事情本身几乎就是一个领域,因为需要进行大量的逆向工程。
当然,最终 - 应用程序的性能取决于更多的因素,而不仅仅是指令延迟/吞吐量...
【讨论】: