Clang在不同平台上比GCC更具确定性吗?

Is Clang more deterministic than GCC across platforms?

本文关键字:确定性 GCC 平台 Clang      更新时间:2023-10-16

我正在考虑用c++编写一款多用户RTS游戏(部分)的可行性。我很快发现,一个硬性要求是游戏模拟必须在服务器和所有客户端之间完全确定,能够将网络通信限制在用户输入,而不是游戏状态本身。因为每个人都有一台不同的电脑,这似乎是一个难题。

那么,有没有一些"神奇"的方法让c++编译器创建一个在Linux(服务器)、Windows和Mac上完全确定的可执行文件?我认为两个主要的OSS c++编译器是GCC和Clang,所以我想知道在这方面是否一个比另一个表现得更好。

我也会对任何可以用来验证c++确定性的测试套件感兴趣。

通过确定性,我的意思是编译后的程序,给定相同的初始状态和相同顺序的输入,将总是产生相同的输出,在它运行的任何平台上。在整个网络中也是如此。对我来说,一致听起来是对这种行为的恰当定义,但我不是英语母语者,所以我可能会误解确切的含义。

[EDIT#2]虽然讨论决定论/一致性是否重要,我是否应该在游戏引擎中瞄准这一点,以及它在c++中通常是一个多大的问题,这很有趣,但它并没有以任何方式真正回答这个问题。到目前为止,没有人告诉我应该使用Clang还是GCC来获得最可靠/确定/一致的结果。

[EDIT#3]我突然想到,有一种方法可以在c++中获得与Java完全相同的结果。必须采用JVM的开源实现,并提取实现运算符和数学函数的代码。然后将其转换为独立库并调用其中的可内联函数,而不是直接使用操作符。手工操作会很痛苦,但如果生成了代码,那么它就是一个完美的解决方案。也许这甚至可以通过类和操作符重载来实现,这样看起来也很自然。

由于每个人都有不同的电脑,这似乎是一个难题。

它不是。实际上,只要您不做任何规范未定义的事情,这种网络就非常简单。IEEE-754对于如何进行浮点数学运算,如何进行舍入等都非常清楚,并且它在各个平台上的实现是相同的。

你不需要做的最重要的事情是在代码中依赖SIMD CPU指令,这需要是确定性的(注意:这是物理,AI等:游戏状态)。而不是图形,这是您需要SIMD的地方)。这些类型的指令使用浮点规则时快时慢。所以你的游戏代码中没有SIMD;只有在"客户端"代码(图形,声音等)。

另外,你需要确保你的游戏状态不依赖于时间之类的东西;每个游戏状态时钟应该是一个固定的时间间隔,而不是基于PC的时钟或其他性质的东西。

显然,您应该避免任何您没有代码的随机函数。但同样,这只适用于你的主要玩法循环;图形内容可以是特定于客户端的,因为它只是视觉效果,无关紧要。

就保持两个游戏状态同步而言,差不多就是这样了。你使用的编译器对你来说不会是一个大问题。

请注意,《星际争霸》和《星际争霸2》将此作为其网络模型的基础。它们都可以在mac和pc上运行,并且都可以相互竞争。所以这是非常可能的,而且不需要Clang。

如果你喜欢Clang,你应该使用它。但这应该是因为你喜欢它,而不是为了社交。

不要依赖未定义或未指定的行为,(特别是不要使用浮点数),这与使用哪种编译器无关。

a = 1, b = 2,则a + b = 3。这是由语言标准保证的。

c++不是一个在某些编译器中某些事情是"确定性的"而在其他编译器中它们不是的地方。c++陈述一些事实(如1 + 2 == 3),并将一些事情留给编译器(如函数参数的求值顺序)。如果程序的输出只依赖于前者(和用户的输入),并且您使用符合标准的编译器,那么给定相同的用户输入,您的程序将始终产生相同的输出。

如果程序的输出也取决于(比如说)用户的操作系统,那么程序仍然是确定性的,只是输出现在由用户的输入和他们的操作系统共同决定。如果您希望输出仅依赖于用户的输入,则由您来确保用户的操作系统不是影响程序输出的因素。一种方法是只依赖语言标准保证的行为,并使用符合该标准的编译器。

总之,所有的代码都是确定性的,这取决于它的输入。你只需要确保输入是由你想要的东西组成的。

我认为你使用哪种编译器并不重要。

《毁灭战士》就使用了这种完全确定性的方法。他们使用的不是随机数生成器,而是固定的"随机数"数组。游戏时间是用游戏内的刻度来衡量的(如果我没记错的话,大概是1/30秒)。

如果你通过游戏机制来衡量一切,而不是将你的工作卸载到一些标准库中,这些库可能有不同的版本,我相信你应该在不同的机器上实现良好的可移植性。当然,前提是这些机器足够快,可以运行您的代码!

然而,网络通信本身也会产生问题:延迟、掉线等。你的游戏应该能够处理延迟的信息,并在必要时重新同步自己。例如,您可能希望不时发送完整(或至少:更详细)的游戏状态,而不是仅依赖于用户输入。

还要考虑可能的漏洞:

  • 客户端:我在扔手榴弹
  • 服务器:你没有手榴弹
  • 客户:我不在乎。尽管如此,我还是在扔手榴弹

这有点愚蠢。您的程序将不是"完全确定的"(不管这意味着什么)。在大端序机器和小端序机器上,也不是在64位机器和32位机器和其他随机机器上,"直到最后一位"。

说到随机性,许多游戏都带有随机性元素。如果您通过调用c标准函数rand()来实现这一点,那么所有的赌注都将取消。

如果你开始使用浮点数,你所有的赌注都取消了。你会遇到很难找到/修复的问题,你得到不同的值,即使在同一平台上,只是通过选择英特尔或AMD的cpu。

许多运行时库都针对不同的芯片优化了代码路径。这些都在规格范围内,但有些比这更精确。这将导致细微的舍入错误,这些错误迟早会累积成可能破坏事情的差异。

你的目标应该是摆脱100%的确定性。毕竟:对于玩家来说,如果对手比应该向左多一个像素会有什么影响吗?事实并非如此。重要的是,客户端和服务器之间的微小差异不会破坏游戏玩法。

玩家在屏幕上看到的内容应该是确定的,这样他就不会觉得被欺骗了,但这并不是必须的。

我所参与的游戏通过不断地在所有客户端之间重新同步所有实体的游戏状态来存档。然而,我们几乎从未发送过整个游戏状态,但我们每帧发送几个对象的游戏状态,将任务分配到几秒钟内。

只给对象最重要的优先级高于其他的,这将是好的。例如,在赛车游戏中,如果对手的汽车离你很远,那么它的确切位置就不重要了,每隔20秒左右更新一次就可以了。

在这些更新之间,只要相信小的舍入错误不会累积太多而导致麻烦。