您的位置:首页 > 移动开发 > Unity3D

Understanding optimization in Unity (Asset auditing)

2017-01-13 14:27 2036 查看


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 settings
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(更好的确保这样的错误是不可能的)

Using AssetPostprocessor

The
AssetPostprocessor
class 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
OnPreprocess
methods. Significant(值得注意) ones include:

OnPreprocessTexture


OnPreprocessModel


OnPreprocessAnimation


OnPreprocessAudio


See the Scripting Reference on AssetPostprocessor
for more possible
OnPreprocess
methods.

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
AssetPostprocessor
enforcing 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 enabled
flag (represented by the
isReadable

property) is set to
true
. If it is, it forces(强迫) the flag to be
false
and then saves and reimports the Asset.

Note that calling
SaveAndReimport
causes this snippet of code to be called again! However, because it is now assured(确定) that
isReadable
is 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 flag

The
Read/Write enabled
flag 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 Enabled
is only necessary when manipulating(手动) Texture data outside of Shaders (such as with the
Texture.GetPixel
and
Texture.SetPixel
APIs) 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 flag

The
Read/Write enabled
flag 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
Animator
component 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
Transforms
list.

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 settings

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.

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 garbage
collection 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(人的平均寿命).




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity3d