当前位置:网站首页>Unity game optimization (version 2) learning record 8
Unity game optimization (version 2) learning record 8
2022-06-22 16:08:00 【Salted fish never turn over】
Unity Game optimization [ The second edition ] Learning record 8
- The first 8 Chapter Master memory management
- One 、Mono platform
- Two 、 The code to compile
- 3、 ... and 、 Analyze memory
- Four 、 Memory management performance enhancements
- 1、 Garbage collection strategy
- 2、 Manual JIT compile
- 3、 Value type and reference type
- 4、 String connection
- 5、 Packing (Boxing)
- 6、 The importance of data layout
- 7、Unity API In the array
- 8、 Use... For dictionary keys InstanceID
- 9、foreach loop
- 10、 coroutines
- 11、 Closure
- 12、.NET Library function
- 13、 Temporary work buffer
- 14、 Object pool
- 15、 Precast tank
- 16、IL2CPP Optimize
- 17、WebGL Optimize
- 5、 ... and 、Unity、Mono and IL2CPP The future of
The first 8 Chapter Master memory management
One 、Mono platform
Memory domain
The first memory domain —— Managed domain . The domain is Mono Where the platform works , Anything we write MonoBehaviour Scripts and custom C# Classes instantiate objects in this domain at runtime , So whatever we write C# The code will clearly interact with this field . It is called a managed domain , Because memory space is automatically managed by garbage collection .
The second memory domain —— Local domain , It's more subtle , Because we only interact with it indirectly .Unity There are some underlying native code functions , It consists of C++ To write , And compile it into different applications according to the target platform . This field is concerned with the allocation of internal memory space , For each subsystem ( Such as rendering pipeline 、 Physical systems 、 User input system ) Allocate resource data ( For example, texture 、 Audio files and grids ) And memory space . Last , It includes GameObject and Component Some local descriptions of important game objects , To interact with these internal systems . This is also most built-in Unity class ( for example Transform and Rigidbody Components ) Where they protect their data .
The third and last memory domain is the external library , for example DirectX and OpenGL library , It also includes many custom libraries and plug-ins included in the project .
1、 Garbage collection
Garbage collector (Garbage Collector,GC) There is an important job , This work ensures that the application does not use too much managed heap memory , Memory that is no longer needed is automatically reclaimed . for example , If you create a GameObject, Then destroy it , that GC This... Will be marked GameObject Memory space used , For future recycling . This is not an immediate process ,GC Only reclaim memory when needed .
Unity The use of Mono In version GC It's a kind of tracking GC, It uses marking and purging policies . The algorithm is divided into two stages : Each allocated object is tracked by an additional data bit . This data bit identifies whether the object is marked ( That is, whether it is quoted ). These tags are set to false, Identifies that it has not been marked ( Not yet quoted ).
When the collection process begins , It sets the identification of the object to true, Mark all objects that are still accessible to the program . The second phase involves iterating over such references , And decide whether it should be recycled based on its tag status .
2、 Memory fragments
When objects of different sizes are allocated and released in alternating order , And allocating a large number of large objects after releasing a large number of small objects , Memory fragmentation will occur .
for example :
(1) The size of an empty heap space is 384 Bytes , Allocated 4 Size is 64 The objects of bytes are A,B,C,D
(2) Later, it was recycled A,C Two objects are freed 128 byte
(3) Then try to allocate 128 Large object of bytes
Which has been released at this time A,C Objects are not contiguous in memory ( adjacent ), At this time, an object larger than two independent spaces cannot be allocated from the heap , This is memory fragmentation .
3、 Runtime garbage collection
When the game requests a new memory allocation ,CPU It takes... To complete the allocation CPU Cycle through the following tasks :
(1) Verify that there is enough contiguous space to allocate new objects
(2) If there is not enough space , Iterate over all known direct and indirect references , Mark whether they are reachable
(3) Iterate over all these references again , Identify unmarked objects for recycling
(4) Iterate over all identification objects , To check if recycling some objects creates enough contiguous space for new objects
(5) without , Request a new memory block from the operating system , To expand the heap
(6) Assign a new object before the newly assigned block , And return it to the caller
4、 Multithreaded garbage collection
GC Running on two separate threads : The main thread and the so-called Finalizer Thread. When calling GC when , It runs on the main thread , And mark the heap memory block for subsequent recycling . This will not happen immediately , from Mono The control of the Finalizer Thread Before memory is finally freed and available for reallocation , It may be delayed for a few seconds .
Can pass Profiler Window Memory Area Of Total Allocated Block observe this behavior ( The green line ). It may take several seconds for the total allocation value to decrease after garbage collection . Because of this delay , You should not rely on the idea that memory is available once it is reclaimed , And so you shouldn't waste time trying to consume the last byte of available memory . It must be ensured that there is some type of buffer for future allocations .
Two 、 The code to compile
When it changes C# Code , And from what I like IDE( Usually MonoDevelop Or more functional Visual Studio) Switch to Unity Editor time , The code will compile automatically . The code will be converted to a common intermediate language (Common Intermediate Language,CIL), It is an abstraction over native code . That's exactly what it is. .NET Support multiple languages —— Each language uses a different compiler , But they all turn into CIL.
stay CIL in , middle CIL The code is actually compiled into native code as needed . This timely local compilation can be done by AOT(Ahead-Of-Time) or JIT(Just-In-Time) Compiler complete , Which one to choose depends on the target platform .
AOT Compilation is a typical behavior of code compilation , It happens before the build process , In some cases, before the program is initialized . Either way , The code is compiled in advance , There is no consumption due to dynamic compilation during subsequent run-time .
JIT Compilation is dynamically executed in a separate thread at run time , And before the instruction is executed . Usually , This dynamic compilation causes the code to , Run a little slower , Because the code must be compiled before execution . However , Just execute the same code block , There is no need to recompile , Instructions are executed by previously compiled native code .
Not all platforms support JIT compile ,Unity A complete list of restrictions is provided at the following URL :
https://docs.unity3d.com/Manual/ScriptingRestrictions.html
IL2CPP
A few years ago ,Unity Technologies Faced with a choice , Or choose to continue to support Unity More and more difficult to keep up with Mono platform , Or you can implement your own script backend ,Unity Technologies Choose the latter , Now there are many platforms to support IL2CPP, It is an intermediate language to C++ For short .
IL2CPP Is a script backend , Is used to Mono Of CIL The output is directly converted to local C++ Code . Because the application is now running native code , So this will lead to performance improvements .
Be careful IL2CPP Automatically in IOS and WebGL Enable... In the project . For other supported platforms ,IL2CPP Can pass Edit | Project Settings | Player | Configure | Scripting Backend Turn on .
Current support can be found at the following URL IL2CPP The list of platforms :
https://docs.unity3d.com/Manual/IL2CPP.html
3、 ... and 、 Analyze memory
1、 Analyze memory consumption
There is no direct control over what happens in the local domain , Because it's useless Unity Engine source code , So you can't directly add code that interacts with it . But it can be controlled indirectly through various script level functions .
Can pass Profiler Window Memory Area Observe how much memory has been allocated , And how much memory is reserved in the memory field . The local memory allocation is shown in the marked Unity Of , You can even use Detailed Mode And sampling the current frame , For more details .
Be careful :Edit Mode The memory consumption under is usually quite different from that of the standalone version , Because various debugging and editor hook data are applied . Use should be avoided Edit Mode Perform benchmarking and measurement .
have access to Profiler.GetRuntimeMemorySize() Method to get the local memory allocation of a specific object .
It can also be used separately at runtime Profiler.GetMonoUsedSize() and Profiler.GetMonoHeapSize() Method to determine the currently used and reserved heap space .
2、 Analyze memory efficiency
The best indicator that can be used to measure the health of memory management is simple observation GC act . The more work it does , The more waste is generated , The worse the performance of the program .
Can be used at the same time Profiler Window CPU Usage Area(GarbageCollector Check box ) and Memory Area(GC Allocated Check box ) To observe GC Workload and execution time . This is relatively simple for some cases , For example, only a temporary small memory block is allocated or destroyed GameObject.
However , Root cause analysis of memory efficiency problems is a challenging and time-consuming operation . When observing GC The peak of behavior , It may be a sign that the previous frame allocated too much memory while the current frame allocated only a little memory , Request at this time GC Scan a lot of fragmented memory , Determine if there is enough space , And decide whether new memory blocks can be allocated . The memory it cleans up may have been allocated a long time ago , Only applications that have been running for a long time , To observe these effects , Even when the scene is relatively idle , There is no sudden trigger GC The obvious reason . To make matters worse ,Profiler Can only point out what happened in the last few seconds , It cannot directly display what data is being deleted .
If you want to make sure there is no memory leak , The test procedure must be carefully and strictly implemented , In the simulation of the usual game scene or create GC When there is too much work to be done in a frame , Observe its memory behavior .
Four 、 Memory management performance enhancements
1、 Garbage collection strategy
One strategy to minimize garbage collection problems is to manually trigger garbage collection at the right time , When it is determined that the player will not notice this behavior, it can secretly trigger garbage collection . Garbage collection can be done through System.GC.Collect() Manual call .
A good opportunity to trigger recycling can be when loading a scene , When the game stops , At the moment after opening the menu interface , When switching scenes , Or when any player can't observe or care about the sudden performance degradation and interrupt the game . It can even be used at runtime Profiler.GetMonoUsedSize() and Profiler.GetMonoHeapSize() Method to determine whether garbage collection needs to be called .
Unity There are several different object types in the engine , They achieve IDisposable Interface class , for example :NetworkConnection,WWW,UnityWebRequest,UploadHandler,DownloadHandler,VertexHelper,CullingGroup,PhotoCapture,VideoCature,PhraseRecognizer,GestureRecognizer,DictationRecognizer,SurfaceObsercer etc. . These are tool classes for pulling large data sets , Can call script code Dispose() Method , It can ensure that the memory buffer is released in time when necessary .
2、 Manual JIT compile
If JIT Compilation causes runtime performance degradation , Note that it is actually possible to force the method through reflection at any time JIT compile . Reflection is C# A useful feature of language , It allows the code base to probe its own type information 、 Method 、 Values and metadata . Using reflection is often a very expensive process , You should avoid at run time , Or even used only at initialization or other load times . Failure to do so could easily lead to serious CPU Peak and game Caton .
You can use reflection to force... Manually JIT Compile a method , To get the function pointer :
var method = typeof(MyComponent).GetMethod(“MethodName”);
if (method != null)
{
Method.MethodHandler.GetFunctionPointer();
Debug.Log(“JIT compilation complete!”);
}
The previous code is only for public Methods useful . obtain private or protected Method can be used by BindingFlags complete .
This type of method should only be allowed to run when it is determined JIT Compilation will result in CPU Where the peak is .
3、 Value type and reference type
.NET Framework There are concepts of value types and reference types , And when GC After executing the tag - When clearing the algorithm , Only reference types need to be GC Mark . Big data sets , Objects instantiated from classes are all reference types . This also includes arrays 、 entrust 、 All the classes .
Reference types are usually allocated on the heap , Value types can be allocated on the stack or heap . Such as bool、int and float These basic data types are examples of value types . These values are usually allocated on the stack , But once the value type is included in the reference type , For example, classes or arrays , This implies that the value is too large for the stack , Or exist longer than the current scope , So it must be allocated on the heap , Bound to the reference type that contains it .
Pass by value and pass by reference
Technically speaking , Each time the value of the data is passed from one method to another as a parameter , Always copy something , Whether it is a value type or a reference type . Data passed into the object , It is often called passing by value . Instead, just copy the reference to another parameter , Is called passing by reference .
An important difference between a value type and a reference type is that a reference type is simply a pointer to another location in memory , It only consumes 4 or 8 byte (32 Bit or 63 position , It depends on the architecture ), Whatever it really points to .
Data can also be used ref keyword , Pass by reference , But this is very different from the concept of value and reference types . Value types can be passed by value or reference , You can also pass reference types by value or reference . This means that according to the traditional type and whether to use ref keyword , Yes 4 Different data transfer situations in .
A structure is a value type
Struct The type is C# An interesting special case in .struct A type is a value type ,class Type is reference type .
If the only purpose of using a class is to send a data block somewhere in the middle of the program , And the duration of the data block does not need to exceed the current scope , Then you can use struct Type instead of . for example :
public class Test
{
public int a;
public bool b;
public float c;
…
}
You can change to :
public struct Test
{
public int a;
public bool b;
public float c;
…
}
Only will Test From the definition of class Change the type to struct type , You can save a lot of unnecessary garbage collection , Because the value type is allocated on the stack , Reference types are allocated on the heap .
This is not a one size fits all solution . Because the structure is a value type , It will copy the entire data block , And passed to the next method on the call stack , Regardless of the size of the data block . To solve this problem , have access to ref Keywords are passed by reference struct object , To minimize the amount of data per replication ( Copy only one pointer ). However , It could be dangerous , Because passing a structure by reference will allow subsequent method modifications struct object , In this case, it is best to set the data to read-only .
Array and reference types
An array is a reference type .
This means that the following code will cause heap allocation :
TestStruct[] dataObj = new TestStruct[1000];
for(int i = 0;i < 1000;++i)
{
dataObj[i].data = i;
DoSomething(dataObj[i]);
}
However , The following code functions the same , But it does not result in any heap allocation , Because of the struct Objects are value types , Create... On the stack :
for(int i = 0;i < 1000;++i)
{
TestStruct dataObj = new TestStruct();
dataObj[i].data = i;
DoSomething(dataObj[i]);
}
Be careful , When allocating an array of reference types , Is to create an array of references , Each reference can refer to another location on the heap . However , When allocating an array of value types , Is to create a compressed list of value types on the heap . Each value type cannot be set to null, Will be initialized to 0, Each reference to the array of reference types is initialized to null, Because it hasn't been assigned yet .
String is an immutable reference type
A string is essentially an array of characters , So they are reference types , Follow all the same rules as other reference types : They are allocated on the heap , When copying from one method to another, the only thing copied is the pointer . Because the string is an array , This implies that the characters it contains must be contiguous in memory . However , We often expand 、 Join or merge strings , To create other strings . This can lead us to make false assumptions about how strings work . We may assume that , Because strings are so common 、 Omnipresent objects , Performing operations on them is fast and low-cost . Unfortunately , This is wrong . Strings are not fast , It's just convenient .
String object classes are immutable , This means that they cannot change after allocating memory . therefore , When changing a string , In fact, a new string is allocated on the heap to replace it , The original contents will be copied to the new character array , And modify the corresponding characters as needed , The original string object reference now points to the new string object . In this case , The old string is no longer referenced , Will not mark - Clear the mark in the process , Was eventually GC eliminate .
The following code illustrates the differences between string and normal reference types :
void TestFunction()
{
string testString = “Hello”;
DoSomething(testString);
Debug.Log(testString);
}
void DoSomething(string localString)
{
localString = “world!”;
}
This example will output Hello. It's actually , Because references are passed by value ,DoSomething() scoped local String Variables start by referencing memory and testString The same position , There are two references . However , Once modified localString Value , There will be a little conflict . Strings are immutable , Therefore, they cannot be modified , therefore , An inclusive value must be assigned ”World!” And assign its reference to localString Value , here ”Hello” The number of references to the string becomes 1( Only testString References to variables ). therefore ,testString The value of , call DoSomething() after , A new string will be created on the heap , Then it is recycled , No data has been modified .
If modified DoSomething() Method definition of , adopt ref Keyword a reference to an incoming string , The output will change to ”World!”.
To sum up :
· If you pass a value type by value , You can only modify the copy value of its data .
· If the value type is passed by reference , You can modify the incoming raw data .
· If you pass a reference type by value , You can modify the original referenced object .
· If you pass a reference type by reference , You can modify the original referenced object or dataset .
4、 String connection
void CreateFloatingDamageText(DamageResult result)
{
String outputTest = result.attacker.GetCharacterName() + “ dealt ” +
result.totalDamegeDealt.ToString() + “ ” +
result.damegeType.ToString() + “ damage to ” +
result.defener.GetCharacterName() + “ (” +
result.damageBlocked.ToString() + “ blocked)”
}
This function outputs the following string :
Dwarf dealt 15 Slashing damage to Orc (3 blocked)
This function is filled with some string literals ( Hard coded string allocated during program initialization ), for example ” dealt”” damage to ”” blocked)” etc. , They are the simplest constructs for compilers , The compiler can allocate memory for them in advance . However , Because other local variables are used in the merged string , This string cannot be compiled at build time , So every time a method is called at runtime , Dynamically generate complete strings .
Every time you execute + or += When the operator , A new heap allocation will be made : Merge only one pair of strings at a time , Each time, heap memory is allocated for the new string . next , The result of one merge is sent to the next process , Merge with the next string , And so on , Until the final string object is built .
therefore , The previous example will result in the assignment of 9 Different strings ( Notice that the operators handle from right to left ):
“3 blocked)”
“ (3 blocked)”
“Orc (3 blocked)”
“ damage to Orc (3 blocked)”
“Slashing damage to Orc (3 blocked)”
“ Slashing damage to Orc (3 blocked)”
“15 Slashing damage to Orc (3 blocked)”
“ dealt 15 Slashing damage to Orc (3 blocked)”
“Dwarf dealt 15 Slashing damage to Orc (3 blocked)”
This uses 262 Two characters instead of 49 individual . therefore , This abuse of string concatenation procedures , It wastes a lot of memory , To generate unnecessary strings .
StringBuilder
The traditional view is that , If you know roughly the final size of the result string , Then you can allocate an appropriate buffer in advance , To reduce unnecessary memory allocation . That's exactly what it is. StringBuilder The target of the class .StringBuilder It's actually an object based on a variable string , It works like a dynamic array . It allocates a space , You can copy future string objects into it , And allocate additional space when it exceeds the current size . Of course , The maximum size required should be predicted as far as possible , And allocate a buffer of sufficient size in advance , To avoid expanding the buffer .
When using StringBuilder when , You can call ToString() Method to fetch the result string object . This will still allocate memory for the completed string , But at least one large string is allocated , Instead of using + or += Operator to allocate many smaller strings .
String formatting
If you don't know the final size of the result string , Use StringBuilder It is impossible to generate a buffer of appropriate size . The resulting buffer is either too large ( Waste space ), Or too small , This situation is even worse , Because you have to keep expanding the buffer while generating the complete string . here , It is best to use one of the different formatting methods for various strings .
The string class has 3 A method for generating strings :string.Format()、string.Join()、string.Concat(). Each method operates differently . But the final output is the same . Assign a new string object , Contains the contents of the passed in string object . These are all done in one step , No extra string assignments .
In a given situation , It's hard to say which string generation method is more advantageous , So the easiest way is to use one of the traditional methods described above . Whenever you encounter poor performance using a string manipulation method , You should also try another method , Check if it leads to performance improvements .
5、 Packing (Boxing)
C# Everything is an object , This means that they all inherit from System.Object class . even to the extent that int、float and bool Basic data type , Are implicitly from System.Object In the inheritance , It is itself a reference type . Whenever these value types are implicitly handled in the way objects are handled ,CLR A temporary object is automatically created to store or box internal values , To treat it as a typical reference type object . obviously , This will result in heap allocation , To create a container containing .
Boxing is different from using a value type as a member variable of a reference type . Boxing occurs only when a value type is treated as a reference type by conversion or forced conversion .
for example , The following code will integer variables i Boxing in objects obj in :
int i = 128;
object obj = i;
The following code uses objects to represent obj To replace the value stored in the integer , And unpack and return to the integer , Keep it in i in . Final i The value of is 256:
int i = 128;
object obj = i;
obj = 256;
i = (int)obj; // i = 256
alike , These types can be dynamically modified :
int i = 128;
object obj = i;
obj = 512f;
float f = (float)obj; // b = 512f
The same principle translates into in the same way bool It's OK, too .
Be careful , Try to obj When unpacking to a type that is not the latest assignment , Will lead to InvalidCastException abnormal :
int i = 128;
object obj = i;
obj = 512f;
i = (int)obj;
In the above code , Because the last conversion was float type , This will throw InvalidCastException abnormal
Boxing can be implicit , As shown in the previous example , It can also be explicit , Forced conversion to by type System.Object. Unpacking must show that the forced type is converted to its original type . When the value type is passed to use System.Object Method as a parameter , Boxing is done implicitly .
take System.Object parametric String.Format() Method is an example of this .
As long as the function uses System.Object The value type is passed as a parameter , You should realize that heap allocation is caused by boxing .
6、 The importance of data layout
Essentially , We want to separate a large number of reference types from value types . If it's a value type ( For example, structure ) There is a reference type in , that GC Will focus on the entire object , And all its member data 、 Indirectly referenced objects . When a mark occurs - Clearance time , All fields of the object must be validated before moving . However , If you separate different types into different arrays , that GC You can skip a lot of data .
for example , If there is an array of structure objects , As shown in the following code , that GC You need to iterate over each member of each structure , This is quite time-consuming :
public struct MyStruct
{
int myInt;
float myFloat;
bool myBool;
string myString;
}
MyStruct[] arrayOfStructs = new MyStruct[1000];
However , If you reorganize all data blocks into multiple arrays at a time , that GC All basic data types are ignored , Check only string objects . The following code will make GC Clear faster :
int[] myInts = new int[1000];
float[] myFloats = new floar[1000];
bool[] myBools = new bool[1000];
string[] myStrings = new string[1000];
The reason for this is to reduce GC Indirect references to check . When data is divided into multiple independent arrays ( Reference type ),GC Will find 3 An array of value types (string Array is a reference type array ), Tag array , Then immediately continue with other work , Because there is no reason to mark the contents of a value type array . here ,GC There is no need to iterate over additional 3000 Data (mtInts、myFloats and myBools Medium 3000 It's worth ).
7、Unity API In the array
Unity API There are many instructions in that cause heap memory allocation , for example :
GetComponent(); //(T[])
Mesh.vertices; //(Vector3[])
Camera.allCameras; //(Camera[])
Every time you call Unity Returns the... Of the array API When the method is used , This will result in a new version of the data being allocated . These methods should be avoided as much as possible , Or call only a few times and cache the results , Avoid allocating memory more frequently than you actually need .
Unity There are others API call , You need to provide an array for the method , Then write the required data to the array . For example, provide Partical[] Array to ParticalSystem, To get its particle data . These types of API The advantage of calling is that it can avoid repeated allocation of large data , However , The downside is that the array needs to be large enough , To accommodate all objects . If the number of objects you need to get continues to increase , Will continue to allocate larger arrays . If it's a particle system , You need to make sure that the array you create is large enough , To contain the maximum number of Particle objects generated at any given time .
8、 Use... For dictionary keys InstanceID
A dictionary is used to map associations between two different objects , It can quickly find out if there is a mapping , If it is , What does it map . A common practice is to MonoBehaviour or ScriptableObject Reference key as dictionary , But this can cause some problems . When accessing dictionary elements , You need to call some from UnityEngine.Object The method of inheritance in , These object types are inherited from this class . This makes element comparison and mapping acquisition relatively slow .
This can be done by using Object.Get Instance ID() improvement , It returns an integer , The unique identification value used to represent the object , Throughout the life cycle of the program , This value does not change , It will not be reused between two objects .
It is best to cache this value in an object in some way , And use it as a key in the dictionary , Then the comparison of elements will be two to three times faster than using object references directly .
9、foreach loop
stay Unity Of C# In the code , quite a lot foreach The loop eventually results in unnecessary heap memory allocation during the call , Because they allocate one on the heap Enumerator Class object , Instead of allocating structures on the stack .
For traditional arrays, use foreach The loop is safe .Mono The compiler secretly converts the array's foreach The loop turns into a simple loop .
The cost is negligible , Because the heap allocation cost does not increase with the number of iterations . Only one was assigned Enumerator object , And use it repeatedly , in general , It only needs a small amount of memory .
Be careful , To a Transfrom Component execution foreach Usually iterations Transfrom Abbreviation of the child node of a component . for example :
foreach(Transform child in transform)
{
//do stuff with ‘child’
}
However , This will lead to the heap allocation problem mentioned above . therefore , This code style should be avoided , Instead, use the following code :
for(int i = 0;i < transform.childCount;++i)
{
Transform child = transform.GetChild(i);
//do stuff with ‘child’
}
10、 coroutines
As mentioned earlier , Starting a coroutine consumes a small amount of memory , But note that in method calls yield There will be no further consumption . If memory consumption and garbage collection are serious problems , We should try to avoid too many short-term co processes , And avoid calling too many... At run time StartCoroutine().
11、 Closure
A closure is a function that has access to variables in the scope of another function . Anonymous methods and lambda Expressions can be closures , But not always closures .
for example :
System.Func<int, int> anon = (x) => { return x; };
int result = anon(5);
The anonymous function in the above code is not a closure , Because it is self-contained , The function is the same as that of other locally defined functions . However , If an anonymous function pulls in data outside its scope , This anonymous function becomes a closure , Because it encloses the environment of the required data . The following code will cause closures :
int i = 1024;
System.Func<int, int> anon = (x) => { return x + i; };
int result = anon(5);
To complete the transaction u, The compiler must define a new custom class , It refers to accessible data values i The environment . At run time , It creates the corresponding object on the heap and provides it to anonymous functions . Note that this includes value types , The value type is initially allocated on the stack , This may break the original purpose of allocating them on the stack . therefore , Each call to the second method results in heap allocation and unavoidable garbage collection .
12、.NET Library function
.NET Class libraries provide a large number of general functions , To help programmers solve a large number of problems that they may encounter in daily development .
.NET There are also two features in class libraries that usually cause major performance problems when used . These two characteristics are LINQ And regular expressions .
LINQ Provides a way , Think of array data as a small database , Use something similar to SQL Query with the syntax of . Simple code style and complex underlying system ( By using closures ) It implies , It has a considerable performance cost .LINQ It is an easy-to-use tool , But it does not apply to high performance 、 Real time applications , For example, games , Can't even run without JIT On the platform , for example IOS.
meanwhile , adopt Regex Class to allow repeated string parsing , To find substrings that match a particular format , Replace part of the string , Or construct strings from different inputs . Regular expressions are another very useful tool , But if you don't need its features , Direct string substitution may be more efficient .
13、 Temporary work buffer
If you are used to using large temporary work buffers for a task , You should look for opportunities to reuse them , Instead of redistributing them over and over again , Because this can reduce the overhead involved in allocation and garbage collection . These functions should be extracted from feature specific classes to generic classes that contain large workspaces , So that multiple classes can reuse it .
14、 Object pool
Speaking of temporary work buffers , Object pooling by avoiding deallocation and reallocation , To minimize and establish control over memory usage . When an object is recycled , Just hide it , Put it to sleep , Until you need it again , At this point it regenerates from a previously reclaimed object , And used to replace objects that may need to be newly allocated .
15、 Precast tank
The previous object pool scheme is for traditional C# Objects are very useful , But not for GameObject and MonoBehaviour And so on Unity object . These objects tend to consume a lot of runtime memory , When you create and destroy them , It will consume a lot of CPU, It may also cause a large amount of garbage collection at runtime . for example , In a small RPG In the life cycle of the game , There could be a thousand orcs , But at any given moment , Only a small part is needed , May be 10 individual . It is best to use a similar pool as before , for example Unity The precast body , It can reduce unnecessary expenses , Avoid creating and destroying unnecessary 990 An orc .
Our goal is to push the instantiation of most objects to the scene initialization , Instead of having them create at run time . This can save a lot of runtime CPU, And avoid a large number of problems caused by object creation and destruction and garbage collection CPU Peak value , But the cost is the increase of scene load time and runtime memory consumption .
( Shorthand the code , The steps are shown in the following headings )
Poolable components
Precast tank system
Preset pool
Generating objects
Pre generated instances
Recycling of objects
Prefabrication pool and scene loading
Prefabrication pool summary
16、IL2CPP Optimize
If you use IL2CPP And you need to squeeze the last bit of performance out of your application , Then you can check out the series of blog posts in the following link :
https://blogs.unity3d.com/2016/07/26/il2cpp-optimizations-devirtualization/
https://blogs.unity3d.com/2016/08/04/il2cpp-optimizations-faster-virtual-method-calls/
https://blogs.unity3d.com/2016/08/11/il2cpp-optimizations-avoid-boxing/
17、WebGL Optimize
Unity Technologies Also released some information about WebGL Applied blog posts , It also includes some of all WebGL Important information about memory management that developers should know . You can find it in the link below :
https://blogs.unity3d.com/2016/09/20/understanding-memory-in-unity-webgl/
https://blogs.unity3d.com/2016/12/05/unity-webgl-memory-the-unity-heap/
5、 ... and 、Unity、Mono and IL2CPP The future of
Unity Use .NET3.5 Function of class library , Now it has 10 Years old . This limits .NET Classes available in the class library , Limit C# Linguistic characteristics , Limited performance , So during this period, the class library has made a lot of performance enhancements . However ,Unity2017( And later ) Can pass Edit | Project Settings | Player | Configuration | Scripting Runtime Version It is amended as follows Experiment(.NET 4.6 Equivalent). This setting has been upgraded Mono Runtime , And allow to use .NET4.6 The function of .
see Unity Road map , To understand Unity Technologies What you are doing and when you expect them to appear , The reference website is :
https://unity3d.com/unity/roadmap
forthcoming C# Job System
( Look at the , This technology Unity Is out of the )
C# Job System Can create simple tasks that run in background threads , To reduce the workload of the main thread .C# Job System It is very suitable for tasks with poor parallelism , For example, let thousands of simple AI Agents operate in a scene at the same time, etc . Of course , It can also be used for traditional multithreading behavior , Perform some calculations in the background that do not need to get the structure immediately .C# Job System Some editor technology improvements have also been introduced , Get greater performance gains than simply moving tasks into separate threads .
There are race conditions when writing a big problem with multithreading 、 Deadlocks and hard to reproduce and debug bug The risk of .C# Job System Designed to make these tasks easier than usual .
Unity One of the founders Joachim Ante stay Unite Europe2017 Push on C# Job System Speech , Lectures can be found on the web :
https://www.youtobe.com/watch?v=AXUvnk7Jws4
边栏推荐
- 【华为云至简致远】征文获奖名单出炉!
- 蓝桥杯2019年国赛最长子序列
- 2020年蓝桥杯省赛真题-走方格(DP/DFS)
- [Shanda conference] application setting module
- Recommend several AI Intelligent Platforms
- C语言学生成绩排名系统
- 84.(cesium篇)cesium模型在地形上运动
- 华为云HCDEZ专场暨分布式技术峰会:华为云分布式云原生技术与实践之路
- The MIHA tour club in June is hot! 500+ posts, more than HC, just this summer (with internal promotion method)
- 【山大会议】WebRTC基础之对等体连接
猜你喜欢

Trust level of discover

Runtime——探索类,对象,分类本质

【LeetCode】9、回文数
![[single chip microcomputer] [make buzzer sound] know the buzzer and let it make the sound you want](/img/cb/826b3591bafa62cc71826732eb32ef.png)
[single chip microcomputer] [make buzzer sound] know the buzzer and let it make the sound you want

jmeter关联登录302类型的接口

#进程地址空间

C language learning -18-makefile file writing examples and how to generate and call dynamic libraries

Community article | mosn building subset optimization ideas sharing

The bank card identification function of Huawei machine learning service enables bank card identification and binding with one click

Meet webassembly again
随机推荐
String的模拟实现
HMS core news industry solution: let technology add humanistic temperature
使用 zipfile、openpyxl、flask 批量导出excel zip
快速排序quick_sort
【山大会议】WebRTC基础之用户媒体的获取
odoo本地文档功能开发记录
Promouvoir l'adaptation compatible et permettre le développement collaboratif du Service Express adaptatif gbase en mai
Promoting compatibility and adaptation, enabling coordinated development of gbase may adaptation Express
【山大会议】项目引入 Redux
Rosbag使用命令
C language learning -18-makefile file writing examples and how to generate and call dynamic libraries
校企联合在路上!华为云GaussDB又来高校啦
让pycharm项目里面的文本模板支持jinjia2语法
Uni develops wechat applet to customize automatic camera detection (portrait + ID card)
Scala语言学习-05-递归和尾递归效率对比
在JFlash中添加未知类型的单片机
Community article | mosn building subset optimization ideas sharing
[Shanda conference] application setting module
程序替换函数
Dear students, don't read the textbooks any more. Just read this one for the complexity of time