计时器、线程和编译器不当行为

timers, threads and compiler misbehaviour

本文关键字:编译器 不当行为 线程 计时器      更新时间:2023-10-16

我在某件事上遇到了麻烦,找不到任何答案,因为我什至不知道要搜索什么。我有一个使用 QueryPerformanceCounter 完成的计时器类,从我的应用程序中,我启动了第二个线程对象,该对象具有自己的实例化计时器,我只有一个无限循环从计时器获取增量时间并使用它来输出每秒循环迭代次数。

我注意到它给了我奇怪的值,所以我开始打印增量时间,发现它有时显示为 0,所以我进入了返回增量时间的方法并做了一些测试。这是我的 deltaTime(( 方法:

    double MyTimer2::deltaTime()
    {
        LARGE_INTEGER timenow;
        QueryPerformanceCounter(&timenow);
        //std::cout << "timenow=" << (double)timenow.QuadPart << "   currentticks=" << (double)m_currentTicks.QuadPart << std::endl;
        double m_deltaTime = (double)(timenow.QuadPart - m_currentTicks.QuadPart) /* 1000.0*/ / (double)m_frequency.QuadPart;
        m_currentTicks = timenow;
        if(m_deltaTime < 0.000001)
            return 0.0;
        return m_deltaTime;
    }

因此,我在"return 0.0"上放置了一个断点,结果是它大部分时间都到达那里,这是不正确的。但是,如果我取消注释打印代码并运行,我将永远不会在断点上停止。所以从理论上讲,我的打印代码正在使其正常工作,而如果我删除它,事情就会停止正常工作!这怎么可能,为什么会发生,我该如何解决?我尝试过_ReadWriteBarrier((没有成功。

提前感谢!

编辑:我需要一个高分辨率的物理模拟计时器!

几代处理器前,QueryPerformanceCounter()会读取 CPU 的周期计数器(例如 rdtsc (。 使用此方法,连续读取的即时报价数永远不会为零。 分辨率等于 CPU 时钟速率,例如 3 GHz。

现代处理器具有两个特性,使周期计数器对计时毫无用处。 首先,您有多个内核,每个内核都有自己的周期计数器。 线程可以在内核之间迁移,如果从两个不同的内核读取周期计数器,则差异与运行时间无关。 它甚至可能是负面的。 其次,您有基于负载的动态时钟(降频以节省功耗和超频以达到性能(。 英特尔分别称这些为"SpeedStep"和"Turbo Boost"。 当周期率不固定时,无法从即时报价转换为时间。

因此,QueryPerformanceCounter现在使用一种称为高性能事件计数器(HPET(的专用硬件,分辨率为几MHz。 重要的是,无论您有多少个内核,都只有一个,并且它不会动态改变速度。 但是,由于分辨率较低,现在可以在即时报价之间读取两次,在这种情况下,您将获得报告为零的经过时间。

实际上,这不是问题。 如果您需要比HPET可以提供的更精确的计时,那么通用计算机不适合您。 纳秒范围内的时序将受到中断的严重影响。

这个块的目的可能是什么?

if(m_deltaTime < 0.000001)
    return 0.0;

没有价值,它只是搞砸了结果,告诉你时间是零,而实际上并非如此。

首先,你的计时器是错误的:它密集地消耗了你的CPU。在单核机器上,它会减慢所有系统的速度。如果要创建计时器并面向 Windows,可以使用计时器函数。

然后,deltaTime()函数返回的每个非负值都是有效的。虽然不是在实时操作系统中托管,但每个操作可能需要任意时间。一次迭代可能需要大约十个处理器周期,或者十年。没有人保证。

第三,关于实验结果。似乎如果上下文在两个连续的时间测量之间切换一次,你会得到大约0.016s的值,如果没有,你会得到低于0s 0.000001s的值。

如前所述,打印到控制台是相对繁重的操作,当您启用它时,您实际上总是会切换上下文。

编辑

虽然QueryPerformanceCounter似乎提供了很好的分辨率,但它困住了你。除非你在实时操作系统中工作,否则你永远不会得到真正的高分辨率计时器。