从clr profiler的角度看string concat 和stringbuilder的性能差别

private int BUFSIZE = 10000;
private void button1_Click(object sender, System.EventArgs e)
{
 string s = "";
 long t1 = System.DateTime.Now.Ticks;
 for(int i=0;i<BUFSIZE;i++)s += "a";
 long t2 = System.DateTime.Now.Ticks;
 button1.Text = Convert.ToString(t2-t1);
}

private void button2_Click(object sender, System.EventArgs e)
{
 long t1 = System.DateTime.Now.Ticks;
 System.Text.StringBuilder sb = new System.Text.StringBuilder(BUFSIZE);
 for(int i=0;i<BUFSIZE;i++)sb.Append("a");
 long t2 = System.DateTime.Now.Ticks;
 button2.Text = Convert.ToString(t2-t1);
}

代码很简单,地球人也都知道,StringBuilder要比String的cat快很多。原理大家应该也都清楚,StringBuilder会预先分配2*capacity大小的内存来放置字符串内容。以后append的时候,尽可能的在当前内存块内操作。而String却是每次都重新new一个新对象。
现在我们从clr profiler的角度来看一下这个问题。

首先看strcat方式下的objects分配情况,如下图:

红色的string和黄色的byte[],一共分配了473966+67134=大约540K大小的内存。其中,黄色的byte[]被“挤”到了GC的第二代上。红色的目前是在第0代上,很少量的绿色object[](13K)被挤到了第1代上。
我们再看GC在压缩、回收的过程表现:

大概在第6秒的时候,注意上面白色的部分,和红色的尖峰部分。内存不定的被释放(free),然后不停的被申请,大概到第7.5秒的时候,内存释放与申请达到了一个平滑状态。

我们再看StringBuilder的表现,依旧是先看objects的分配状况:

红色的string和黄色的byte[],一共分配了133588+94911=大约127K大小的内存。其中,黄色的byte[]被“挤”到了GC的第1代上。红色的目前是在第0代上,很少量的cyan色char[](35K)被挤到了第1代上。
我们再看GC在压缩、回收的过程表现:

从图示中可以看到,没有白色的free的legend,即:没有发生此过程。为什么会这样?因为我们再StringBuilder的构造方法中,分配了2*BUFSIZE的大小。
那么,这个猜测是否正确呢?我们修改上面的button2_click的代码,修改为:

private void button2_Click(object sender, System.EventArgs e)
{
 long t1 = System.DateTime.Now.Ticks;
 System.Text.StringBuilder sb = new System.Text.StringBuilder(10);
 for(int i=0;i<BUFSIZE;i++)sb.Append("a");
 long t2 = System.DateTime.Now.Ticks;
 button2.Text = Convert.ToString(t2-t1);
}
然后再看看objects的分布和gc timeline的情况:

嘿嘿,还是没有free再malloc的过程。但是注意的一点是,最上面那一大块红色,变厚了(内存地址空间增大了),变短了(时间降低了)。

我没有做过多次试验,但是我猜测,因为GC本身回收的时机不确定,所以上面最后两个图看不出太大的区别了。

偶没仔细研究过stringbuilder的代码,所以上面的表述不一定完全正确,呵呵。

posted @ 2005-04-19 15:59 鞠强 阅读(...) 评论(...) 编辑 收藏

hello

world