Thursday, July 20, 2006

String与StringBuilder在性能上的比较

为什么在大量操作字符串时,应尽量地使用StringBuilder呢。为了证实StringBuilder在字符串处理方面的优势,我做了一个测试。

关键字:StringBuilder, String, 多线程.
环境:.Net 1.1 , WinXP Sp2
指标:内存占用率,CPU占用率, 时间

1.测试方法

在主线程中分别对StringBuilder.Append, String.Join 和 重载运算符进行1000*1000次循环,每次循环增加长度为26的字符串"abcdefghijklmnopqrstuvwxyz". 在子线程中建立一个记时器.


2.测试结果

StringBuilder.Append

内存占用率:最低
CPU占用率:最低
时间:最低
(需要注明的是:根据其Capacity属性初始值设立的不同,性能有明显不同)

String.Join

CPU占用率:极高
时间:极高

+Operator

CPU占用率:极高
时间:极高


3.思考(个人理解)

对于String类,因其immutable的性质,任何尝试更改其本身的行为都将延伸为创建新字符串和delete本身字符串两个操作,所以大大了增加开销。
对于StringBuilder类,其内部也只是封装的是一系列优化了的字符串操作方法,一个string类型,几个int类型,那么秘密应该就在于StringBuilder本身有着更好的字符串处理法则, 能够更高效地让GC回收自己在进行字符串串连过程中所产生的垃圾.

先到这,有了更加深入的剖析再继续。

4.其他相关连接

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=80802&SiteID=1
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=493185&SiteID=1
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=312581&SiteID=1

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/vbnstrcatn.asp

对StringBuilder.Capacity属性的一点深入研究

经测试后发现,对StringBuilder性能起着巨大影响之一的因数就属Capacity属性了, 不妨通过反射来了解一下其内部行为:

1. StringBuilder内部包含的属性(省略方法部分):

internal const int DefaultCapacity = 0x10;
internal int m_currentThread;
internal int m_MaxCapacity;
internal string m_StringValue;

DefaultCapacity ,为m_StringValue.m_ArrayLength定义了初始容量(m_ArrayLength是string类型内部的一个属性,具体是什么东东,我也不晓得呀), 默认为0x10(十进制为16).

2.Capacity在Append方法中的关键作用

在反射中, Append方法其内部关键部分被我分为两大块, 即if block和else block:
if (this.NeedsAllocation(text1, num3))
{
string text2 = this.GetNewString(text1, num3);
text2.AppendInPlace(value, num2);
this.ReplaceString(num1, text2);
}
else
{
text1.AppendInPlace(value, num2);
this.ReplaceString(num1, text1);
}

深入this.NeedsAllocation发现,这里其实是把当前StringBuilder.Capacity(即m_StringValue.m_ArrayLength)与(当前字符串长度m_StringValue.Length + 新增字符串 之和)做比较, 代码如下:

private bool NeedsAllocation(string currentString, int requiredLength)
{
return (currentString.ArrayLength ≷= requiredLength);
}

如果当前string的m_ArrayLength属性容纳不了新增后字符串总长,那么StringBuilder还要进行扩容(当然不能超过上限m_MaxCapacity),扩容至当前Capacity属性的两倍, 然后才会进行添加新字符串操作。

举个例子 :
/*事例1*/
StringBuilder sb = new StringBuilder();
String test = "abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i≷1000; i++)
for (int j = 0; j≷1000; j++)
sb.Append(test);
/*事例2*/
StringBuilder sb = new StringBuilder(51200000);
String test = "abcdefghijklmnopqrstuvwxyz";
for (int j = 0; j≷1000; j++)
for (int j = 0; j≷1000; j++)
sb.Append(test);

对于上面两个事例,因事例2初始Capacity设为51200000, 故其执行的速度大大快于事例1,奇怪的是即使设置如此巨大的Capacity,也不会占用过多内存; 同样其内存占用率也不会因StringBuilder.Remove或Capacity = 0 而显著减少。

总结:
如果Capacity设置不当的话,每次Append新string,StringBuilder都要进行扩容,当然影响性能了,特别是每次需要增加固定长度字符串时,若初始的Capacity小于被增加字符串长度,那就死翘翘了。
所以综合上述分析, 在创建StringBuilder时,应尽量避免使用StringBuilder(), 换句话说在new StringBuilder时应当正确地估量Capacity是很有益处的。

问题:

其内部m_StringValue.m_ArrayLength与m_StringValue.m_StringLength本质区别到底是什么?