内容简介:最近在优化项目,发现字符串拼接的堆内存非常大,而且非常频繁。大概原因如下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); } }
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); } }
通通过这两张图大家可以看出来装箱和不装箱对GC有多大的影响了,还有最后的无GC版本。代码参考了下面的这两篇文章。
http://www.gavpugh.com/2010/04/05/xnac-a-garbage-free-stringbuilder-format-method/
http://www.gavpugh.com/2010/03/23/xnac-stringbuilder-to-string-with-no-garbage/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(); } }
- 本文固定链接: https://www.xuanyusong.com/archives/4601
- 转载请注明:雨松MOMO 于雨松MOMO程序研究院 发表
雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!
捐 赠 如果您愿意花20块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP项目开发全程实录
清华大学出版社 / 2008 / 56.00元
《软件项目开发全程实录丛书•PHP项目开发全程实录:DVD17小时语音视频讲解(附光盘1张)》主要特色: (1)12-32小时全程语音同步视频讲解,目前市场上唯一的“全程语音视频教学”的案例类 图书,培训数千元容,尽在一盘中! (2)10套“应用系统”并公开全部“源代码”,誓将案例学习进行到底! (3)丛书总计80个应用系统300个应用模块。 (4)含5000页SQL se......一起来看看 《PHP项目开发全程实录》 这本书的介绍吧!