【问题标题】:What good are thread affinity mask changes for the current thread?当前线程的线程关联掩码更改有什么好处?
【发布时间】:2011-01-12 18:53:38
【问题描述】:

我正在编写一个游戏引擎,我需要一种方法来获取精确且准确的“deltatime”值,从中导出当前 FPS 以进行调试并限制帧速率(这对我们的项目很重要)。

做了一些研究,我发现最好的方法之一是使用 WinAPI 的QueryPerformanceCounter 函数。 GetTicksCount必须用来防止forward counter leaps,但它本身不是很准确。

现在,QueryPerformanceCounter 的问题在于,它显然可能返回的值看起来像时间扭曲后的样子(即,相对于过去的另一个调用,一个调用可能会在时间上返回一个更早的值)。仅当将使用给定处理器内核获得的值与使用另一个处理器内核获得的值进行比较时才会发生这种情况,这使我想到了促使我发表这篇文章的终极问题:

  1. 操作系统是否可以在线程已经运行时将线程“重新分配”到另一个内核,或者线程是否已分配给给定的内核,直到线程死亡?
  2. 如果无法重新分配线程(至少这对我来说很有意义),那么为什么我可以做SetThreadAffinityMask(GetCurrentThread(),mask) 之类的事情? Ogre3D 在其Ogre::Timer class (Windows implementation) 中这样做,我假设这是为了避免时间倒退。但要做到这一点,我将不得不考虑操作系统任意将线程从一个内核移动到另一个内核的可能性,这对我来说似乎很奇怪(不知道为什么)。

我想这就是我现在想知道的。谢谢。

【问题讨论】:

    标签: c++ winapi setthreadaffinitymask performancecounter


    【解决方案1】:

    除非线程具有处理器关联掩码,否则调度程序会将其从一个处理器移动到另一个处理器,以便为其提供执行时间。由于在处理器之间移动线程会降低性能,它会尝试不移动它,但给它一个执行的处理器优先于不移动它。因此,通常线程会移动。

    至于计时器 API。 timeGetTime是专为多媒体计时设计的,所以比GetTickCount要准确一点。

    QueryPerformanceCounter(). 仍然是您最精确的测量值。微软对此有话要说。

    在多处理器计算机上,调用哪个处理器并不重要。但是,由于基本输入/输出系统 (BIOS) 或硬件抽象层 (HAL) 中的错误,您可能会在不同的处理器上获得不同的结果。要为线程指定处理器亲和性,请使用 SetThreadAffinityMask 函数。

    因此,如果您在特定计算机上进行时序测试,您可能不必担心 QPC 会倒退,您应该进行一些测试,看看它在您的计算机上是否重要。

    【讨论】:

    • 很高兴知道。我真的很想知道有多少种方法可以进行这种测量。真的只有这 3 种众所周知的可移植方法吗?
    • 据我所知,只有这 3 个。有一个实时时钟滴答,但很难进入内核模式之外。而且它只有 1.1 Mhz 的滴答率。
    • 关于在特定计算机上进行计时测试,就像我说的那样,这不是用于性能测试。它是最终产品的重要组成部分。不过,感谢您的评论。
    【解决方案2】:

    线程可以并且正在(除非它们具有关联集)在线程运行时重新分配。 Windows 将负载分散到所有处理器上以最大限度地提高性能。

    【讨论】:

    • 因此,据您所知,不将关联掩码设置为仅一个核心并使用 QueryFrequencyCounter 确实会使计数器在从不同核心调用该函数时变回。是这样吗?
    • 换核时有时间倒退的可能,是的。
    • 好的,我想这就解决了。我必须创建一个线程来测量时间并使用互斥锁来查询它,这样测量总是只在一个线程中进行,对吗?
    • 类似的东西。不要忘记互斥体/关键部分涉及从用户模式切换到内核模式,因此它们会降低速度。
    【解决方案3】:

    即使您使用 SetAffinityMask 将线程锁定到一个处理器,如果您真的不走运并且硬件很糟糕,QPC 也可以向后运行。最好只处理 QPC 返回错误值的情况。在 Windows 7 中,QPC 在这方面得到了显着改进,但是由于您正在编写游戏,因此您可能针对的是 XP,它对您没有帮助。

    另外,不要设置线程亲和性,你可能会死锁自己,引入奇怪的时间和性能错误,通常会让你自己伤心。

    【讨论】:

    • 那么您的建议是什么?每当我检测到向后运行时,忽略该事件并使用最后一个增量时间?如果我确定它不会一直发生,我只能这样做。您还有其他想法吗?
    • 这不是一个坏方法,或者保持最后 4 帧的运行平均值,如果出现垃圾则使用它。但总的来说,你不会一直看到 QPC 对你这样做,这是相当罕见的情况 - 只是愚蠢的游戏代码执行了无符号减法和下溢问题
    【解决方案4】:

    1) 线程可以将线程分配给具有空闲处理时间的内核。这就是为什么您经常会看到在四核机器上使用 50% 的软件,而当您查看图表时,它使用了所有四核机器的一半。

    2) 见 1 ;)

    【讨论】:

      【解决方案5】:

      使用 SetThreadAffinity() 通常不是一个好主意,除非线程只进行计时。如果您将线程锁定到单核,那么您首先会消除拥有多核系统的所有好处。您的应用程序无法再扩展。即使您启动了应用的多个实例,它们仍将被锁定在一个内核中。

      【讨论】:

      • 为什么有人会启动多个游戏实例?
      • 谁知道呢。也许应用程序的各个部分被分解为单独的应用程序。更重要的是,这有什么关系?
      • @Crashworks 没有人会,但他确实有道理。这就是为什么我也没有先问就继续建立亲和力的原因。我还认为应该只为时间安排。很高兴听到和我一样想法的人。
      • 因为他们想对一个游戏的多个实例做某事?例如,无论出于何种原因在本地测试网络游戏时,这种情况并非闻所未闻。
      • FWIW,我们的经验是,我们引擎的各个组件对时间非常敏感,以至于我们必须为每个线程作业设置明确的关联性。我们预先知道框架的哪些部分可以并发运行以及它们依赖于什么,并且允许操作系统将线程从一个内核移动到另一个内核只会导致问题,要么是由于调度错误的并发性,要么是在上下文切换上浪费了许多微秒。
      【解决方案6】:

      因此,我们通常必须在运行计时时将游戏锁定在单个线程中;我们没有找到有效的解决方法,因为在测量性能时需要亚微秒级分辨率。

      让它变得更容易一点的一点是,我们的引擎被分割成广泛的组件,这些组件始终同时运行(例如游戏/逻辑“服务器”、输入/图形“客户端”、音频、 render 是他们自己的线程),所以我们所做的就是将这些线程中的每一个锁定到自己的核心上并独立地对它们进行计时。

      同样,因为我们知道例如渲染循环总是在核心 0 上,所以我们使用它来计时帧率。

      【讨论】:

        猜你喜欢
        • 2011-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-24
        • 2013-01-25
        • 1970-01-01
        相关资源
        最近更新 更多