【RuiMo】Optimization of Unity String

Hello friends, today I will share with you "string optimization".

First, by tracing the System.String class, we see that string.Format renews a StringBuilder in the final implementation process. Therefore, we can directly share a StringBuilder when performing Format operations on strings. The specific implementation is as follows:

private static StringBuilder stringBuilder = new StringBuilder();

public static string Format(string str, params object[] args)
{
    stringBuilder.Remove(0, stringBuilder.Length);
    stringBuilder.AppendFormat(str, args);
    return stringBuilder.ToString();
} 

The consumption of StringBuilder.Format is actually quite large, and the AppendFormatHelper function is finally called, and the implementation inside is also extremely complicated, so try not to use Format when you can try not to use Format. Consider using the Concat function instead.

string.Concat will regenerate a string every time it is used, and then fill it with data. When we need to concat a lot of data, it will judge whether the filled data is null every time, and there will be an objct-to-string operation in the loop body, but this is not necessary. Since we already have a general-purpose StringBuilder before, it can be shared directly, and the old data is cleared before each use to realize recycling. As shown below:

public static string Concat(string str1, string str2)
{
    stringBuilder.Remove(0, stringBuilder.Length);
    stringBuilder.Append(str1);
    stringBuilder.Append(str2);
    return stringBuilder.ToString();
} 

If there are multiple parameters str1, str2, str3, etc., write multiple Concat functions. If you encounter a loop body when calling externally, you can define an externally shared StringBuilder here, as follows:

private static StringBuilder shareStringBuilder = new StringBuilder();

public static StringBuilder GetShareStringBuilder()
{
    shareStringBuilder.Remove(0, stringBuilder.Length);
    return shareStringBuilder;
}

Maybe you see this, your head is completely confused, and you don't know what I'm doing. Now I go directly to the operation steps:

First, create two new scripts, namely GString and GStringSample, as shown in the following figure:

GString stores our string optimization class, the complete code is as follows:

using System.Text;

namespace GameStartStudio
{
    // 字符串优化类
    public static class GString
    {
        private static readonly StringBuilder StrBuilder = new();
        private static readonly StringBuilder ShareStrBuilder = new();

        public static StringBuilder GetShareStringBuilder()
        {
            ShareStrBuilder.Remove(0, StrBuilder.Length);
            return ShareStrBuilder;
        }

        public static string Format(string str, params object[] args)
        {
            StrBuilder.Remove(0, StrBuilder.Length);
            StrBuilder.AppendFormat(str, args);
            return StrBuilder.ToString();
        }

        public static string Concat(string str1, string str2)
        {
            StrBuilder.Remove(0, StrBuilder.Length);
            StrBuilder.Append(str1);
            StrBuilder.Append(str2);
            return StrBuilder.ToString();
        }

        public static string Concat(string str1, string str2, string str3)
        {
            StrBuilder.Remove(0, StrBuilder.Length);
            StrBuilder.Append(str1);
            StrBuilder.Append(str2);
            StrBuilder.Append(str3);
            return StrBuilder.ToString();
        }
    }
}

GStringSample stores the test code, don't forget to mount the code.

using UnityEngine;
using System.Text;
using UnityEngine.Profiling;

namespace GameStartStudio
{
    public class GStringSample : MonoBehaviour
    {
        private void Update()
        {
            // 优化前
            BeforeOptimize();

            // 优化后
            AfterOptimize();
        }

        string s1 = string.Empty;

        private void BeforeOptimize()
        {
            Profiler.BeginSample("Before Optimize");
            s1 = string.Empty;
            for (int i = 0; i < 1000; i++)
            {
                s1 += i;
            }

            Profiler.EndSample();
        }

        private readonly StringBuilder _stringBuilder = GString.GetShareStringBuilder();

        private void AfterOptimize()
        {
            Profiler.BeginSample("After Optimize");
            _stringBuilder.Remove(0, _stringBuilder.Length);
            for (int i = 0; i < 1000; i++)
            {
                _stringBuilder.Append(i);
            }

            Profiler.EndSample();
        }
    }
}

Finally, view the performance analysis by observing the Profiler Tab:

GC before optimization is 2.7MB

After optimization, the GC is only 27.1KB 

Guess you like

Origin blog.csdn.net/rrmod/article/details/130752134