Unity3D研究院之字符串拼接0GC(一百零四)

栏目: 后端 · 发布时间: 4年前

内容简介:最近在优化项目,发现字符串拼接的堆内存非常大,而且非常频繁。大概原因如下1.字符串就是char[] 长短发生变化必然要重新分配长度,以及拷贝之前的部分,产生GC

最近在优化项目,发现字符串拼接的堆内存非常大,而且非常频繁。

大概原因如下

1.字符串就是char[] 长短发生变化必然要重新分配长度,以及拷贝之前的部分,产生GC

2.字符串+=进行拼接会产生装箱,生成GC

3.Append传入值类型数据,它会调用ToString方法,需要new string 然后把char[]拷进去,又会产生堆内存。

4.new StringBuidler 产生堆内存

5.StringBuidler.ToString 产生堆内存

5.string.format 它内部使用的就是StringBuilder,但是 1)new StringBuilder 2)Append 3)ToString都会产生堆内存。

所以我们需要优化的是

1.

int a = 100;

string b = a + “”;

禁止上述这种写法,会额外产生一次装箱操作。所以要采用如下写法。

string b = a.ToString();

2.少用或者不用string.format,提前缓存共享StringBuilder对象。避免用时候产生堆内存。

3.网上找到一个算法,挺有意思。提前定义好 0 – 9 之间的字符数组,如果传入值类型数据,从高位依次除以10算出每一位的数,然后再去预先声明的0-9字符数组中找对应的char,这样就就不会产生装箱GC了。

4.如果可以提前确定字符串的长度,例如,界面上显示玩家的等级, 我们可以确定level不可能超过3位数也就是100,那么可以提前声明一个长度为3的StringBuilder,通过反射取出来内部的_str,这样就可以避免最后的ToString产生的堆内存了。由于_str内容可能无法擦掉之前的所以需要调用GarbageFreeClear();方法。

1.装箱版本

using System.Text;
using UnityEngine;
using UnityEngine.Profiling;
 
public class NewBehaviourScript : MonoBehaviour {
 
    StringBuilder m_StringBuilder = new StringBuilder(100);
    string m_StringBuildertxt = string.Empty;
    private void Start()
    {
        m_StringBuildertxt = m_StringBuilder.GetGarbageFreeString();
    }
    private void Update()
    {
        int i = Random.Range(0, 100);
        float f = Random.Range(0.01f, 200.01f);
        float d = Random.Range(0.01f, 200.01f);
        string s = "yusong: " + i;
 
        Profiler.BeginSample("string.format");
        string s1 = string.Format("{0}{1}{2}{3}", i, f, d, s);
        Profiler.EndSample();
 
        Profiler.BeginSample("+=");
        string s2 = i +"" + f +"" + d +"" + s;
        Profiler.EndSample();
 
        Profiler.BeginSample("StringBuilder");
        string s3 = new StringBuilder().Append(i).Append(f).Append(d).Append(s).ToString();
        Profiler.EndSample();
 
        Profiler.BeginSample("StrExt.Format");
        string s4 = StrExt.Format("{0}{1:0.00}{2:0.00}{3}", i, f, d, s);
        Profiler.EndSample();
 
        Profiler.BeginSample("EmptyGC");
        m_StringBuilder.GarbageFreeClear();
        m_StringBuilder.ConcatFormat("{0}{1:0.00}{2:0.00}{3}", i, f, d, s);
        string s5 = m_StringBuildertxt;
        Profiler.EndSample();
 
 
        Debug.LogFormat("s1 : {0}",s1);
        Debug.LogFormat("s2 : {0}", s2);
        Debug.LogFormat("s3 : {0}", s3);
        Debug.LogFormat("s4 : {0}", s4);
        Debug.LogFormat("s5 : {0}", s5);
    }
}

Unity3D研究院之字符串拼接0GC(一百零四)

2.无装箱版本

using System.Text;
using UnityEngine;
using UnityEngine.Profiling;
 
public class NewBehaviourScript : MonoBehaviour {
 
    StringBuilder m_StringBuilder = new StringBuilder(100);
    string m_StringBuildertxt = string.Empty;
    private void Start()
    {
        m_StringBuildertxt = m_StringBuilder.GetGarbageFreeString();
    }
    private void Update()
    {
        int i = Random.Range(0, 100);
        float f = Random.Range(0.01f, 200.01f);
        float d = Random.Range(0.01f, 200.01f);
        string s = "yusong: " + i.ToString();
 
        Profiler.BeginSample("string.format");
        string s1 = string.Format("{0}{1}{2}{3}", i.ToString(), f.ToString(), d.ToString(), s);
        Profiler.EndSample();
 
        Profiler.BeginSample("+=");
        string s2 = i.ToString() + f.ToString() + d.ToString() + s;
        Profiler.EndSample();
 
        Profiler.BeginSample("StringBuilder");
        string s3 = new StringBuilder().Append(i).Append(f).Append(d).Append(s).ToString();
        Profiler.EndSample();
 
        Profiler.BeginSample("StrExt.Format");
        string s4 = StrExt.Format("{0}{1:0.00}{2:0.00}{3}", i, f, d, s);
        Profiler.EndSample();
 
        Profiler.BeginSample("EmptyGC");
        m_StringBuilder.GarbageFreeClear();
        m_StringBuilder.ConcatFormat("{0}{1:0.00}{2:0.00}{3}", i, f, d, s);
        string s5 = m_StringBuildertxt;
        Profiler.EndSample();
 
        Debug.LogFormat("s1 : {0}", s1);
        Debug.LogFormat("s2 : {0}", s2);
        Debug.LogFormat("s3 : {0}", s3);
        Debug.LogFormat("s4 : {0}", s4);
        Debug.LogFormat("s5 : {0}", s5);
    }
}

Unity3D研究院之字符串拼接0GC(一百零四)

通通过这两张图大家可以看出来装箱和不装箱对GC有多大的影响了,还有最后的无GC版本。代码参考了下面的这两篇文章。

using System;
using System.Text;
using UnityEngine;
 
public static class StringBuilderExtensions
{
    // These digits are here in a static array to support hex with simple, easily-understandable code. 
    // Since A-Z don't sit next to 0-9 in the ascii table.
    private static readonly char[] ms_digits = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
 
    private static readonly uint ms_default_decimal_places = 5; //< Matches standard .NET formatting dp's
    private static readonly char ms_default_pad_char = '0';
 
    //! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Any base value allowed.
    public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val, uint pad_amount, char pad_char, uint base_val)
    {
        Debug.Assert(pad_amount >= 0);
        Debug.Assert(base_val > 0 && base_val <= 16);
 
        // Calculate length of integer when written out
        uint length = 0;
        uint length_calc = uint_val;
 
        do
        {
            length_calc /= base_val;
            length++;
        }
        while (length_calc > 0);
 
        // Pad out space for writing.
        string_builder.Append(pad_char, (int)Math.Max(pad_amount, length));
 
        int strpos = string_builder.Length;
 
        // We're writing backwards, one character at a time.
        while (length > 0)
        {
            strpos--;
 
            // Lookup from static char array, to cover hex values too
            string_builder[strpos] = ms_digits[uint_val % base_val];
 
            uint_val /= base_val;
            length--;
        }
 
        return string_builder;
    }
 
    //! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume no padding and base ten.
    public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val)
    {
        string_builder.Concat(uint_val, 0, ms_default_pad_char, 10);
        return string_builder;
    }
 
    //! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume base ten.
    public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val, uint pad_amount)
    {
        string_builder.Concat(uint_val, pad_amount, ms_default_pad_char, 10);
        return string_builder;
    }
 
    //! Convert a given unsigned integer value to a string and concatenate onto the stringbuilder. Assume base ten.
    public static StringBuilder Concat(this StringBuilder string_builder, uint uint_val, uint pad_amount, char pad_char)
    {
        string_builder.Concat(uint_val, pad_amount, pad_char, 10);
        return string_builder;
    }
 
    //! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Any base value allowed.
    public static StringBuilder Concat(this StringBuilder string_builder, int int_val, uint pad_amount, char pad_char, uint base_val)
    {
        Debug.Assert(pad_amount >= 0);
        Debug.Assert(base_val > 0 && base_val <= 16);
 
        // Deal with negative numbers
        if (int_val < 0)
        {
            string_builder.Append('-');
            uint uint_val = uint.MaxValue - ((uint)int_val) + 1; //< This is to deal with Int32.MinValue
            string_builder.Concat(uint_val, pad_amount, pad_char, base_val);
        }
        else
        {
            string_builder.Concat((uint)int_val, pad_amount, pad_char, base_val);
        }
 
        return string_builder;
    }
 
    //! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume no padding and base ten.
    public static StringBuilder Concat(this StringBuilder string_builder, int int_val)
    {
        string_builder.Concat(int_val, 0, ms_default_pad_char, 10);
        return string_builder;
    }
 
    //! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume base ten.
    public static StringBuilder Concat(this StringBuilder string_builder, int int_val, uint pad_amount)
    {
        string_builder.Concat(int_val, pad_amount, ms_default_pad_char, 10);
        return string_builder;
    }
 
    //! Convert a given signed integer value to a string and concatenate onto the stringbuilder. Assume base ten.
    public static StringBuilder Concat(this StringBuilder string_builder, int int_val, uint pad_amount, char pad_char)
    {
        string_builder.Concat(int_val, pad_amount, pad_char, 10);
        return string_builder;
    }
 
    //! Convert a given float value to a string and concatenate onto the stringbuilder
    public static StringBuilder Concat(this StringBuilder string_builder, float float_val, uint decimal_places, uint pad_amount, char pad_char)
    {
        Debug.Assert(pad_amount >= 0);
 
        if (decimal_places == 0)
        {
            // No decimal places, just round up and print it as an int
 
            // Agh, Math.Floor() just works on doubles/decimals. Don't want to cast! Let's do this the old-fashioned way.
            int int_val;
            if (float_val >= 0.0f)
            {
                // Round up
                int_val = (int)(float_val + 0.5f);
            }
            else
            {
                // Round down for negative numbers
                int_val = (int)(float_val - 0.5f);
            }
 
            string_builder.Concat(int_val, pad_amount, pad_char, 10);
        }
        else
        {
            int int_part = (int)float_val;
 
            // First part is easy, just cast to an integer
            string_builder.Concat(int_part, pad_amount, pad_char, 10);
 
            // Decimal point
            string_builder.Append('.');
 
            // Work out remainder we need to print after the d.p.
            float remainder = Math.Abs(float_val - int_part);
 
            // Multiply up to become an int that we can print
            do
            {
                remainder *= 10;
                decimal_places--;
            }
            while (decimal_places > 0);
 
            // Round up. It's guaranteed to be a positive number, so no extra work required here.
            remainder += 0.5f;
 
            // All done, print that as an int!
            string_builder.Concat((uint)remainder, 0, '0', 10);
        }
        return string_builder;
    }
 
    //! Convert a given float value to a string and concatenate onto the stringbuilder. Assumes five decimal places, and no padding.
    public static StringBuilder Concat(this StringBuilder string_builder, float float_val)
    {
        string_builder.Concat(float_val, ms_default_decimal_places, 0, ms_default_pad_char);
        return string_builder;
    }
 
    //! Convert a given float value to a string and concatenate onto the stringbuilder. Assumes no padding.
    public static StringBuilder Concat(this StringBuilder string_builder, float float_val, uint decimal_places)
    {
        string_builder.Concat(float_val, decimal_places, 0, ms_default_pad_char);
        return string_builder;
    }
 
    //! Convert a given float value to a string and concatenate onto the stringbuilder.
    public static StringBuilder Concat(this StringBuilder string_builder, float float_val, uint decimal_places, uint pad_amount)
    {
        string_builder.Concat(float_val, decimal_places, pad_amount, ms_default_pad_char);
        return string_builder;
    }
 
    //! Concatenate a formatted string with arguments
    public static StringBuilder ConcatFormat<A>(this StringBuilder string_builder, String format_string, A arg1)
        where A : IConvertible
    {
        return string_builder.ConcatFormat<A, int, int, int>(format_string, arg1, 0, 0, 0);
    }
 
    //! Concatenate a formatted string with arguments
    public static StringBuilder ConcatFormat<A, B>(this StringBuilder string_builder, String format_string, A arg1, B arg2)
        where A : IConvertible
        where B : IConvertible
    {
        return string_builder.ConcatFormat<A, B, int, int>(format_string, arg1, arg2, 0, 0);
    }
 
    //! Concatenate a formatted string with arguments
    public static StringBuilder ConcatFormat<A, B, C>(this StringBuilder string_builder, String format_string, A arg1, B arg2, C arg3)
        where A : IConvertible
        where B : IConvertible
        where C : IConvertible
    {
        return string_builder.ConcatFormat<A, B, C, int>(format_string, arg1, arg2, arg3, 0);
    }
 
    //! Concatenate a formatted string with arguments
    public static StringBuilder ConcatFormat<A, B, C, D>(this StringBuilder string_builder, String format_string, A arg1, B arg2, C arg3, D arg4)
        where A : IConvertible
        where B : IConvertible
        where C : IConvertible
        where D : IConvertible
    {
        int verbatim_range_start = 0;
 
        for (int index = 0; index < format_string.Length; index++)
        {
            if (format_string[index] == '{')
            {
                // Formatting bit now, so make sure the last block of the string is written out verbatim.
                if (verbatim_range_start < index)
                {
                    // Write out unformatted string portion
                    string_builder.Append(format_string, verbatim_range_start, index - verbatim_range_start);
                }
 
                uint base_value = 10;
                uint padding = 0;
                uint decimal_places = 5; // Default decimal places in .NET libs
 
                index++;
                char format_char = format_string[index];
                if (format_char == '{')
                {
                    string_builder.Append('{');
                    index++;
                }
                else
                {
                    index++;
 
                    if (format_string[index] == ':')
                    {
                        // Extra formatting. This is a crude first pass proof-of-concept. It's not meant to cover
                        // comprehensively what the .NET standard library Format() can do.
                        index++;
 
                        // Deal with padding
                        while (format_string[index] == '0')
                        {
                            index++;
                            padding++;
                        }
 
                        if (format_string[index] == 'X')
                        {
                            index++;
 
                            // Print in hex
                            base_value = 16;
 
                            // Specify amount of padding ( "{0:X8}" for example pads hex to eight characters
                            if ((format_string[index] >= '0') && (format_string[index] <= '9'))
                            {
                                padding = (uint)(format_string[index] - '0');
                                index++;
                            }
                        }
                        else if (format_string[index] == '.')
                        {
                            index++;
 
                            // Specify number of decimal places
                            decimal_places = 0;
 
                            while (format_string[index] == '0')
                            {
                                index++;
                                decimal_places++;
                            }
                        }
                    }
 
 
                    // Scan through to end bracket
                    while (format_string[index] != '}')
                    {
                        index++;
                    }
 
                    // Have any extended settings now, so just print out the particular argument they wanted
                    switch (format_char)
                    {
                        case '0': string_builder.ConcatFormatValue<A>(arg1, padding, base_value, decimal_places); break;
                        case '1': string_builder.ConcatFormatValue<B>(arg2, padding, base_value, decimal_places); break;
                        case '2': string_builder.ConcatFormatValue<C>(arg3, padding, base_value, decimal_places); break;
                        case '3': string_builder.ConcatFormatValue<D>(arg4, padding, base_value, decimal_places); break;
                        default: Debug.Assert(false, "Invalid parameter index"); break;
                    }
                }
 
                // Update the verbatim range, start of a new section now
                verbatim_range_start = (index + 1);
            }
        }
 
        // Anything verbatim to write out?
        if (verbatim_range_start < format_string.Length)
        {
            // Write out unformatted string portion
            string_builder.Append(format_string, verbatim_range_start, format_string.Length - verbatim_range_start);
        }
 
        return string_builder;
    }
 
    //! The worker method. This does a garbage-free conversion of a generic type, and uses the garbage-free Concat() to add to the stringbuilder
    private static void ConcatFormatValue<T>(this StringBuilder string_builder, T arg, uint padding, uint base_value, uint decimal_places) where T : IConvertible
    {
        switch (arg.GetTypeCode())
        {
            case System.TypeCode.UInt32:
                {
                    string_builder.Concat(arg.ToUInt32(System.Globalization.NumberFormatInfo.CurrentInfo), padding, '0', base_value);
                    break;
                }
 
            case System.TypeCode.Int32:
                {
                    string_builder.Concat(arg.ToInt32(System.Globalization.NumberFormatInfo.CurrentInfo), padding, '0', base_value);
                    break;
                }
 
            case System.TypeCode.Single:
                {
                    string_builder.Concat(arg.ToSingle(System.Globalization.NumberFormatInfo.CurrentInfo), decimal_places, padding, '0');
                    break;
                }
 
            case System.TypeCode.String:
                {
                    string_builder.Append(Convert.ToString(arg));
                    break;
                }
 
            default:
                {
                    Debug.Assert(false, "Unknown parameter type");
                    break;
                }
        }
    }
 
    public static void Empty(this StringBuilder string_builder)
    {
        string_builder.Remove(0, string_builder.Length);
    }
 
 
    public static string GetGarbageFreeString(this StringBuilder string_builder)
    {
        return (string)string_builder.GetType().GetField("_str", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(string_builder);
    }
 
    public static void GarbageFreeClear(this StringBuilder string_builder)
    {
        string_builder.Length = 0;
        string_builder.Append(' ', string_builder.Capacity);
        string_builder.Length = 0;
    }
}
public static class StrExt
{
    //只允许拼接50个字符串
    private static StringBuilder s_StringBuilder = new StringBuilder(50, 50);
 
    public static string Format<A>(String format_string, A arg1)
            where A : IConvertible
    {
        s_StringBuilder.Empty();
        return s_StringBuilder.ConcatFormat<A, int, int, int>(format_string, arg1, 0, 0, 0).ToString();
    }
 
    public static string Format<A, B>(String format_string, A arg1, B arg2)
        where A : IConvertible
        where B : IConvertible
    {
        s_StringBuilder.Empty();
        return s_StringBuilder.ConcatFormat<A, B, int, int>(format_string, arg1, arg2, 0, 0).ToString();
    }
 
    public static string Format<A, B, C>(String format_string, A arg1, B arg2, C arg3)
        where A : IConvertible
        where B : IConvertible
        where C : IConvertible
    {
        s_StringBuilder.Empty();
        return s_StringBuilder.ConcatFormat<A, B, C, int>(format_string, arg1, arg2, arg3, 0).ToString();
    }
 
    public static string Format<A, B, C, D>(String format_string, A arg1, B arg2, C arg3, D arg4)
        where A : IConvertible
        where B : IConvertible
        where C : IConvertible
        where D : IConvertible
    {
        s_StringBuilder.Empty();
        return s_StringBuilder.ConcatFormat<A, B, C, D>(format_string, arg1, arg2, arg3, arg4).ToString();
    }
}

雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!

最后编辑:

作者:雨松MOMO

专注移动互联网,Unity3D游戏开发

站内专栏 QQ交谈 腾讯微博 新浪微博

捐 赠 如果您愿意花20块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

惡血

惡血

[美] 约翰·凯瑞鲁 / 林锦慧 / 商業周刊 / 2018-9-20 / NT$430

--新創神話!?揭露3000億獨創醫療科技的超完美騙局-- 她被譽為女版賈伯斯、《富比世》全球最年輕的創業女富豪, 如何用「一滴血」顛覆血液檢測、翻轉醫療產業? 一項即將改變你我健康的醫療檢測新科技, 而它的技術來自--謊言! ◎即將改編成電影,由奧斯卡影后珍妮佛‧勞倫斯(Jennifer Lawrence)主演 ◎榮登《紐約時報》、《出版人週刊》暢銷榜 ......一起来看看 《惡血》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具