c – 装配性能调整

我正在编写一个编译器(比别的更有趣),但是我想尝试尽可能的高效.例如,我被告知,在英特尔架构上,使用除EAX之外的任何注册表进行数学计算会产生一个代价(大概是因为它交换到EAX来做实际的数学计算).这里至少有一个来源说明了可能性(http://www.swansontec.com/sregisters.html).

我想验证和测量这些性能特征的差异.所以我写了这个程序在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更可能进行上下文切换,测试成为关于所有内容的一切,但我正在测试.

@H_301_14@

解决方法

为了甚至希望在RDTSC给出的水平上具有可重复性,确定性的时序,您需要采取一些额外的步骤.首先,RDTSC不是一个串行化指令,所以它可以按顺序执行,通常会在上面的代码段中使其无意义.

你通常想使用串行化指令,然后使用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在内部真正使用的寄存器几乎没有影响.可以映射到任何重命名寄存器,主要取决于哪个重命名寄存器在指令序列启动时恰好是空闲的.

@H_301_14@ @H_301_14@

相关文章

/** C+⬑ * 默认成员函数 原来C++类中,有6个默认成员函数: 构造函数 析构函数 拷贝...
#pragma once // 1. 设计一个不能被拷贝的类/* 解析:拷贝只会放生在两个场景中:拷贝构造函数以及赋值运...
C类型转换 C语言:显式和隐式类型转换 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译...
//异常的概念/*抛出异常后必须要捕获,否则终止程序(到最外层后会交给main管理,main的行为就是终止) try...
#pragma once /*Smart pointer 智能指针;灵巧指针 智能指针三大件//1.RAII//2.像指针一样使用//3.拷贝问...
目录&lt;future&gt;future模板类成员函数:promise类promise的使用例程:packaged_task模板类例程...