• GC优化策略官篇2


    原文:http://www.cnblogs.com/zblade/

    英文:英文地址

    降低GC的影响的方法

       大体上来说,我们可以通过三种方法来降低GC的影响:

      1)减少GC的运行次数;

      2)减少单次GC的运行时间;

      3)将GC的运行时间延迟,避免在关键时候触发,比如可以在场景加载的时候调用GC

          似乎看起来很简单,基于此,我们可以采用三种策略:

      1)对游戏进行重构,减少堆内存的分配和引用的分配。更少的变量和引用会减少GC操作中的检测个数从而提高GC的运行效率。

      2)降低堆内存分配和回收的频率,尤其是在关键时刻。也就是说更少的事件触发GC操作,同时也降低堆内存的碎片化。

      3)我们可以试着测量GC和堆内存扩展的时间,使其按照可预测的顺序执行。当然这样操作的难度极大,但是这会大大降低GC的影响。

    减少内存垃圾的数量

       减少内存垃圾主要可以通过一些方法来减少:  

      缓存

       如果在代码中反复调用某些造成堆内存分配的函数但是其返回结果并没有使用,这就会造成不必要的内存垃圾,我们可以缓存这些变量来重复利用,这就是缓存。

       例如下面的代码每次调用的时候就会造成堆内存分配,主要是每次都会分配一个新的数组:

    1
    2
    3
    4
    5
    void OnTriggerEnter(Collider other)
    {
         Renderer[] allRenderers = FindObjectsOfType<Renderer>();
         ExampleFunction(allRenderers);      
    }

      对比下面的代码,只会生产一个数组用来缓存数据,实现反复利用而不需要造成更多的内存垃圾:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private Renderer[] allRenderers;
     
    void Start()
    {
       allRenderers = FindObjectsOfType<Renderer>();
    }
     
    void OnTriggerEnter(Collider other)
    {
        ExampleFunction(allRenderers);
    }

      不要在频繁调用的函数中反复进行堆内存分配

       在MonoBehaviour中,如果我们需要进行堆内存分配,最坏的情况就是在其反复调用的函数中进行堆内存分配,例如Update()和LateUpdate()函数这种每帧都调用的函数,这会造成大量的内存垃圾。我们可以考虑在Start()或者Awake()函数中进行内存分配,这样可以减少内存垃圾。

    下面的例子中,update函数会多次触发内存垃圾的产生:

    1
    2
    3
    4
    void Update()
    {
        ExampleGarbageGenerationFunction(transform.position.x);
    }

      通过一个简单的改变,我们可以确保每次在x改变的时候才触发函数调用,这样避免每帧都进行堆内存分配:

    private float previousTransformPositionX;
    
    void Update()
    {
        float transformPositionX = transform.position.x;
        if(transfromPositionX != previousTransformPositionX)
        {
            ExampleGarbageGenerationFunction(transformPositionX);    
            previousTransformPositionX = trasnformPositionX;
        }
    }
    

      另外的一种方法是在update中采用计时器,特别是在运行有规律但是不需要每帧都运行的代码中,例如:

    1
    2
    3
    4
    void Update()
    {
        ExampleGarbageGeneratiingFunction()
    }

      通过添加一个计时器,我们可以确保每隔1s才触发该函数一次:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private float timeSinceLastCalled;
    private float delay = 1f;
    void Update()
    {
        timSinceLastCalled += Time.deltaTime;
        if(timeSinceLastCalled > delay)
        {
             ExampleGarbageGenerationFunction();
             timeSinceLastCalled = 0f;
        }
    }                  

      通过这样细小的改变,我们可以使得代码运行的更快同时减少内存垃圾的产生。

      附: 不要忽略这一个方法,在最近的项目性能优化中,我经常采用这样的方法来优化游戏的性能,很多对于固定时间的事件回调函数中,如果每次都分配新的缓存,但是在操作完后并不释放,这样就会造成大量的内存垃圾,对于这样的缓存,最好的办法就是当前周期回调后执行清除或者标志为废弃。 

       清除链表

      在堆内存上进行链表的分配的时候,如果该链表需要多次反复的分配,我们可以采用链表的clear函数来清空链表从而替代反复多次的创建分配链表。

    1
    2
    3
    4
    5
    void Update()
    {
        List myList = new List();
        PopulateList(myList);       
    }

      通过改进,我们可以将该链表只在第一次创建或者该链表必须重新设置的时候才进行堆内存分配,从而大大减少内存垃圾的产生:

    1
    2
    3
    4
    5
    6
    private List myList = new List();
    void Update()
    {
        myList.Clear();
        PopulateList(myList);
    }

      对象池

      即便我们在代码中尽可能地减少堆内存的分配行为,但是如果游戏有大量的对象需要产生和销毁依然会造成GC。对象池技术可以通过重复使用对象来降低堆内存的分配和回收频率。对象池在游戏中广泛的使用,特别是在游戏中需要频繁的创建和销毁相同的游戏对象的时候,例如枪的子弹这种会频繁生成和销毁的对象。

      要详细的讲解对象池已经超出本文的范围,但是该技术值得我们深入的研究This tutorial on object pooling on the Unity Learn site对于对象池有详细深入的讲解。

      附:对象池技术属于游戏中比较通用的技术,如果有闲余时间,大家可以学习一下这方面的知识。

     

  • 相关阅读:
    Git 远程分支的查看及相关问题
    Clean Code – Chapter 6 Objects and Data Structures
    Clean Code – Chapter 5 Formatting
    Clean Code – Chapter 4: Comments
    利用 SerialPort 控件实现 PC 串口通信
    Clean Code – Chapter 3: Functions
    oracle如何查看当前有哪些用户连接到数据库
    c++ Ansi和Unicode相互转换
    c++ Utf8和Unicode相互转换
    c++ 根据某个字符或者字符串分割另外一个字符串
  • 原文地址:https://www.cnblogs.com/TravelingLight/p/8986178.html
Copyright © 2020-2023  润新知