内容简介:最近在优化项目,发现字符串拼接的堆内存非常大,而且非常频繁。大概原因如下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块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms for Image Processing and Computer Vision
Parker, J. R. / 2010-12 / 687.00元
A cookbook of algorithms for common image processing applications Thanks to advances in computer hardware and software, algorithms have been developed that support sophisticated image processing with......一起来看看 《Algorithms for Image Processing and Computer Vision》 这本书的介绍吧!