2.6K Views
November 27, 23
スライド概要
■Overview
In RE ENGINE, all game logic is written in the C# programming language and runs on a proprietary virtual machine called REVM.
We will discuss the benefits of replacing our long-maintained C# 7.3/.NET Framework 4.8 equivalent environment with C# 8.0/.NET, what comes next after doing so, as well as a few details of REVM's implementation.
Note: This is the contents of the publicly available CAPCOM Open Conference Professional RE:2023 videos, converted to slideshows, with some minor modifications.
■Prerequisites
Assumes basic knowledge of the C# language.
I'll show you just a little bit of the content !
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CAPCOM Open Conference Professional RE:2023
https://www.capcom-games.com/coc/2023/
Check the official Twitter for the latest information on CAPCOM R&D !
https://twitter.com/capcom_randd
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
株式会社カプコンが誇るゲームエンジン「RE ENGINE」を開発している技術研究統括によるカプコン公式アカウントです。 これまでの技術カンファレンスなどで行った講演資料を公開しています。 【CAPCOM オープンカンファレンス プロフェッショナル RE:2023】 https://www.capcom-games.com/coc/2023/ 【CAPCOM オープンカンファレンス RE:2022】 https://www.capcom.co.jp/RE2022/ 【CAPCOM オープンカンファレンス RE:2019】 http://www.capcom.co.jp/RE2019/
C# 8.0 / .NET Support for Game Code and the Future I will start the session on C#8.0/.NET support for game programs and its future. ©CAPCOM 1
Introduction Supporting a newer C# language version has numerous advantages Ideally, we want our environment to support the latest version Supporting a newer language version in the C# scripting system requires • Replacing .NET classes with corresponding implementations • Implementation of new language features Moving from NET Framework to .NET has corresponding labor costs In this session, we will discuss RE ENGINE‘s C# scripting system. Newer versions of the C# language have many advantages, such as more ways to write scripts, so it is preferable to have1 the latest version available. So, with that said, why did we choose C# 8.0? In order to support a higher version of C#, you need to reimplement the relevant .NET classes, and add support for any new language features. Before this undertaking, we were using .NET Framework's C# 7.3. That meant that to support a C# version later than 8.0, we had to move to .NET. Therefore, the cost of supporting the newest language version would have been even higher, so we decided to migrate to C#8.0, which was the first version available in .NET. ©CAPCOM 2
Agenda C# Scripting System Environment Prior to Migration to C#8.0/.NET and Migration Issues Post-migration Environment Summary and Future Outlook This is the agenda. In the first half of the presentation, we will discuss RE ENGINE's C# scripting system and the challenges that existed when 2 migrating to C# 8.0/.NET. In the second half, we will discuss the benefits gained as a result of the migration and our outlook for the future. ©CAPCOM 3
About the C# Scripting System First, let's talk a little about RE ENGINE's C# scripting system. 3 ©CAPCOM 4
About the Operating Environment for C# Scripts All game logic written in C# language No title-specific C++ code, no unsafe code allowed • • Crash: Basically, if it happens, the cause is on the engine side Exceptions: Cause is on the title side if thrown Runs on its own virtual machine (REVM) Operates with its own garbage collection (FrameGC) This is about the operating environment for C# scripts. All game logic developed in RE ENGINE is written in the C# language. There is no title-specific C++ code, and unsafe code is prohibited. 4 This is to distinguish between crashes caused by the engine and exception throws caused by the title, so that code that may crash cannot be written on the title side. The system also runs on its own virtual machine, REVM, and has its own implementation of garbage collection. ©CAPCOM 5
What is REVM? JIT compilation not supported Convert C# code to IL and compile AOT Pre-deploy generics, etc. and link them statically to speed up execution During development: Prioritize iteration speed, output MicroCode In production: Prioritize execution performance, output C++ code (IL2CPP) C# C# Compilation (Roslyn) Development MicroCode Production C++ AOT compilation REVM does not support JIT compilation. Instead, it converts C# code to IL and then performs its own AOT compilation to speed up execution. 5 During development, priority is given to iteration speed, and MicroCode is output and executed on the interpreter. In production, priority is given to execution performance, and C++ code is output and executed after build. ©CAPCOM 6
About the C# Scripting System Reference materials [CEDEC2016] ラピッドイテレーションを実現するゲームエンジンの設計 [CAPCOM RE:2019] Architecture of RE ENGINE : Creating an All-Purpose Engine REVM has been presented in the past at CEDEC2016 and RE:2019, so please refer to those for more information. 6 ©CAPCOM 7
Environment Prior to Migration to C#8.0/.NET and Migration Issues I will then discuss what the environment was like before the transition to C# 8.0 and the challenges of the migration. 7 ©CAPCOM 8
Environment Prior to Migration to C#8.0/.NET and Migration Issues C# 7.3/.NET Framework 4.8 (equivalent) since May 2018 Standard libraries of the .NET Framework era Import necessary functions from CoreCLR and CoreFx Migration issues • • • Replacing internals with .NET Core 3.1 implementation Types that depend on JIT Impact on all existing titles Prior to the transition, We had been maintaining an equivalent environment to C# 7.3/.NET Framework 4.8 since May 2018 when it was released. 8 This was accomplished by importing the necessary functionality from CoreCLR and CoreFx during the .NET Framework era. There were various challenges in migrating such an environment to C#8.0/.NET. I'll explain each challenge in detail. ©CAPCOM 9
Issue 1: Replacing Internals with .NET Core 3.1 Implementation C#8.0 is not available in the .NET Framework environment C# .NET Framework .NET All versions .NET Standard 1.x .NET Standard 2.0 .NET Core 2.x N/A .NET Standard 2.1 .NET Core 3.x 7.3 8.0 Adopt .NET Core 3.1, the latest in the C#8.0 environment Issue #1 is replacing the internal implementation with .NET Core 3.1. Implementing C#8.0 functionality required switching to a .NET Standard 2.1 or higher environment, as it is not available9in the .NET Framework. The environment we actually went with was .NET Core 3.1, the latest environment in which C#8.0 can be used. To transfer RE ENGINE's C# scripting system to this environment, ©CAPCOM 10
Issue 1: Replacing Internals with .NET Core 3.1 Implementation About 300 files imported from CoreCLR and CoreFx needed to be replaced with .NET Core3.1 implementations Some language features are not available in .NET Framework's C#7.3 Stackalloc for Span<T> type Error because Span<T> does not exist in .NET Framework We had to replace all files imported from CoreCLR and CoreFx. It works out to about 300 files, but it's not just a case of overwriting them and calling it a day. 10 There are features that can't be used in .NET Framework C#7.3 because the relevant classes don't exist. Therefore, we had to add those new classes and functions for .NET. ©CAPCOM 11
Issue 1: Replacing Internals with .NET Core 3.1 Implementation Proprietary implementations exist for optimization and to support our VM and GC Example: Basic functions of System.Array are implemented in C++ IL2CPP Replace function Also, there are custom implementations mixed in for optimization and to support our VM and GC, and we had to take all of these into consideration as we proceeded. 11 For example, the basic functions of System.Array are implemented in C++ with InternalCall, and some of them can be optimized by replacing the functions themselves during IL2CPP. ©CAPCOM 12
Issue 2: Classes that Depend on JIT REVM does not support JIT compilation System.Span<T>, System.ReadOnlySpan<T> Implements functionality (reference field) that is not usually available https://github.com/dotnet/coreclr/blob/v3.1.27/src/System.Private.CoreLib/shared/System/Span.Fast.cs No implementation https://github.com/dotnet/coreclr/blob/v3.1.27/src/System.Private.CoreLib/shared/System/ByReference.cs Issue #2 is that some classes depend on JIT. As a reminder, JIT compilation is not supported in the REVM. 12 We needed to make classes that depend on JIT compilation work in this environment. The System.Span<T> and ReadOnlySpan<T> classes, which are essential for language functions, depend on JIT compilation. ByReference<T> class, which Span<T> class has as a member, depends on JIT compilation because it is a class to realize functions that are not available in the C#8.0. If we try to run it with REVM as it is, an exception will occur when the Span<T> class is generated, and it will not work properly. Therefore, some measures were necessary. ©CAPCOM 13
Issue 3: Impact on All Existing Titles RE ENGINE supports compatibility with all existing titles Maintain compatibility: Behavioral changes are unacceptable .NET allows breaking changes Performance: both speed and memory usage must not be adversely affected May differ from .NET performance due to proprietary implementation of REVM Issue #3 is the impact on all existing titles. RE ENGINE operates to support compatibility with all existing titles. 13 This means we had to implement it such that no title's behavior would change. However, because .NET allows breaking changes, it was necessary to reproduce the original behavior of imported features that have been modified. In addition, performance must not be adversely affected, as this would inconvenience titles under development. Even though .NET may have improved performance, the original implementation of the REVM may have performed better. Therefore, each of the related parts of the original implementation needed to be verified. ©CAPCOM 14
Issue Summary To migrate from C# 7.3 to C# 8.0 we need to switch to a .NET Core 3.1 implementation • • • Performance needs to be improved to account for changes in proprietary implementations Need to implement Span<T> which depends on JIT compilation Need to maintain compatibility I'll summarize the issues. To migrate from C# 7.3 to C# 8.0, it is necessary to replace it with the .NET Core 3.1 implementation. 14 To do so, performance improvements must be made to account for the unique implementation of REVM so as not to affect titles currently under development. We need to implement our own versions of classes that rely on JIT compilation because they are needed for language functionality. Operational behavior must be maintained to ensure compatibility with all titles. Due to these many challenges, we had not been able to migrate for a long time. ©CAPCOM 15
Post-migration Environment I will now tell you the benefits we have gained as a result of migrating and addressing all the issues. 15 ©CAPCOM 16
Performance Comparison Comparing performance without changes to game logic Comparison: Devil May Cry 5 Special Edition (PS5) Let's compare the performance of Devil May Cry 5 Special Edition without changing any of its game logic. Since the game has already been released, the game logic has been optimized considerably. ©CAPCOM 16 17
Performance Comparison: Speed Automatic play, early game against Urizen Before migration After C#8.0/.NET migration approx. Max Time: 2.83 ms Max Time: 2.72 ms 4% faster First, let's look at the execution speed. We will run the early-game Urizen battle in auto play and compare almost the same instant in play. 17 We can see that the maximum time for UpdateBehavior, which represents the processing time of the C# script, has gone from 2.83 ms to 2.72 ms, which is about 4% faster. The reason for the faster speed is that, despite the fact that the game logic has not been changed at all, ©CAPCOM 18
Optimization of the .NET Library
Example: string.Format() is faster
100,000 function calls (PS5 IL2CPP)
Format string, type
Before migration
After C#8.0/.NET migration
"{0:D2}", int type
31.7243ms
15.8984ms Approx. 50% faster
"{0:F2}", float type
51.4866ms
32.4636ms Approx. 37% faster
Result: Speeds up the game logic without changing it in any way
Note: Measurements were taken with some custom implementations included
The .NET library itself has been optimized.
string.Format(), for example, shows a 50% improvement in execution speed for a ints and a 37% improvement for floats
18after 100,000
calls to the function with a format string.
There are many such optimizations that speed up the game without changing any of the game logic.
These results and the results for Devil May Cry 5 Special Edition shown earlier were measured with after optimizing parts of REVM that
became slower after the migration.
I'll now introduce some of the optimized implementations that we performed.
©CAPCOM
19
Replacement with Proprietary Implementation
Example: Span<T>, ReadOnlySpan<T> custom implementation
Changed to have addresses as members
Changed to return data from C++
10 million element accesses (PS5 IL2CPP)
int[]
Span<int>
Write
6.30073ms
8.71613ms Approx. 38% slower
Read
2.13456ms
8.97972ms Approx. 420% slower
As an example, let's look at the JIT-dependent Span<T> and ReadOnlySpan<T> classes.
These classes can be handled much like arrays, and are used a lot internally since .NET.
19
By modifying it to have a pointer address as a member, and returned the data from C++, we avoided the JIT dependency.
Comparing 10 million element accesses between the array and the Span<T> class, the Span<T> class is about 38% slower when writing
and 420% slower when reading.
This is due to the fact that REVM arrays are proprietary implementations from the beginning.
©CAPCOM
20
Span<T>, ReadOnlySpan<T> Optimization Further optimization during IL2CPP (to an implementation almost equivalent to Span<T> in C#11) IL2CPP Explicitly inline 10 million element accesses (PS5 IL2CPP) int[] Span<int> Write 6.30073ms 6.14248ms Approx. 3% faster Read 2.13456ms 2.12426ms Approx. 0.5% faster The Span<T> class is used a lot in .NET, so if it is slow, the entire game will be slow. We can optimize it at the IL2CPP stage. When encountering code that accesses elements of a Span<T> class, explicitly inlining has performance benefits. 20 This implementation is similar to the Span<T> class in C#11. Comparing 10 million element accesses between an array and the Span<T> class, the Span<T> class is now about 3% faster when writing and about 0.5% faster when reading. By implementing various optimizations like this, we can speed up the overall game. ©CAPCOM 21
Performance Comparison: Memory Maximum memory usage after running from initial startup to game clear in autoplay Max used: about 0.3% improvement Max used: about 0.4% improvement Max used: about 1.8% improvement Before migration After C#8.0/.NET migration Next let's look at memory. Let's compare the maximum memory usage after running the game from the first startup to the game clear in auto play. 21 RE ENGINE also has its own memory allocator, which has different categories based on usage. Default, where memory allocated by C# scripts is placed, improved the maximum usage by about 0.3%. Permanent, which contains static memory, improved the maximum usage by about 0.4%. Physical, which represents the sum of all CPU memory, shows an improvement of about 1.8% in maximum usage. The fact that the total value is greater than the sum of the improved values for Default and Permanent also indicates that fragmentation has also improved. The reason for the improvement in memory is, ©CAPCOM 22
stackalloc Now Available Able to use stackalloc for Span<T> to prevent fragmentation Changed only this line 10 million allocations (PS5 IL2CPP) Speed Memory Allocation ArrayAlloc 180.647ms 4.9 GB SpanAlloc 170.618ms Approx. 5.5% faster No memory allocation The implementation of the Span<T> class has enabled the use of stackalloc for the Span<T> class. The implementation in the .NET library has also been optimized to actively use stackalloc, which improves fragmentation. 22 Compare these two functions, ArrayAlloc() and SpanAlloc(), which allocate an array and execute a for statement. Although only one line is changed, when each function is executed 10 million times, SpanAlloc() is 5.5% faster and does not require 4.9 GB of memory allocation. Since fragmentation is often a problem in games, reducing the number of memory allocations is a big advantage. Since this feature is also available in the game logic, we can expect to see further improvements in fragmentation in titles currently under development. ©CAPCOM 23
C# Compiler Optimization
Example: static ReadOnlySpan<T> allows memory reduction
InitializeArray: copies
Constructor: doesn't copy (because it can be judged as read-only)
Memory usage has also improved due to new C# compiler optimizations.
Let's compare a static byte array with a static ReadOnlySpan<byte> class.
23
The contents of the functions described are identical, but looking at IL, the functions called are completely different.
While InitializeArray(), which is called on an array, is copied from the initial value data, the ReadOnlySpan<byte> class is not.
The C# compiler can determine that it is read-only, so it is optimized to refer directly to the initial value data, and the memory usage is
limited to the initial value data.
©CAPCOM
24
Benefits Only Attainable with New Implementations New Classes and Functions New Syntax Now I will focus for a bit on advantages that can be only be obtained thanks to new implementations. 24 ©CAPCOM 25
New Classes and Functions
Example: string.Create()
Function that can generate and write to a string of specified length at the same time
Span<T> and SpanAction<char,TState> added in .NET, are required
100,000 function calls (PS5 IL2CPP)
Speed
Memory Allocation
StringConcat
48.9616ms
Approx. 74.2MB
StringCreate
4.87977ms
Approx. 90% faster
Approx. 4.9MB
Approx. 95% reduction
New classes and functions have been added.
As an example, let's look at the Create function added to the string class. This function can generate a string with a specified
length and
25
write to it at the same time.
The Span<T> and SpanAction<char,TState> classes are newly added to .NET and implemented.
We'll create a string, "0123456789", with a for statement. Compare the two test functions, StringConcat() and StringCreate().
After 100,000 function calls, the speed is about 90% faster and the amount of memory allocated is about 95% less, a significant
performance improvement.
New classes and functions with more sophisticated processing and can provide significant benefits simply by using them in new
implementations.
©CAPCOM
26
New Syntax Example: Amount of code can be reduced C#7.3 or earlier C#8.0 or later Null coalescing assignment operator Range operator Property pattern matching New description methods are also available. The null coalescing assignment operator. 26 Range operators. Property pattern matching. Various features have been added to reduce the amount of code that needs to be written. Each of these is a small change, but if you can reduce the amount of code, you can spend more time implementing and debugging, which will improve the quality of your game. ©CAPCOM 27
Summary and Future Outlook Finally, a summary and our future plans. 27 ©CAPCOM 28
Summary Simply switching to the new C#/.NET improves performance Optimization by changing the internal implementation of the .NET library New optimizations with C# compiler New language features New classes and functions open up possibilities for optimization New features of the language make writing code easier In environments with proprietary implementations, migration costs will be incurred, but the many benefits will outweigh them Now, to summarize. Simply switching to a newer version of C#/.NET can improve performance. 28 This is due to a variety of factors, including implementation optimizations in the .NET library, optimizations by the C# compiler, new language features, etc. The availability of new classes and functions also increases the potential for optimization. In addition, new language features reduce the amount of code to be written, making implementation easier. In environments with proprietary implementations, such as RE ENGINE, the transition costs are more than offset by the many benefits. ©CAPCOM 29
About the Future Plans to move to C#11/.NET 7, currently the latest version, by the end of this fiscal year C#9.0 Reduction of boiler plate code with record types global using, file scope namespace to reduce code C#10 Common implementation through generic type numerical operations C#11 .NET5–7 Adding new classes and functions Collections, Linq, etc. to optimize existing functions As for the future, RE ENGINE will be migrated to the latest C#11/.NET 7 environment within this fiscal year. The migration will provide the following advantages: Reduction of boilerplate code with record types, 29 Reduction of code volume with global using and file scope namespaces, Common processing with generic type numerical operations, Addition of more classes and functions, Optimization of existing features such as Collections, LINQ, etc., and so on. The advantages of the new system will definitely be useful for game development in the future. ©CAPCOM 30
That's all That is all. Thank you for your attention. 30 ©CAPCOM 31