我想验证和测量这些性能特征的差异.所以我写了这个程序在C:
#include "stdafx.h" #include <intrin.h> #include <iostream> using namespace std; int _tmain(int argc,_TCHAR* argv[]) { __int64 startval; __int64 stopval; unsigned int value; // Keep the value to keep from it being optomized out startval = __rdtsc(); // Get the cpu Tick Counter using assembly RDTSC opcode // Simple Math: a = (a << 3) + 0x0054E9 _asm { mov ebx,0x1E532 // Seed shl ebx,3 add ebx,0x0054E9 mov value,ebx } stopval = __rdtsc(); __int64 val = (stopval - startval); cout << "Result: " << value << " -> " << val << endl; int i; cin >> i; return 0; }
我尝试这个代码交换eax和ebx,但我没有得到一个“稳定”的数字.我希望测试是确定性的(每次都是同一个数字),因为它的测试很短,因此在测试过程中不太可能发生上下文切换.因为它没有统计学差异,但数字如此波动,所以不可能做出这个决定.即使我拿了大量样本,这个数字仍然是不可能变化的.
我也想测试xor eax,eax vs mov eax,但有同样的问题.
有没有办法在Windows(或其他任何地方)进行这些类型的性能测试?当我习惯为我的TI-Calc编程Z80时,我有一个工具,我可以选择一些程序集,它会告诉我执行代码有多少个时钟周期 – 这可以不用我们的新风格的现代处理器吗?
编辑:有很多答案表明运行循环百万次.要澄清,这实际上使事情变得更糟. cpu更可能进行上下文切换,测试成为关于所有内容的一切,但我正在测试.
解决方法
你通常想使用串行化指令,然后使用RDTSC,然后是有问题的代码,另一个串行化指令和第二个RDTSC.
在用户模式下几乎只有可用的序列化指令是cpuID.然而,这又增加了一个小小的皱纹:IntelID记录了cpuID需要不同的执行时间 – 前两个执行速度可能比其他更慢.
因此,您的代码的正常计时顺序将是这样的:
XOR EAX,EAX cpuID XOR EAX,EAX cpuID ; Intel says by the third execution,the timing will be stable. RDTSC ; read the clock push eax ; save the start time push edx mov ebx,0x1E532 // Seed // execute test sequence shl ebx,3 add ebx,0x0054E9 mov value,ebx XOR EAX,EAX ; serialize cpuID rdtsc ; get end time pop ecx ; get start time back pop ebp sub eax,ebp ; find end-start sbb edx,ecx
我们开始接近,但最后一点很难处理在大多数编译器上使用内联代码:还可能会从跨越缓存行中产生一些影响,因此您通常希望强制将代码与16对齐字节(段)边界.任何体面的汇编程序都会支持,但编译器中的内联汇编通常不会.
说完这一切,我想你在浪费你的时间.你可以猜到,我在这个级别做了相当数量的时间安排,我相当肯定你听说过的是一个完全的神话.实际上,所有最近的x86 cpu都使用一套所谓的“重命名寄存器”.为了缩短故事时间,这意味着您用于注册表的名称并不重要 – cpu对于实际操作使用了更大的一组寄存器(例如,英特尔约为40个),因此在EBX与EAX中放置一个值对于cpu在内部真正使用的寄存器几乎没有影响.可以映射到任何重命名寄存器,主要取决于哪个重命名寄存器在指令序列启动时恰好是空闲的.