【问题标题】:Trigger CPU cache write back manually in java: possible? necessary?在java中手动触发CPU缓存写回:可能吗?必要的?
【发布时间】:2019-06-03 12:31:20
【问题描述】:

我在业余时间写一个视频游戏,在引入多线程时有一个关于数据一致性的问题。

目前我的游戏是单线程的,并且有一个简单的游戏循环,正如许多教程中所教授的那样:

while game window is not closed
{
    poll user input
    react to user input
    update game state
    render game objects
    flip buffers
}

我现在想在我的游戏中添加一个新功能,让玩家可以自动执行某些冗长乏味的任务,例如长距离步行(快速旅行)。我可能会选择简单地将玩家角色“传送”到他们的目的地,但我不希望这样做。取而代之的是,游戏将加速,玩家角色实际上会像玩家手动行走一样行走。这样做的好处是游戏世界会像往常一样与玩家角色互动,任何可能发生的特殊事件仍然会发生并立即停止快速旅行。

为了实现这个功能,我正在考虑这样的事情:

  • 启动一个新线程(工作线程)并让该线程不断更新游戏状态,直到玩家角色到达其目的地
  • 让主线程不再像往常一样更新游戏状态和渲染游戏对象,而是以更简单的方式显示旅行进度
  • 使用同步消息队列让主线程和工作线程通信
  • 当快速旅行完成或取消(由于玩家交互或其他原因)时,工作线程会终止并使用主线程恢复标准游戏循环

在伪代码中可能如下所示:

[main thread]
while game window is not closed
{
    poll user input

    if user wants to cancel fast travel
    {
        write to message queue player input "cancel"
    }

    poll message queue about fast travel status

    if fast travel finished or canceled
    {
        resume regular game loop
    } else {
        render travel status
        flip buffers
    }
}

[worker thread]
while (travel ongoing)
{
    poll message queue

    if user wants to cancel fast travel
    {
        write to message queue fast travel status "canceled"
        return
    }

    update game state

    if fast travel is interrupted by internal game event
    {
        write to message queue fast travel status "canceled"
        return
    }

    write to message queue fast travel status "ongoing"
}
if travel was finished
{
    write to message queue fast travel status "finished"
}

消息队列将是某种双通道同步数据结构。也许有两个 ArrayDeque,每个都有一个 Lock。我相当肯定这不会太麻烦。

我比较关心的是游戏数据的缓存问题:

  • 1.a) 会不会是工作线程在启动后可能会看到旧的游戏数据,因为主线程可能运行在缓存了部分结果的不同内核上?
  • 1.b) 如果上述情况属实:我是否需要将游戏数据中的每个字段都声明为 volatile,以绝对保证不会出现不一致的数据?
  • 2) 如果所有字段都是易变的,我是否可以假设性能会受到不小的影响?
  • 3) 由于我只需要在几个线程之间传递数据并且控制良好的时间点,是否可以强制所有缓存写回主内存而不是使用易失性字段?
  • 4) 有更好的方法吗?我的概念可能是错误的吗?

感谢您的帮助,并对大量文字感到抱歉。我认为如果您知道预期用途会更容易回答这个问题。

【问题讨论】:

  • 它有点宽泛,但您可以使用现有的游戏循环获得相同的结果,只需跳过用户输入阶段并从某种“队列”中获取输入。问题是知道何时根据用户当前位置注入某些输入事件,但我敢肯定,如果您有一个预定义的路径供用户遵循,您可以跟踪他们的位置以及下一个事件将发生什么需要为了继续沿着这条道路 - 就像一个想法
  • 是的,但是在快速旅行期间窗口会变得无响应。我希望快速旅行尽可能快,这与 CPU cam 进行计算的速度一样快。游戏窗口的更新速度仍然会受到图形管道的限制。
  • 只要你使用的是SMP系统,而不是大规模的NUMA/cluster系统,你就不必关心缓存的一致性。
  • 是否可以 100% 保证所有想玩我的游戏的人在未来多年(也许是十年或两年)内总是拥有 SMP?即使是这样,即使我的担忧是不必要的,我仍然一般对我的问题的答案感兴趣。
  • 你应该阅读java memory model

标签: java multithreading volatile cpu-cache game-loop


【解决方案1】:

由于我只需要在几个线程之间传递数据并且控制良好,是否可以强制所有缓存写回主内存而不是使用易失性字段?

没有。这不是任何工作的方式。让我给你一个非常简短的答案来解释你为什么会以错误的方式思考这个问题:

1.a) 会不会是工作线程在启动后可能会看到旧的游戏数据,因为主线程可能运行在缓存了部分结果的不同内核上?

当然。或者可能是出于其他原因。无法保证内存可见性,因此您不能依赖它,除非您使用有保证的东西来提供内存可见性。

1.b) 如果上述情况属实:我是否需要将游戏数据中的每个字段都声明为 volatile 以绝对保证不会出现不一致的数据?

没有。任何确保内存可见性的方法都可以。您不必以任何特定方式进行操作。

2) 如果所有字段都不稳定,我是否可以假设性能会受到不小的影响?

大概吧。这可能是最糟糕的方法。

3) 由于我只需要在几个线程之间传递数据并且控制良好,是否可以强制所有缓存写回主内存而不是使用易失性字段?

没有。由于没有确保内存可见性的“将缓存写回内存”操作。您的平台甚至可能没有这样的缓存,问题可能完全是另外一回事。您正在编写 Java 代码,您不必考虑您的特定 CPU 是如何工作的,它有哪些内核或缓存,或者类似的事情。这是使用语义得到保证且不涉及内核、缓存或类似内容的语言的一大优势。

4) 有没有更好的方法?我的概念可能是错误的吗?

当然。您正在编写 Java 代码。使用各种 Java 同步类和函数,并依靠它们来证明它们所记录的语义。甚至不要考虑核心、缓存、刷新到内存或类似的事情。这些是硬件细节,作为 Java 程序员,您甚至不必考虑。

您看到的任何关于内核、缓存或刷新到内存的 Java 文档实际上并不是在谈论真正的内核、缓存或刷新到内存。它只是给你一些思考假设硬件的方法,这样你就可以思考为什么内存可见性和总排序本身并不总是完美地工作。您的真实 CPU 或平台可能存在与此假设硬件完全不同的问题。 (现实世界的 CPU 和系统具有由硬件保证的缓存一致性,它们的可见性/排序问题实际上完全不同!)

【讨论】:

    猜你喜欢
    • 2017-08-26
    • 2012-02-09
    • 2014-12-26
    • 1970-01-01
    • 2015-03-15
    • 2011-05-17
    • 1970-01-01
    • 2011-05-07
    • 1970-01-01
    相关资源
    最近更新 更多