你如何计算指令的执行时间?是否只是通过检查芯片制造商所说的一个动作可能需要多少时钟周期来完成?还有什么我应该知道的吗?感觉我错过了什么......
2 回答
据我所知,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 个前导字节的标准初始化(由“当前”、“常量”、“下”、“上”、“时间低”、“时间高”组成)大多数 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 操作以防万一:)
这是一项不平凡的任务。最简单的方法是查看其他人发现的结果。
例如,Agner Fog 作为当前 x86/x64 处理器的此信息的重要参考:http ://www.agner.org/optimize/instruction_tables.pdf
如果您真的想自己测量指令延迟和吞吐量,您将需要非常深入地了解处理器的工作原理。然后你必须深入研究汇编编码。编写微基准来衡量这些事情本身几乎就是一个领域,因为需要进行大量的逆向工程。
当然,最终 - 应用程序的性能取决于更多的因素,而不仅仅是指令延迟/吞吐量......