Understanding optimization in Unity (Asset auditing)
2017-01-13 14:27
2036 查看
of existing Assets.
For any project of significant scale(巨大的规模), it is best to have a first line of defense(第一道防线) against human errors. It is relatively simple to write a small piece of code that ensures(保证) that no one can add a 4K uncompressed
Texture to a project.
And yet, this is a surprisingly common problem(很常见的问题). A 4K uncompressed Texture occupies(占用) 60 megabytes of memory. On a low-end mobile device, such as an iPhone 4S, it is dangerous(危险) to consume(消耗) more than about 180–200
megabytes of memory. If added by mistake, this Texture inadvertently occupies (无意中占用)between a third(三分之一) and a quarter of(四分之一) the application’s memory budget(预算), and causes difficult-to-diagnose(难以诊断) out-of-memory errors.
While it is now possible to track these issues down with the 5.3 Memory Profiler, it is arguably better to make sure such mistakes are impossible in the first place(更好的确保这样的错误是不可能的)
and implement one or more
See the Scripting Reference on AssetPostprocessor
for more possible
This is a simple example of an
This class is called any time a model is imported into the project, or whenever a model has its import settings changed. The code simply checks to see if the
property) is set to
Note that calling
The reason for this change is discussed in the “Models” section, below.
The
slow(非常慢). Reading a Texture from GPU memory into a temporary buffer for use by CPU code (e.g. Texture.GetPixel) would be very nonperformant(性能不好).). In Unity, this setting is disabled by default, but it can be accidentally(不小心) switched on.
possible.
Disable Mipmaps if possible
For objects that have a relatively invariant(相对不变) Z-depth relative to the Camera, it is possible to disable mipmaps to save about a third of(三分之一) the memory required to load the Texture. If an object changes Z-depth, disabling
mipmaps can lead to worse(不好的) Texture sampling performance(性能) on the GPU.
In general, this is useful for UI Textures and other Textures that are displayed at a constant size on screen.
Compress all Textures
Using a texture compression format suitable for the project’s target platform is crucial(至关重要) for saving memory.
If the selected texture compression format is unsuited(不合适) to the target platform, Unity decompresses(解压) the Texture when the Texture is loaded, consuming(消耗) both CPU time and an excessive amount of (过多的)memory. This is most
commonly a problem on Android devices, which often support vastly different texture compression formats depending on the chipset(芯片).
Enforce(执行) sensible(合理) Texture size limits
While this is simple, it is also very easy to forget to resize a Texture or to inadvertently(无心) alter(修改) the Texture size import setting. Determine a sensible(合理) maximum size for different types of Textures and enforce these
via code.
For many mobile applications, 2048x2048 or 1024x1024 is sufficient for(足够) Texture atlases, and 512x512 is sufficient(足够) for Textures applied to 3D models.
The
Unity requires this flag to be enabled if a project is modifying a Mesh at runtime via script, or if the Mesh is used as the basis for a MeshCollider component. If the model is not used in a MeshCollider and is not manipulated(操作)
by scripts, disable this flag to save half of the model’s memory.
Disable rigs on non-character models
By default, Unity imports a generic rig for non-character models. This causes an
this adds unnecessary overhead(无必要的费用) to the animation system, because all active Animators must be ticked once per frame.
Disable the rig on non-animated models to avoid this automatic addition of an Animator component and possible inadvertent(无意地) addition of unwanted Animators to a Scene.
Enable the Optimize Game Objects option on animated models
The Optimize Game Objects option has a significant performance impact(显著的性能影响) on animated models. When the option is disabled, Unity creates a large transform hierarchy mirroring the model’s bone structure whenever
the model is instantiated. This transform hierarchy is expensive to update, especially(特别) if other components (such as Particle Systems or Colliders) are attached to it. It also limits Unity’s ability to multithread Mesh skinning and bone animation calculations(计算).
If specific locations(指定位置) on a model’s bone structure need to be exposed(暴露) (such as exposing a model’s hands in order to dynamically attach weapon models) then these locations can be specifically(明确) whitelisted(白名单) in the
Some additional details can be found in the Unity Manual page on theModel Importer.
Use Mesh compression when possible
Enabling Mesh compression reduces(减少) the number of bits used to represent(代表) the floating-point numbers for different channels(通道) of a model’s data. This can lead to a minor loss of precision(一个小的精度损失), and the effects of
this imprecision(不精确) should be checked(核对) by artists(美术) before use in a final project.
The specific numbers of bits that a given compression level uses are detailed in theModelImporterMeshCompression
Scripting Reference.
Note that it is possible to use different levels of compression for different channels, so a project may choose to compress only tangents and normals while leaving(留下) UVs and vertex positions uncompressed.
Note: Mesh Renderer Settings
When adding Mesh Renderers to Prefabs or GameObjects, take note of the settings on the component. By default, Unity enables Shadow casting and receiving, Light Probe sampling, Reflection Probe sampling, and Motion Vector calculation.
If a project does not require one or more of these features, ensure that they’re turned off (关掉)by an automated(自动化) script. Any runtime code that adds MeshRenderers needs to toggle(开关,切换) these settings as well.
For 2D games, accidentally(意外地) adding a MeshRenderer to the Scene with the shadow options turned on adds a full shadow pass to the rendering loop. This is generally a waste of performance(浪费性能的).
Enable a compression format for audio that matches the available hardware. All iOS devices include hardware MP3 decompressors(解压), and many Android devices support Vorbis natively.
Further, import uncompressed audio files into Unity. Unity always recompresses(再压缩) audio when building a project. There is no need to import compressed audio and then recompress it; this only serves to degrade(降低) the quality(质量)
of the final audio clips.
Force audio clips to mono
Few mobile devices actually have stereo speakers(立体喇叭). On a mobile project, forcing(强逼) the imported audio clips to be mono-channel(通道) halves(减半) their memory consumption(消耗). This setting is also applicable(合适) to any audio
without stereo effects, such as most UI sound effects.
Reduce audio bitrate(比特率)
While this requires consultation(咨询) with an audio designer, attempt to minimize the bitrate of audio files in order to further save on memory consumption(消耗) and built project size.
collection strategy(战略) tends to(趋向) fragment(片段) memory, which can prevent(预防) a large heap from shrinking.
Strictly speaking(严格的说), all non-null reference-typed objects and all boxed value-typed objects must be allocated on the managed heap).
In the above diagram, the white box represents a quantity of(一些) memory apportioned to(分配) the managed heap, and the colored boxes within it represent data values stored within the managed heap’s memory space. When additional
values are needed, more space is allocated from within the managed heap.
The garbage collector runs periodically(3)(周期) (NOTE: The exact timing is platform-dependent). This sweeps(清理) through all objects on the heap(堆), marking(标记) for deletion(删除) any objects that are no longer referenced. Unreferenced
objects are then deleted, freeing up memory.
Crucially(关键的), Unity’s garbage collection – which uses theBoehm GC algorithm
– is non-generational and non-compacting. “Non-generational” means that the GC must sweep(扫除) through the entire heap when performing a collection pass, and its performance(执行) therefore degrades(降低) as the heap expands(扩展). “Non-compacting” means that objects
in memory are not relocated(重新定位) in order to close(关闭) gaps(空隙) between objects.
The above diagram shows an example of memory fragmentation(碎片). When an object is released, its memory is freed. However, the freed space doesnot become part of(一部分) a single large pool(一个大的池) of “free memory”.
The objects on either side of(在任何一边) the freed object may still be in use. Because of this, the freed space is a “gap” (间隙)between other segments(片段) of memory (this gap(间隙) is indicated by the red circle(红色的圈圈) in the diagram). The newly-freed space(刚释放的控件)
can therefore only be used to store data of identical(相同) or lesser size than the freed object.
When allocating an object, remember that the object must always occupy(占用) a contiguous(连续) block of space in memory.
This leads to the core problem(核心问题) of memory fragmentation(碎片): while the overall(整体) amount of space available(可用) in the heap may be substantial(大量的), it is possible that some or all of that space is in small “gaps”(间隙) between
allocated objects. In this case, even though(尽管) there may be enough total space to accommodate(容纳) a certain allocation, the managed heap cannot find a large enough block of contiguous(连续) memory in which to fit the allocation.
However, if a large object is allocated and there is insufficient(不足) contiguous(连续) free space to accommodate(容乃) the object, as illustrated above, the Unity memory manager performs(执行) two operations.
First, if it has not already done so(已经没有足够的空间), the garbage collector runs. This attempts to free up enough space to fulfill the allocation request.
If, after the GC runs, there is still not enough contiguous(连续) space to fit the requested(要求) amount of memory, the heap must expand(扩展). The specific amount that the heap expands is platform-dependent; however, most Unity platforms
double the size of the managed heap.
Unity does not often release the memory pages(页) allocated to the managed heap when it expands; it optimistically(乐观) retains(保留) the expanded(扩充的) heap, even if(即使) a large portion of(很大一部分) it is empty. This is to prevent(这是为了防止)
the need to re-expand the heap should further large allocations occur.
On most platforms, Unity eventually(最终) releases the pages used by empty portions of(一部分) the managed heap back to(回到) the operating system. The interval(间隔) at which this occurs(发生) is not guaranteed(保证) and should not be relied
upon(依赖上面的).
The address space(地址空间) used by the managed heap is never returned to the operating system.
For 32-bit programs, this can lead to address space exhaustion(耗尽) if the managed heap expands and contracts(扩展与缩小) many times. If a program’s available memory address space is exhausted(耗尽), the operating system will terminate(终止)
the program.
For 64-bit programs, the address space is sufficiently(足够) large that this is extremely unlikely to(非常不可能) occur for programs whose running time does not exceed(超过) the average human lifespan(人的平均寿命).
Asset auditing
Many of the problems found in real projects occur because of honest mistakes – temporary “test” changes and misclicks(错误点击) by a tired(疲惫) developer can silently(默默地) add poorly-performing(不良的) Assets, or change the import settingsof existing Assets.
For any project of significant scale(巨大的规模), it is best to have a first line of defense(第一道防线) against human errors. It is relatively simple to write a small piece of code that ensures(保证) that no one can add a 4K uncompressed
Texture to a project.
And yet, this is a surprisingly common problem(很常见的问题). A 4K uncompressed Texture occupies(占用) 60 megabytes of memory. On a low-end mobile device, such as an iPhone 4S, it is dangerous(危险) to consume(消耗) more than about 180–200
megabytes of memory. If added by mistake, this Texture inadvertently occupies (无意中占用)between a third(三分之一) and a quarter of(四分之一) the application’s memory budget(预算), and causes difficult-to-diagnose(难以诊断) out-of-memory errors.
While it is now possible to track these issues down with the 5.3 Memory Profiler, it is arguably better to make sure such mistakes are impossible in the first place(更好的确保这样的错误是不可能的)
Using AssetPostprocessor
TheAssetPostprocessorclass in the Unity Editor can be used to enforce certain minimum standards(执行最低的标准) on a Unity project. This class receives callbacks when Assets are imported. To use it, inherit from
AssetPostprocessor
and implement one or more
OnPreprocessmethods. Significant(值得注意) ones include:
OnPreprocessTexture
OnPreprocessModel
OnPreprocessAnimation
OnPreprocessAudio
See the Scripting Reference on AssetPostprocessor
for more possible
OnPreprocessmethods.
public class ReadOnlyModelPostprocessor : AssetPostprocessor { public void OnPreprocessModel() { ModelImporter modelImporter = (ModelImporter)assetImporter; if(modelImporter.isReadable) { modelImporter.isReadable = false; modelImporter.SaveAndReimport(); } } }
This is a simple example of an
AssetPostprocessorenforcing rules(执行规则) on a project:
This class is called any time a model is imported into the project, or whenever a model has its import settings changed. The code simply checks to see if the
Read/Write enabledflag (represented by the
isReadable
property) is set to
true. If it is, it forces(强迫) the flag to be
falseand then saves and reimports the Asset.
Note that calling
SaveAndReimportcauses this snippet of code to be called again! However, because it is now assured(确定) that
isReadableis false, this code does not produce an infinite reimport loop.
The reason for this change is discussed in the “Models” section, below.
Common Asset rules
Textures
Disable the read/write enabled flagThe
Read/Write enabledflag causes(导致) a Texture to be kept twice in memory: once on the GPU and once in CPU-addressable(可以寻址) memory(1) (NOTE: This is because, on most platforms, readback from GPU memory is extremely
slow(非常慢). Reading a Texture from GPU memory into a temporary buffer for use by CPU code (e.g. Texture.GetPixel) would be very nonperformant(性能不好).). In Unity, this setting is disabled by default, but it can be accidentally(不小心) switched on.
Read/Write Enabledis only necessary when manipulating(手动) Texture data outside of Shaders (such as with the
Texture.GetPixeland
Texture.SetPixelAPIs) and should be avoided(避免) whenever
possible.
Disable Mipmaps if possible
For objects that have a relatively invariant(相对不变) Z-depth relative to the Camera, it is possible to disable mipmaps to save about a third of(三分之一) the memory required to load the Texture. If an object changes Z-depth, disabling
mipmaps can lead to worse(不好的) Texture sampling performance(性能) on the GPU.
In general, this is useful for UI Textures and other Textures that are displayed at a constant size on screen.
Compress all Textures
Using a texture compression format suitable for the project’s target platform is crucial(至关重要) for saving memory.
If the selected texture compression format is unsuited(不合适) to the target platform, Unity decompresses(解压) the Texture when the Texture is loaded, consuming(消耗) both CPU time and an excessive amount of (过多的)memory. This is most
commonly a problem on Android devices, which often support vastly different texture compression formats depending on the chipset(芯片).
Enforce(执行) sensible(合理) Texture size limits
While this is simple, it is also very easy to forget to resize a Texture or to inadvertently(无心) alter(修改) the Texture size import setting. Determine a sensible(合理) maximum size for different types of Textures and enforce these
via code.
For many mobile applications, 2048x2048 or 1024x1024 is sufficient for(足够) Texture atlases, and 512x512 is sufficient(足够) for Textures applied to 3D models.
Models
Disable the Read/Write enabled flagThe
Read/Write enabledflag for models operates identically to(一致) the one described for Textures. However, it is enabled by default for models.
Unity requires this flag to be enabled if a project is modifying a Mesh at runtime via script, or if the Mesh is used as the basis for a MeshCollider component. If the model is not used in a MeshCollider and is not manipulated(操作)
by scripts, disable this flag to save half of the model’s memory.
Disable rigs on non-character models
By default, Unity imports a generic rig for non-character models. This causes an
Animatorcomponent be to added if the model is instantiated(实例化) at runtime. If the model is not animated via the Animation system,
this adds unnecessary overhead(无必要的费用) to the animation system, because all active Animators must be ticked once per frame.
Disable the rig on non-animated models to avoid this automatic addition of an Animator component and possible inadvertent(无意地) addition of unwanted Animators to a Scene.
Enable the Optimize Game Objects option on animated models
The Optimize Game Objects option has a significant performance impact(显著的性能影响) on animated models. When the option is disabled, Unity creates a large transform hierarchy mirroring the model’s bone structure whenever
the model is instantiated. This transform hierarchy is expensive to update, especially(特别) if other components (such as Particle Systems or Colliders) are attached to it. It also limits Unity’s ability to multithread Mesh skinning and bone animation calculations(计算).
If specific locations(指定位置) on a model’s bone structure need to be exposed(暴露) (such as exposing a model’s hands in order to dynamically attach weapon models) then these locations can be specifically(明确) whitelisted(白名单) in the
Extra Transformslist.
Some additional details can be found in the Unity Manual page on theModel Importer.
Use Mesh compression when possible
Enabling Mesh compression reduces(减少) the number of bits used to represent(代表) the floating-point numbers for different channels(通道) of a model’s data. This can lead to a minor loss of precision(一个小的精度损失), and the effects of
this imprecision(不精确) should be checked(核对) by artists(美术) before use in a final project.
The specific numbers of bits that a given compression level uses are detailed in theModelImporterMeshCompression
Scripting Reference.
Note that it is possible to use different levels of compression for different channels, so a project may choose to compress only tangents and normals while leaving(留下) UVs and vertex positions uncompressed.
Note: Mesh Renderer Settings
When adding Mesh Renderers to Prefabs or GameObjects, take note of the settings on the component. By default, Unity enables Shadow casting and receiving, Light Probe sampling, Reflection Probe sampling, and Motion Vector calculation.
If a project does not require one or more of these features, ensure that they’re turned off (关掉)by an automated(自动化) script. Any runtime code that adds MeshRenderers needs to toggle(开关,切换) these settings as well.
For 2D games, accidentally(意外地) adding a MeshRenderer to the Scene with the shadow options turned on adds a full shadow pass to the rendering loop. This is generally a waste of performance(浪费性能的).
Audio(声音)
Platform-appropriate(适当) compression settingsEnable a compression format for audio that matches the available hardware. All iOS devices include hardware MP3 decompressors(解压), and many Android devices support Vorbis natively.
Further, import uncompressed audio files into Unity. Unity always recompresses(再压缩) audio when building a project. There is no need to import compressed audio and then recompress it; this only serves to degrade(降低) the quality(质量)
of the final audio clips.
Force audio clips to mono
Few mobile devices actually have stereo speakers(立体喇叭). On a mobile project, forcing(强逼) the imported audio clips to be mono-channel(通道) halves(减半) their memory consumption(消耗). This setting is also applicable(合适) to any audio
without stereo effects, such as most UI sound effects.
Reduce audio bitrate(比特率)
While this requires consultation(咨询) with an audio designer, attempt to minimize the bitrate of audio files in order to further save on memory consumption(消耗) and built project size.
Understanding garbage collection & the managed heap(堆)
Another common problem faced by many Unity developers is the unexpected(意想不到) expansion(膨胀) of the managed heap. In Unity, the managed heap expands(expansion) much more readily than it shrinks(收缩). Furthermore(此外), Unity’s garbagecollection strategy(战略) tends to(趋向) fragment(片段) memory, which can prevent(预防) a large heap from shrinking.
Technical Details: how the managed heap operates & why it expands
The “managed heap” is a section of memory that is automatically managed by the memory manager of a project’s scripting runtime (Mono or IL2CPP). All objects created in managed code must be allocated on the managed heap(2) (NOTE:Strictly speaking(严格的说), all non-null reference-typed objects and all boxed value-typed objects must be allocated on the managed heap).
In the above diagram, the white box represents a quantity of(一些) memory apportioned to(分配) the managed heap, and the colored boxes within it represent data values stored within the managed heap’s memory space. When additional
values are needed, more space is allocated from within the managed heap.
The garbage collector runs periodically(3)(周期) (NOTE: The exact timing is platform-dependent). This sweeps(清理) through all objects on the heap(堆), marking(标记) for deletion(删除) any objects that are no longer referenced. Unreferenced
objects are then deleted, freeing up memory.
Crucially(关键的), Unity’s garbage collection – which uses theBoehm GC algorithm
– is non-generational and non-compacting. “Non-generational” means that the GC must sweep(扫除) through the entire heap when performing a collection pass, and its performance(执行) therefore degrades(降低) as the heap expands(扩展). “Non-compacting” means that objects
in memory are not relocated(重新定位) in order to close(关闭) gaps(空隙) between objects.
The above diagram shows an example of memory fragmentation(碎片). When an object is released, its memory is freed. However, the freed space doesnot become part of(一部分) a single large pool(一个大的池) of “free memory”.
The objects on either side of(在任何一边) the freed object may still be in use. Because of this, the freed space is a “gap” (间隙)between other segments(片段) of memory (this gap(间隙) is indicated by the red circle(红色的圈圈) in the diagram). The newly-freed space(刚释放的控件)
can therefore only be used to store data of identical(相同) or lesser size than the freed object.
When allocating an object, remember that the object must always occupy(占用) a contiguous(连续) block of space in memory.
This leads to the core problem(核心问题) of memory fragmentation(碎片): while the overall(整体) amount of space available(可用) in the heap may be substantial(大量的), it is possible that some or all of that space is in small “gaps”(间隙) between
allocated objects. In this case, even though(尽管) there may be enough total space to accommodate(容纳) a certain allocation, the managed heap cannot find a large enough block of contiguous(连续) memory in which to fit the allocation.
However, if a large object is allocated and there is insufficient(不足) contiguous(连续) free space to accommodate(容乃) the object, as illustrated above, the Unity memory manager performs(执行) two operations.
First, if it has not already done so(已经没有足够的空间), the garbage collector runs. This attempts to free up enough space to fulfill the allocation request.
If, after the GC runs, there is still not enough contiguous(连续) space to fit the requested(要求) amount of memory, the heap must expand(扩展). The specific amount that the heap expands is platform-dependent; however, most Unity platforms
double the size of the managed heap.
Key problems with the heap
The core issues(核心问题) with managed heap expansion(膨胀) are twofold(两部分):Unity does not often release the memory pages(页) allocated to the managed heap when it expands; it optimistically(乐观) retains(保留) the expanded(扩充的) heap, even if(即使) a large portion of(很大一部分) it is empty. This is to prevent(这是为了防止)
the need to re-expand the heap should further large allocations occur.
On most platforms, Unity eventually(最终) releases the pages used by empty portions of(一部分) the managed heap back to(回到) the operating system. The interval(间隔) at which this occurs(发生) is not guaranteed(保证) and should not be relied
upon(依赖上面的).
The address space(地址空间) used by the managed heap is never returned to the operating system.
For 32-bit programs, this can lead to address space exhaustion(耗尽) if the managed heap expands and contracts(扩展与缩小) many times. If a program’s available memory address space is exhausted(耗尽), the operating system will terminate(终止)
the program.
For 64-bit programs, the address space is sufficiently(足够) large that this is extremely unlikely to(非常不可能) occur for programs whose running time does not exceed(超过) the average human lifespan(人的平均寿命).
相关文章推荐
- unity Bug(1)UnityEditor.UI.dll' is in timestamps but is not known in assetdatabase
- UnityEngine.UI.dll is in timestamps but is not known in assetdatabase
- Unity 解决 An asset is marked with HideFlags.DontSave but is included in the build 问题。
- Unity5.5.0f3中".bytes"文件出现"Unrecognized assets cannot be included in AssetBundles"问题
- MEMORY OPTIMIZATION FOR IOS APPS DEVELOPED IN UNITY
- New AssetBundle build system in Unity 5.0
- AssetBundle in Unity 5.0
- Unity5 使用自带的字体导致BuildAssetbundle失败An asset is marked with HideFlags.DontSave but is included in the
- UnityEditor.UI.dll' is in timestamps but is not known in assetdatabase
- Understanding and Implementing Factory Pattern in C++
- Understanding and Using rem Units in CSS
- Unity使用自定义资源(.asset)配置数据
- Unity5 AssetBundle 打包以及加载
- Understanding Strings In COM
- Unity 热更新之AssetBundle
- Unity5.4.1与NGUI出现的问题Ignoring menu item NGUI because it is in no submenu!
- Genetic Algorithms in Search and Optimization
- Unity中Shader和AssetBundle结合使用的注意事项
- unity GetComponentsInChildren
- [Unity AssetBundle]Asset资源处理