.NET Core 中游戏开发的浮点确定性 [英] Floating point determinism for gamedev in .NET Core

查看:34
本文介绍了.NET Core 中游戏开发的浮点确定性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在使用 C# 和 .NET Core 开发 RTS 游戏引擎.与大多数其他实时多人游戏不同,RTS 游戏倾向于通过将玩家输入同步到其他玩家,并同时在所有客户端上同步运行游戏模拟来工作.这要求游戏逻辑具有确定性,以免游戏不同步.

We're working on an RTS game engine using C# and .NET Core. Unlike most other real-time multiplayer games, RTS games tend to work by synchronizing player inputs to other players, and running the game simulation in lockstep on all clients at the same time. This requires game logic to be deterministic so that games don't get out of sync.

非确定性的一个潜在来源是浮点运算.从我收集到的主要问题是旧的 x87 FPU 指令 - 它们使用内部 80 位寄存器,而 IEEE-754 浮点值是 32 位或 64 位,因此从寄存器移动时值会被截断记忆.代码和/或编译器的小改动可能会导致截断在不同时间发生,从而导致结果略有不同.非确定性也可能因意外使用不同的 FP 舍入模式而引起,但如果我理解正确,这主要是一个已解决的问题.

One potential source of non-determinism are floating point operations. From what I've gathered the primary issue is with the old x87 FPU instructions - they use an internal 80-bit register, while IEEE-754 floating point values are 32-bit or 64-bit, so values are truncated when moved from registers to memory. Small changes to code and/or the compiler can result in truncation happening at different times, resulting in slightly different results. Non-determinism can also be caused by accidentally using different FP rounding modes, though if I understood correctly this is mostly a solved issue.

我还印象 SSE(2) 指令不会受到截断问题的困扰,因为它们在 32 位或 64 位中执行所有浮点运算,而无需更高精度的寄存器.

I've also gotten the impression that SSE(2) instructions do not suffer from the truncation issue, as they perform all floating point arithmetic in 32- or 64-bit without a higher precision register.

最后,据我所知,CLR 在 x86 上使用 x87 FPU 指令(或者至少在 RyuJIT 之前是这样),在 x86-64 上使用 SSE 指令.我不确定这是否适用于所有或大多数操作.

Finally, as far as I know the CLR uses x87 FPU instructions on x86 (or that was at least the case before RyuJIT), and SSE instructions on x86-64. I'm not sure if that means for all or most operations.

支持准确的单精度数学 最近已添加到 .NET Core(如果有的话).

Support for accurate single precision math has recently been added to .NET Core, if that matters.

但是在研究是否可以在 .NET 中确定性地使用浮点数时,有很多答案都说不可以,尽管它们主要涉及运行时的旧版本.

But when researching whether or not floating point can be used deterministically in .NET there are a lot of answers that say no, although they mostly concern older versions of the runtime.

  • 在 2013 年的 StackOverflow 回答中,埃里克·利珀特 (Eric Lippert) 说,如果您想保证 .NET 中的算术可重复,您应该使用整数".
  • 在 Roslyn 的 GitHub 页面上的讨论中,游戏开发者说在 2017 年的评论中,他们无法在 C# 中实现可重复的浮点运算,尽管他没有指定他们使用的运行时.
  • 在 2011 年的游戏开发堆栈交换 answer 中,作者得出结论,他无法在 .NET 中获得可靠的 FP 算法.他为 .NET 提供了基于软件的浮点实现,该实现与 IEEE754 浮点二进制兼容.
  • In a StackOverflow answer from 2013 Eric Lippert said that if you want to guarantee reproducible arithmetic in .NET, you should "Use integers".
  • In a is discussion about the subject on Roslyn's GitHub page a game developer said in a comment in 2017 that they were unable to reach repeatable floating point operations in C#, though he did not specify which runtime(s) they used.
  • In a 2011 Game Development Stack Exchange answer the author concludes that he was unable to attain reliable FP arithmetic in .NET. He provides a software-based floating point implementation for .NET, which is binary compatible with IEEE754 floating point.

那么,如果 CoreCLR 在 x86-64 上使用 SSE FP 指令,这是否意味着它不会受到截断问题和/或任何其他与 FP 相关的不确定性的影响?我们将随引擎一起发布 .NET Core,因此每个客户端都将使用相同的运行时,并且我们要求玩家使用完全相同版本的游戏客户端.将引擎限制为仅适用于 x86-64(在 PC 上)也是可以接受的限制.

So, if CoreCLR uses SSE FP instructions on x86-64, does that mean that it doesn't suffer from the truncation issues, and/or any other FP-related non-determinism? We are shipping .NET Core with the engine so every client would use the same runtime, and we would require that the players use exactly the same version of the game client. Limiting the engine to only work on x86-64 (on PC) is also an acceptable limitation.

如果运行时仍然使用具有不可靠结果的 x87 指令,那么使用软件浮点实现(如上面答案中链接的那个)来计算关于单个值的计算是否有意义,并使用新的 SSE 加速向量操作 硬件内在函数?我已经制作了这个原型,它似乎可以工作,但是没有必要吗?

If the runtime still uses x87 instructions with unreliable results, would it make sense to use a software float implementation (like the one linked in an answer above) for computations concerning single values, and accelerate vector operations with SSE using the new hardware intrinsics? I've prototyped this and it seems to be work, but is it unnecessary?

如果我们只能使用普通的浮点运算,有什么我们应该避免的,比如三角函数吗?

If we can just use normal floating point operations, is there anything we should avoid, like trigonometric functions?

最后,如果到目前为止一切正常,当不同的客户端使用不同的操作系统甚至不同的 CPU 架构时,这将如何工作?现代 ARM CPU 是否存在 80 位截断问题,或者相同的代码是否会与 x86 运行相同(如果我们排除三角学等棘手的东西),假设实现没有错误?

Finally, if everything is OK so far how would this work when different clients use different operating systems or even different CPU architectures? Do modern ARM CPUs suffer from the 80-bit truncation issue, or would the same code run identically to x86 (if we exclude trickier stuff like trigonometry), assuming the implementation has no bugs?

推荐答案

那么,如果 CoreCLR 在 x86-64 上使用 SSE FP 指令,这是否意味着它不会受到截断问题和/或任何其他与 FP 相关的不确定性的影响?

So, if CoreCLR uses SSE FP instructions on x86-64, does that mean that it doesn't suffer from the truncation issues, and/or any other FP-related non-determinism?

如果您继续使用 x86-64 并且在任何地方都使用完全相同版本的 CoreCLR,那么它应该是确定性的.

If you stay on x86-64 and you use the exact same version of CoreCLR everywhere, it should be deterministic.

如果运行时仍然使用结果不可靠的 x87 指令,使用软件浮点实现是否有意义[...] 我已经对此进行了原型设计,它似乎可以工作,但是否没有必要?

If the runtime still uses x87 instructions with unreliable results, would it make sense to use a software float implementation [...] I've prototyped this and it seems to be work, but is it unnecessary?

这可能是解决 JIT 问题的解决方案,但您可能需要开发 Roslyn 分析器以确保您不使用浮点运算而不经过这些……或者编写一个 IL 重写器为您执行此操作(但这会使您的 .NET 程序集依赖...这取决于您的要求是可以接受的)

It could be a solution to workaround the JIT issue, but you will likely have to develop a Roslyn analyzer to make sure that you are not using floating point operations without going through these... or to write an IL rewriter that would perform this for you (but that would make your .NET assemblies arch dependent... which could be acceptable depending on your requirements)

如果我们只能使用普通的浮点运算,有什么我们应该避免的,比如三角函数吗?

If we can just use normal floating point operations, is there anything we should avoid, like trigonometric functions?

据我所知,CoreCLR 正在将数学函数重定向到编译器 libc,所以只要您保持在同一版本、同一平台上,应该没问题.

As far as I know, CoreCLR is redirecting math functions to the compiler libc, so as long as you stay on the same version, same platform, it should be fine.

最后,如果到目前为止一切正常,当不同的客户端使用不同的操作系统甚至不同的 CPU 架构时,这将如何工作?现代 ARM CPU 是否存在 80 位截断问题,或者相同的代码是否会与 x86 运行相同(如果我们排除三角学等棘手的东西),假设实现没有错误?

Finally, if everything is OK so far how would this work when different clients use different operating systems or even different CPU architectures? Do modern ARM CPUs suffer from the 80-bit truncation issue, or would the same code run identically to x86 (if we exclude trickier stuff like trigonometry), assuming the implementation has no bugs?

您可能会遇到一些与额外精度无关的问题.例如,对于 ARMv7,次正规浮点数被刷新为零,而 aarch64 上的 ARMv8 将保留它们.

You can have some issues not related to extra precision. For example, for ARMv7, subnormal floats are flushed to zero while ARMv8 on aarch64 will keep them.

因此,假设您继续使用 ARMv8,我不太清楚 ARMv8 的 JIT CoreCLR 在这方面的表现;您可能应该直接在 GitHub 上询问.仍然存在可能破坏确定性结果的 libc 行为.

So assuming that you are staying on ARMv8, I don't know well if the JIT CoreCLR for ARMv8 is behaving in that regard; you should probably ask on GitHub directly. There is still also the behavior of the libc that would likely break deterministic results.

我们正致力于在 Unity 的突发"编译器上解决这个问题,以将 .NET IL 转换为本机代码.我们在所有机器上使用 LLVM 代码生成,禁用一些可能破坏确定性的优化(所以在这里,总的来说我们可以尝试保证编译器跨平台的行为),我们还使用 SLEEF 库来提供确定性计算数学函数(参见例如 https://github.com/shibatch/sleef/issues/187)...所以可以这样做.

We are working exactly at solving this at Unity on our "burst" compiler to translate .NET IL to native code. We are using LLVM codegen across all machines, disabling a few optimizations that could break determinism (so here, overall we can try to guarantee the behavior of the compiler across the platforms), and we are also using the SLEEF library to provide deterministic calculation of mathematical functions (see for example https://github.com/shibatch/sleef/issues/187)… so it is possible to do it.

在您的立场上,我可能会尝试调查 CoreCLR 是否真的对 x64 和 ARMv8 之间的普通浮点运算具有确定性……如果看起来没问题,您可以调用这些 SLEEF 函数而不是 System.Math,它可以开箱即用,或者建议 CoreCLR 从 libc 切换到 SLEEF.

In your position, I would probably try to investigate if CoreCLR is really deterministic for plain floating point operations between x64 and ARMv8… And if it looks okay, you could call these SLEEF functions instead of System.Math and it could work out of the box, or propose CoreCLR to switch from libc to SLEEF.

这篇关于.NET Core 中游戏开发的浮点确定性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆