[Unity] HTFramework framework (40) Debug performance monitoring

Updated: April 22, 2021.
Github source code: [Click me to get the source code]
Gitee source code: [Click me to get the source code]

C# code performance monitoring

The performance consumption of C# code runtime is mainly reflected in two aspects, 1 is 时间消耗and 2 is 空间消耗.

  • Time consumption can be understood as the CPU time consumed by a certain piece of code execution;
  • Space consumption is also the consumed memory space. In C#, the memory space is divided into stack space and heap space . Of course, the heap space is also called managed heap space , because the heap has been managed by the CLR, which will be responsible for opening up heap space and heap space. Clean up the heap space , so our main concern is to open up and clean up these two processes, because in C# these two processes are the root cause of the performance bottleneck, which is the so-called GC.

use

Debug performance monitoring mode

Monitoring Code Snippets

1. We can monitor the execution performance of a code fragment in the following ways:

    void Start()
    {
    
    
        List<int> ints = new List<int>(1000000);

        //开始监控
        Main.m_Debug.BeginMonitor("计算一百万次");

        int test = 0;
        foreach (var i in ints)
        {
    
    
            test += i;
        }

        //结束监控
        MonitorData data = Main.m_Debug.EndMonitor();

        //打印监控日志
        data.ToString().Info();
    }

The result of the operation is as follows:

Note:
1. The heap memory garbage generated: that is, the managed space opened on the managed heap;
2. The number of GC triggers: when the CLR cleans up the managed heap, it is also a GC;
if your code opens up more space, Or the more GC times they cause, the more likely they are to cause performance bottlenecks.

Here, although the foreach traversal performs one million integer addition operations, it does not generate heap memory garbage.
insert image description here

2. Then we change the code and put the assignment statement of the variable ints in the monitoring code segment:

    void Start()
    {
    
    
        //开始监控
        Main.m_Debug.BeginMonitor("计算一百万次");

        List<int> ints = new List<int>(1000000);

        int test = 0;
        foreach (var i in ints)
        {
    
    
            test += i;
        }

        //结束监控
        MonitorData data = Main.m_Debug.EndMonitor();

        //打印监控日志
        data.ToString().Info();
    }

The result of the operation is as follows:

Here, because List is a reference type, a new reference type will open up new space on the managed heap, that is, a piece of memory garbage is generated (although it is not yet memory garbage, but it will be after it is used up), it can be seen that a whole The size of the type is 4 bytes , and a million integers are exactly 4 million bytes , which is exactly 4M bytes ! However, the 4M garbage is not seen by the CLR, and the number of GC triggers is 0, which proves that he did not start the recycling operation because of this newly added garbage.

insert image description here

3. Then we change the code again and manually GC once:

    void Start()
    {
    
    
        //开始监控
        Main.m_Debug.BeginMonitor("计算一百万次");

        List<int> ints = new List<int>(1000000);

        int test = 0;
        foreach (var i in ints)
        {
    
    
            test += i;
        }

        //清理一次内存,将主动触发一次GC
        Main.m_Resource.ClearMemory();

        //结束监控
        MonitorData data = Main.m_Debug.EndMonitor();

        //打印监控日志
        data.ToString().Info();
    }

The result of the operation is as follows:

It can be seen that GC has been done once, that is, garbage has been collected once, but the newly added heap memory garbage still has 3M. ​​Obviously, the memory space pointed to by ints has not been recycled, and the CLR has only recovered 1M from other places. Free memory, because the CLR currently does not know that the space pointed to by ints has become garbage, because the entire Start method has not yet ended, and you can still call ints later , so it is not a garbage space.

insert image description here
4. Then we change the code and put the code fragment in a method:

    void Start()
    {
    
    
        //开始监控
        Main.m_Debug.BeginMonitor("计算一百万次");

        Test();

        //清理一次内存,将主动触发一次GC
        Main.m_Resource.ClearMemory();

        //结束监控
        MonitorData data = Main.m_Debug.EndMonitor();

        //打印监控日志
        data.ToString().Info();
    }

    private void Test()
    {
    
    
        List<int> ints = new List<int>(1000000);

        int test = 0;
        foreach (var i in ints)
        {
    
    
            test += i;
        }
    }

The result of the operation is as follows:

It can be seen that after GC once, the 4M garbage space generated by ints has been recycled. Why can it be recycled in this way? Because the scope of ints has changed to the Test method, when the GC recycles, Test has been executed, and the CLR receives a clear instruction: what ints points to is already a piece of garbage space and can be recycled.

Note: Let’s compare the time consumption of the code. The execution time without triggering GC is: 0.0005 seconds, and the execution time when triggering a GC is: 0.0166 seconds. Obviously, the extra execution time of nearly 30 times is brought by GC The performance loss, which is like using the price of time in exchange for space!
insert image description here

As mentioned above, when we monitor the execution efficiency of a code fragment, we focus on its execution time and how much heap memory garbage is generated. Of course, if GC is triggered during the monitoring process, the final generated heap memory garbage The number is not necessarily accurate, because some garbage may have been collected, but if your code is always triggering GC frequently, then you must consider refactoring them!

monitoring method

Of course, Debug also supports running a certain method in monitoring mode, as follows:

    void Start()
    {
    
    
        //监控模式执行Test
        MonitorData data = Main.m_Debug.MonitorExecute(Test);
        
        //打印监控日志
        data.ToString().Info();
    }

    private void Test()
    {
    
    
        List<int> ints = new List<int>(1000000);

        int test = 0;
        foreach (var i in ints)
        {
    
    
            test += i;
        }
    }

Applicable scene

1. We can use the performance monitor to monitor some behaviors that cause performance bottlenecks, for example, the following behavior with great performance loss:

    void Start()
    {
    
    
        //开始监控
        MonitorData data = Main.m_Debug.MonitorExecute(Test, "字符串累加");
        
        //打印监控日志
        data.ToString().Info();
    }

    private void Test()
    {
    
    
    	//string累加一万次
        string test = "";
        for (int i = 0; i < 10000; i++)
        {
    
    
            test += "string";
        }
    }

The result of the operation is as follows:

The heap memory garbage record generated at this time is no longer accurate, because 79 GCs have been triggered !

insert image description here
2. After discovering this huge problem, we immediately choose to use StringBuilder to improve:

    void Start()
    {
    
    
        //开始监控
        MonitorData data = Main.m_Debug.MonitorExecute(Test, "StringBuilder累加");
        
        //打印监控日志
        data.ToString().Info();
    }

    private void Test()
    {
    
    
    	//string累加一万次
        StringBuilder builder = new StringBuilder();
        string test = "";
        for (int i = 0; i < 10000; i++)
        {
    
    
            builder.Append("string");
        }
        test = builder.ToString();
    }

The result of the operation is as follows:

You can see the amazing optimization effect, only 262KB of garbage is generated, and GC is avoided!
insert image description here
3. Of course, we can also try to use string.Format to achieve the same effect:

    void Start()
    {
    
    
        //开始监控
        MonitorData data = Main.m_Debug.MonitorExecute(Test, "Format累加");
        
        //打印监控日志
        data.ToString().Info();
    }

    private void Test()
    {
    
    
        //同样是string累加一万次
        string test = "";
        for (int i = 0; i < 10000; i += 10)
        {
    
    
            test = string.Format("{0}{1}{1}{1}{1}{1}{1}{1}{1}{1}{1}", test, "string");
        }
    }

The result of the operation is as follows:

It can be seen that although string.Format is not the optimal method, it can also bring some optimization effects.
insert image description here

To sum up, when you have a relatively complex piece of code that is hard to see with the human eye, it is a good optimization guide to check the time and space it consumes during runtime!

Guess you like

Origin blog.csdn.net/qq992817263/article/details/116025230