.NET Core中Gamedev的浮点确定性 [英] Floating point determinism for gamedev in .NET Core

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

问题描述

我们正在使用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指令产生不可靠的结果,那么使用软件浮点实现(如上面的答案中链接的那个)进行有关单个值的计算是否有意义,并使用新的

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分析器,以确保在不进行这些操作的情况下不要使用浮点运算...或者编写将为您执行此操作(但是这将使您的.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中Gamedev的浮点确定性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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