Hello World
Spiga

重谈字符串连接性能(上):性能评测

2009-11-26 01:12 by 老赵, 15395 visits

看到这个标题是不是觉得很奇怪呢?字符串连接的性能,这个话题已经被谈了一遍又一遍,一次又一次,似乎已成定论,这又有什么好谈的呢?不过说来奇怪,根据我的实验结果在网上进行搜索,却找不到答案。因此,我现在和大家一起重新再作一次实验并观察结果。在文章最后我也会给出完整的代码,您可以自由地运行,修改,尝试,我们再一起进行交流。

说起字符串拼接,最简单的方式便是使用最普通的连接操作“+”,以及StringBuilder了。为此,我们准备一些测试代码:

private static readonly string STR = "0123456789";

private static string NormalConcat(int count)
{
    var result = "";
    for (int i = 0; i < count; i++) result += STR;
    return result;
}

private static string StringBuilder(int count)
{
    var builder = new StringBuilder();
    for (int i = 0; i < count; i++) builder.Append(STR);
    return builder.ToString();
}

两个方法都是将一个长度为10的字符串连接n次,形成一个很长的字符串。相信看到这两段代码您在心里可能已经有所答案了,不过别急,我们再准备一段代码。一直说StringBuilder在连接字符串的时候性能高,那是为什么呢?我们如果翻看《CLR via C#》,就会发现书中告诉我们说,在StringBuilder内部其实维护了一个类似于字符数组的结构,在不断添加字符串的过程中,这个数组会在需要的时候将容量加倍——这不就是List<T>的行为方式吗?那么我们自己来写一个CharListBuilder,也一起和StringBuilder比较一下性能:

public class CharListBuilder
{
    private List<char> m_list = new List<char>();

    public CharListBuilder Append(string s)
    {
        this.m_list.AddRange(s);
        return this;
    }

    public string ToString()
    {
        return new String(this.m_list.ToArray());
    }
}

private static string CharListBuilder(int count)
{
    var builder = new CharListBuilder();
    for (int i = 0; i < count; i++) builder.Append(STR);
    return builder.ToString();
}

开始实验:

CodeTimer.Initialize();

for (int i = 2; i <= 1024; i *= 2)
{
    CodeTimer.Time(
        String.Format("Normal Concat ({0})", i),
        10000,
        () => NormalConcat(i));

    CodeTimer.Time(
        String.Format("StringBuilder ({0})", i),
        10000,
        () => StringBuilder(i));

    CodeTimer.Time(
        String.Format("CharListBuilder ({0})", i),
        10000,
        () => CharListBuilder(i));
}

结果如下:

将结果绘制为图表:

由于字符串的普通连接操作和StringBuilder相比差距越来越大,当使用1024个字符串进行连接时两者已经没有任何可比性了,因此这幅图表只取了前7个阶段,也就是最多到128个字符串相连接。从结果中我们可以得到以下三个结论:

  • 对于字符串数量比较少的情况(从数据上来看大约是5-6个),StringBuilder的性能并不比普通连接操作来的快。因此,在任何地方都使用StringBuilder是不恰当的做法
  • 我们的CharListBuilder虽然“模拟”了StringBuilder的情况,但还是慢了许多。说明StringBuilder的内部实现并非想象中那么简单
  • CharListBuilder虽然比StringBuilder慢得多,但是在字符串数量多的情况下还是遥遥领先普通的字符串连接操作,说明不断生成新字符串的做法的确是性能杀手

我在互联网上找寻了半天,似乎各个实验做到这里就得到结论了,那就是:在字符串数量多的情况下,StringBuilder性能比普通连接操作来的好。其原因便是“普通连接操作实际上使用了String.Concat方法,每次都会生成新的字符串”。但是,如果您观察String.Concat方法,就会发现其实它接受的参数是一个String数组——那么,如果我们把要连接的字符串放在一个数组里,一次交给String.Concat进行连接,性能又会怎么样?不妨一试:

private static string StringConcat(int count)
{
    var array = new string[count];
    for (int i = 0; i < count; i++) array[i] = STR;
    return String.Concat(array);
}

但这种做法有个前提,就是我们必须事先知道有多少个字符串才能创建这样一个数组,而对于数量未知的情况就不太好办了。不过也没有关系,我们可以用List<string>先将所有字符串保存起来:

public class StringListBuilder
{
    private List<string> m_list = new List<string>();

    public StringListBuilder Append(string s)
    {
        this.m_list.Add(s);
        return this;
    }

    public string ToString()
    {
        return String.Concat(this.m_list.ToArray());
    }
}

private static string StringListBuilder(int count)
{
    var builder = new StringListBuilder();
    for (int i = 0; i < count; i++) builder.Append(STR);
    return builder.ToString();
}

再把这两种新的做法和StringBuilder进行比较(第一次的其他两种做法性能不堪一击,直接淘汰):

从数量级上看,这次的比较似乎还算“势均力敌”。不过还是绘制成图表看起来比较清楚:

如果用一句话来概括的话,那就是:“StringBuilder以明显了劣势垫底”。不过说实话,这个结果并没有出乎我的意料之外,事实上这本就是我进行这次实验的原因——我始终想不明白,StringBuilder的性能为什么会比String.Concat要高(因为互联网上到处都在“追捧”StringBuilder)。幸好,最终的结果证明了我的猜想:在字符串数量确定的情况下,String.Concat的性能要高于StringBuilder,而且其实字符串数量越多,差距越明显。

但这又是为什么呢?那么我们就把以下几个疑问留到下一次来解决吧:

  • 为什么StringBuilder性能比CharListBuilder要高?
  • 为什么String.Concat性能比StringBuilder要高?
  • 有什么办法可以改进StringBuilder的性能吗?

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

108 条回复

  1. 老赵
    admin
    链接

    老赵 2009-11-26 01:13:00

    有些朋友是不是看不到表格和图片啊?那么看这里吧,我把文章内容截了一幅图。
    http://cid-fba4447598b1d752.skydrive.live.com/self.aspx/Public/string-concat-perf-1.zip
    不过,还是尽快想办法直接看到吧,我不会长期截图的,而且其实看到“看不到的东西”,现在也应该是必备技能了……

  2. 青羽
    *.*.*.*
    链接

    青羽 2009-11-26 01:15:00

    好晚啊~~

  3. Nana's Lich
    *.*.*.*
    链接

    Nana's Lich 2009-11-26 01:45:00

    老赵前辈这篇文章所说的现象我也发现了,和前辈的结论是差不多的。
    (旧blog,上面的很多内容需要修正,除了没有附上代码以外,当时所考虑的情况也很有限)
    很期待下一篇的内容。

  4. OwnWaterloo
    *.*.*.*
    链接

    OwnWaterloo 2009-11-26 02:48:00

    Jeffrey Zhao:
    而且其实看到“看不到的东西”,现在也应该是必备技能了……


    哎……


    如果StringBuilder真像《CLR via C#》所说:


    在StringBuilder内部其实维护了一个类似于字符数组的结构,在不断添加字符串的过程中,这个数组会在需要的时候将容量加倍


    而不是: Append仅仅记录,ToString才求值。
    那比Concat慢还是有道理的。

    毕竟其他工作都相同,而Concat仅分配一次内存。


    使用不可变字符串就会碰到这个问题:意如何抑制过多中间结果生成。
    而且会导致设计api时,字符串上的操作特别多 —— 尽可一个函数就完成需要的工作,仅生成一个结果字符串而不生成中间字符串。


  5. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 03:09:00

    忍不住了……深入讨论等老赵下一篇再看看情况 =_=|||

    老赵其实也快把答案给说出来了,就差没把纸点破而已。关键自然是创建新对象并复制的次数不同:
    1、String.Concat()一次分配到位,StringBuilder不知道最终要Append多少所以得慢慢来,遇到不够用得再分配和复制。
    2、StringBuilder.ToString()有时候会从已有的内部“库存”复制出新的String,而String.Concat()一次复制完之后再ToString()肯定不会再从结果复制String(而是返回自身……不过也很少人对String调用ToString()吧)。

    CharListBuilder在Append()中用List<T>.AddRange(),而String实现了IEnumerable<T>但没实现ICollection<T>,在加到List<T>里的时候要做循环,通过enumerator一个个char取出、复制,而不像Array、String、StringBuilder内部用的复制那样基本上是一整块memcpy式的复制,会明显影响速度。
    CharListBuilder的ToString()也挺伤的,从List<T>.ToArray()就做了一次完整复制,然后new String()又做了一次完整复制。StringBuilder光在ToString()一点上就比CharListBuilder少了一轮复制。

    StringBuilder在线程安全方面有考虑。如果我们已知使用场景不需要线程安全的话可以去掉其中一些保护,性能会好一些。老赵肯定有什么高招让StringBuilder更快些,拭目以待 ^_^

    其实如果只拿StringBuilder来Append()的话就用老赵那个StringListBuilder就不错 XDD
    但考虑到Insert()、Remove()、Replace()之类的操作,这帖里的StringListBuilder就不太适用了。

    再多嘴一句,CLR目前的实现中String里面不包含对char[]实例的引用——String“就是”一个char数组(的内容),加个头。

    剩下的点留给大牛们了。睡觉……

  6. OwnWaterloo
    *.*.*.*
    链接

    OwnWaterloo 2009-11-26 03:17:00

    RednaxelaFX:
    再多嘴一句,CLR目前的实现中String里面不包含对char[]实例的引用——String“就是”一个char数组(的内容),加个头。



    想起一句话:源码之前,了无秘密~

  7. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 03:26:00

    @OwnWaterloo
    SSCLI是提供了源码,不过它无法代表当前版本的CLR的实现——没有任何保证里面是一样的,反正内部实现不影响外部接口。当前的String的结构我还是调试来确认的……SOS是帮了很大忙
    Vance Morrison说下一版CLR(v5么……?)很可能拿String开刀。到时候又得针对新版本再调试来确认一次了 =_=|||

  8. OwnWaterloo
    *.*.*.*
    链接

    OwnWaterloo 2009-11-26 03:37:00

    @RednaxelaFX
    经你这么一说……
    我终于回忆起硬盘上的sscli20_20060311.tgz是啥玩意了……

    在老赵前一篇文章里我留了言,明天帮我看看吧~
    晚安~_~


  9. Keep Walking
    *.*.*.*
    链接

    Keep Walking 2009-11-26 05:38:00

    怎么页面都无法查看啊

  10. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-11-26 07:07:00

    先学习
    后面排队
    呵呵

  11. gfw-un-fans[未注册用户]
    *.*.*.*
    链接

    gfw-un-fans[未注册用户] 2009-11-26 08:18:00

    看到不该看的东西,很容易,但本人还有一个喜好,应该也是别人也有的,那就是将它们copy-〉黏贴到邮箱-〉收藏起来,如果用iframe,对我就不有爱了……而且我本来以2M的速度来看文章,结果看到一堆框框,然后我又爬出去……终于看到一堆图片……看你点文章怎么那么累噢?

  12. Artech
    *.*.*.*
    链接

    Artech 2009-11-26 08:19:00

    @Jeffrey Zhao
    Jeff最近和Performance铆上了,小心走火入魔:)

  13. Kevin Zou
    *.*.*.*
    链接

    Kevin Zou 2009-11-26 08:28:00

    隻看結論,呵呵,太功利了

  14. 张蒙蒙
    *.*.*.*
    链接

    张蒙蒙 2009-11-26 08:40:00

    google最新浏览器,无法查看试验结果。
    错误如下:

    此网页无法访问。

    http://spreadsheets.google.com/pub?key=tZNqHXSSSiP6609EskRF_Lw&single=true&gid=0&output=html&widget=true 的网页可能暂时无法连接,或者它已被永久性地移动到新网址。

    有关此错误的更多信息

  15. llh
    *.*.*.*
    链接

    llh 2009-11-26 08:55:00

    以前只知道stringbuilder的性能要比+=好,还真不知道为什么。受益了。

  16. 李永京
    *.*.*.*
    链接

    李永京 2009-11-26 09:02:00

    看不到~~

  17. 老赵
    admin
    链接

    老赵 2009-11-26 09:07:00

    llh:以前只知道stringbuilder的性能要比+=好,还真不知道为什么。受益了。


    咋只关注了这个……

  18. 老赵
    admin
    链接

    老赵 2009-11-26 09:07:00

    张蒙蒙:
    google最新浏览器,无法查看试验结果。
    错误如下:

    此网页无法访问。


    快想办法看到吧。

  19. 老赵
    admin
    链接

    老赵 2009-11-26 09:07:00

    李永京:看不到~~


    快快想办法看到。

  20. 老赵
    admin
    链接

    老赵 2009-11-26 09:08:00

    Artech:
    @Jeffrey Zhao
    Jeff最近和Performance铆上了,小心走火入魔:)


    hmmm……我的目的是总结出各种性能场景,然后实际使用过程中,哪个地方遇到瓶颈就哪个地方进行优化……

  21. 老赵
    admin
    链接

    老赵 2009-11-26 09:08:00

    gfw-un-fans:看到不该看的东西,很容易,但本人还有一个喜好,应该也是别人也有的,那就是将它们copy-〉黏贴到邮箱-〉收藏起来,如果用iframe,对我就不有爱了……而且我本来以2M的速度来看文章,结果看到一堆框框,然后我又爬出去……终于看到一堆图片……看你点文章怎么那么累噢?


    哎,不能怪我呀,我也是un-fan……

  22. 老赵
    admin
    链接

    老赵 2009-11-26 09:09:00

    RednaxelaFX:
    @OwnWaterloo
    SSCLI是提供了源码,不过它无法代表当前版本的CLR的实现——没有任何保证里面是一样的,反正内部实现不影响外部接口。当前的String的结构我还是调试来确认的……SOS是帮了很大忙
    Vance Morrison说下一版CLR(v5么……?)很可能拿String开刀。到时候又得针对新版本再调试来确认一次了 =_=|||


    不知道通过Refactor看源码能不能说明问题呢?我不想动SOS的,我很懒……
    RFX兄你也别抱太大希望啊,你和我的标准不一样滴,说不定满足不了你,呵呵。

  23. 老赵
    admin
    链接

    老赵 2009-11-26 09:12:00

    那个那个,某些朋友的回复因为某些原因删了啊,不好意思……

  24. feiyang68
    *.*.*.*
    链接

    feiyang68 2009-11-26 09:12:00

    页面有几张图片没显示出来,不知何故.

  25. 老赵
    admin
    链接

    老赵 2009-11-26 09:14:00

    @李永京
    ssh好

  26. 老赵
    admin
    链接

    老赵 2009-11-26 09:23:00

    @李永京
    会啊

  27. 感觉没了
    *.*.*.*
    链接

    感觉没了 2009-11-26 09:28:00

    是我的被删了么。。。

    没这么敏感把

  28. 老赵
    admin
    链接

    老赵 2009-11-26 09:29:00

    @感觉没了
    是的是的,不好意思啊

  29. dk163
    *.*.*.*
    链接

    dk163 2009-11-26 09:32:00

    期待下一篇

  30. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 09:37:00

    Jeffrey Zhao:
    不知道通过Refactor看源码能不能说明问题呢?我不想动SOS的,我很懒……
    RFX兄你也别抱太大希望啊,你和我的标准不一样滴,说不定满足不了你,呵呵。


    嗯Reflector的话也算是“源码”吧,虽然原本的注释就全看不到了,行号也不对应,不过至少逻辑都跟原本的一致。
    只不过Reflector能看到的只有managed的一侧,而native一侧也可能影响对象的结构,就很难一眼看出究竟。例如String里的m_firstChar,光看C#一侧的源码会以为它就是个普通的char,但它实际上是String内部的char数组起始的标记……这就考验人了 =_=||

  31. 老赵
    admin
    链接

    老赵 2009-11-26 09:43:00

    @RednaxelaFX
    还好啦,因为它使用的是 &this.m_firstChar 形式,所以能判断的出是怎么回事,再配合其他方法的一些unsafe方法,我感觉基本上差不多了,呵呵。

  32. 程序设计的艺术
    *.*.*.*
    链接

    程序设计的艺术 2009-11-26 09:45:00

    老赵,能不能增加点架构方面的文章?

  33. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 09:57:00

    Jeffrey Zhao:
    @RednaxelaFX
    还好啦,因为它使用的是 &this.m_firstChar 形式,所以能判断的出是怎么回事,再配合其他方法的一些unsafe方法,我感觉基本上差不多了,呵呵。


    相信很多天天用C#的人也没见过fixed关键字和&作为取地址运算符的用法……因为平时也不会需要用

  34. 韦恩卑鄙 alias:v-zhewg
    *.*.*.*
    链接

    韦恩卑鄙 alias:v-zhewg 2009-11-26 10:16:00

    大家在学习中有一个对已有类库的理解和信任的问题。

    在刚学的时候 容易"给个代码吧" "给个结论吧" "给个答案吧"
    不理解就信任 叫做盲目信任

    后来逐渐理解了 信任就动摇了 有可能盲目的部分皲裂 崩落 把好多正确的知识也给影响到 ,搞不好变成完全不信任

    完全理解之后 至少信任中的盲目部分是荡然无存的 然后大家用理解到的知识从新建立正确的信任

    就好象不看 linked list代码 永远想不到他是一个循环链表
    不看queue的代码 永远不知道他是一个array[] 作的循环队列

    事实是在看 Concat (string[])代码后 我想不出比这个更有效的连接算法


    让我继续用StringBuilder的主要原动力 在于其中具有使更复杂操作"replace" 大幅度减少消耗的可能性。

    我实在不能保证 在目前场景中仅仅为Concat 需求的一段代码 在将来不会增加replace等操作 所以还是尽量推荐用builder

    另一个阴暗理由: 我喜欢在代码里面写 var sb=....

    我喜欢sb这个缩写

  35. 迭戈_Forever
    *.*.*.*
    链接

    迭戈_Forever 2009-11-26 10:27:00

    Jeffrey Zhao:
    有些朋友是不是看不到表格和图片啊?那么看这里吧,我把文章内容截了一幅图。
    http://cid-fba4447598b1d752.skydrive.live.com/self.aspx/Public/string-concat-perf-1.zip
    不过,还是尽快想办法直接看到吧,我不会长期截图的,而且其实看到“看不到的东西”,现在也应该是必备技能了……


    1. 我尝试了网上给的一些 方法,还是无法直接查看您的图表。
    2. 对于您给的截图,我下载下来,发现无法打开,已经被破坏。
    3. 不知道怎么才能直接查看您的截图。

  36. 老赵
    admin
    链接

    老赵 2009-11-26 10:29:00

    @迭戈_Forever
    再下载一次,或者用7-zip打开。
    winrar有时候会无法打开7-zip压缩的zip包。
    很奇怪。

  37. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 10:40:00

    韦恩卑鄙 alias:v-zhewg:
    另一个阴暗理由: 我喜欢在代码里面写 var sb=....

    我喜欢sb这个缩写


    啊……我也是

  38. 颜斌
    *.*.*.*
    链接

    颜斌 2009-11-26 10:59:00

    RednaxelaFX:

    韦恩卑鄙 alias:v-zhewg:
    另一个阴暗理由: 我喜欢在代码里面写 var sb=....

    我喜欢sb这个缩写


    啊……我也是


    好吧,其实我也很喜欢。。。

  39. toEverybody
    *.*.*.*
    链接

    toEverybody 2009-11-26 11:03:00

    现在微软解决了开发中代码的管理问题,也解决了开发的效率问题,可能微软下一步将解决开发出来的程序的性能的问题

  40. 止戈
    *.*.*.*
    链接

    止戈 2009-11-26 11:10:00

  41. Daniel Cai
    *.*.*.*
    链接

    Daniel Cai 2009-11-26 11:37:00

    颜斌:

    RednaxelaFX:

    韦恩卑鄙 alias:v-zhewg:
    另一个阴暗理由: 我喜欢在代码里面写 var sb=....

    我喜欢sb这个缩写


    啊……我也是


    好吧,其实我也很喜欢。。。


    ME 2

  42. 冷冷
    *.*.*.*
    链接

    冷冷 2009-11-26 11:58:00

    嗯,有一次在做一个外部RSS新闻字符串拼接的时候在string和SB间犹豫过,后来做了一个小的测试居然惊奇的发现当字符串的连接次数不是很大时string的性能居然比SB要高出很多~~~

  43. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-26 12:11:00

    用Reflector还是有些东西看不到的,比如String的构造函数,Remove,Replace等方法都是CLR中实现的。另外没有注释也不太爽,能看源码的话还是源码方便。

  44. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-11-26 12:16:00

    其实开了unsafe的话我们也可以写得足够高效

    Reflector貌似还是可以看到大部分东西的 String 和StringBuilder都很全了

  45. oec2003
    *.*.*.*
    链接

    oec2003 2009-11-26 12:36:00

    Daniel Cai:

    颜斌:

    RednaxelaFX:

    韦恩卑鄙 alias:v-zhewg:
    另一个阴暗理由: 我喜欢在代码里面写 var sb=....

    我喜欢sb这个缩写


    啊……我也是


    好吧,其实我也很喜欢。。。


    ME 2



    呵呵 好像大家都喜欢啊

  46. sewage[未注册用户]
    *.*.*.*
    链接

    sewage[未注册用户] 2009-11-26 12:41:00

    终于看明白一篇

  47. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 12:47:00

    StringBuilder的变量我都写builder的。。。。。你们太阴暗了。。。。。


    其实unsafe也不见得能提高多少性能,该优化的地方微软都尽可能的优化了。。。。

  48. 老赵
    admin
    链接

    老赵 2009-11-26 12:50:00

    @Ivony...
    unsafe的适用范围不多的,只能是简单数据类型,然后连续的,密集的,比如数组操作等等才可能用得到……一般要优化到那种程度就nb大发了……的确很多地方微软也已经搞好了。

  49. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-26 12:57:00

    只有在针对内存的操作时,使用指针能稍微提升性能,但是95%的情况下这种差别可以忽略不计。
    性能的提升主要还是靠算法。

  50. 小城故事
    *.*.*.*
    链接

    小城故事 2009-11-26 13:10:00

    5个以下直接拼接,5个以上用stringbuilder,跟偶的经验一样。
    关键是string这东西实现的机制

  51. Zhenway
    *.*.*.*
    链接

    Zhenway 2009-11-26 13:28:00

    说白了就是减少创建实例

    在第二个测试里面:
    StringConcat方法,实例化string数组一次,实例化string一次
    总共创建2次实例

    StringListBuilder方式,实例化StringListBuilder本身,实例化List<string>,在添加过程中,实例化内部的string数组log N次(平均)(建议在实例化List的时候给出容量,减少不必要的实例化内部的string数组),ToArray时,实例化一个string数组,然后调用String.Concat方法,其中实例化string一次
    总共创建4+log N次实例

    StringBuilder方式,实例化StringBuilder一次,实例化内部的可变string log N次(平均),每次添加再额外考虑线程安全,最后ToString时再创建一个string实例
    总共创建2+log N次实例,并且有额外的线程安全处理

  52. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-11-26 14:26:00

    Ivony...:
    StringBuilder的变量我都写builder的。。。。。你们太阴暗了。。。。。


    其实unsafe也不见得能提高多少性能,该优化的地方微软都尽可能的优化了。。。。


    Jeffrey Zhao:
    @Ivony...
    unsafe的适用范围不多的,只能是简单数据类型,然后连续的,密集的,比如数组操作等等才可能用得到……一般要优化到那种程度就nb大发了……的确很多地方微软也已经搞好了。


    大部分时候没必要 我的意思是如果性能要达到sb的程度 用unsafe就好了

  53. 一抹红
    *.*.*.*
    链接

    一抹红 2009-11-26 14:36:00

    我的猜想是:用StringBulider时,每次连接还是要生成一个新字符串,只不过此时内存中产生了一个“字符串内存池”,把以前的操作过的字符串驻留了,这样就比普通操作字符串高效谢吧,所以会有在少量字符串连接时看不到stringbulider比普通的连接快的原因吧

  54. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 15:18:00

    我尝试回答一下,

    * 为什么String.Concat 性能比 StringBuilder 要高?

    经过追踪发现,
    String.Concat 和 StringBuilder 内部都调用了下面的外部方法来快速分配所需的空间,

    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern string FastAllocateString(int length);

    但是区别是,String.Concat 一次性计算出传入的所有字符串的总长度,然后一次性分配;
    而 StringBuilder 在每次发现容量不够的时候就会使 capacity * 2 然后调用一次该空间分配操作。

    * 有什么办法可以改进StringBuilder的性能吗?
    预估结果字符串的长度,然后用下列构造器创建 StringBuilder 对象,
    public StringBuilder(int capacity) : this(string.Empty, capacity)

    以达到减少多次重复分配空间的目的。

  55. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 15:29:00

    * 为什么 StringBuilder 性能比 CharListBuilder 要高?

    CharListBuilder 内部用的是 List<char>,他在 AddRange 调用时,如果容量不够,使用的操作是重新分配一个 char[] 然后 Array.Copy 的做法,这里分配的效率可能比较低一点。

    而 StringBuilder 在同样的情况下使用了

    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern string FastAllocateString(int length);


    private static unsafe void wstrcpy(char* dmem, char* smem, int charCount)

    猜想这个可能要快一些。

    不知道说的对不对。

  56. 老赵
    admin
    链接

    老赵 2009-11-26 15:37:00

    @一抹红
    这里不会驻留的……

  57. 老赵
    admin
    链接

    老赵 2009-11-26 15:38:00

    @木野狐(Neil Chen)
    木老大好强大

  58. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 15:52:00

    @Jeffrey Zhao
    我瞎猜的。。。
    btw, 刚才试了一下好像可以用反射调用到这个方法,也许可以做一下性能测试?

    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern string FastAllocateString(int length);

    ==
    var mi = typeof(string).GetMethod("FastAllocateString", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

    string a = (string)mi.Invoke(null, new object[] { 10 });

    Console.WriteLine(a.Length); // 10

  59. 老赵
    admin
    链接

    老赵 2009-11-26 16:03:00

    @木野狐(Neil Chen)
    嗯嗯,只要方法存在,一般总是可以反射到的。

  60. Will Meng
    *.*.*.*
    链接

    Will Meng 2009-11-26 16:09:00

    Jeffrey Zhao:
    @迭戈_Forever
    再下载一次,或者用7-zip打开。
    winrar有时候会无法打开7-zip压缩的zip包。
    很奇怪。


    让我很纳闷的是,经过多次尝试,无论是重复下载(这个其实一开始我就试过了),或者按照您说的使用7-zip,都无法打开压缩包。
    另外,可不可以说一下怎么在网页直接看呢?

  61. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 16:14:00

    我这边Reflector看到StringBuilder使用的是String.AppendInPlace方法。通过对这个方法的调查可以发现,string并不总是由等同于其Length的连续的char(不妨称之为charArray)组成的,StringBuilder实际上使用了超量分配空间的string而不是char[]。

  62. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 16:14:00

    @Jeffrey Zhao
    哈哈,大致测试了一下。
    用 FastAllocateString() 比 new string[] 大致要快一倍。
    但是一定要用你的 FastReflectionLib 才行 :)

  63. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 16:18:00

    测试代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Diagnostics;
    using System.Linq.Expressions;
    using System.Reflection;
    
    namespace ConsoleApplication9
    {
        class Program
        {
            static void Main(string[] args)
            {
                //[MethodImpl(MethodImplOptions.InternalCall)]private static extern string FastAllocateString(int length);
    
                var mi = typeof(string).GetMethod("FastAllocateString", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
    
                string a = (string)mi.Invoke(null, new object[] { 10 });
    
                //string b = new string('\0', 10);
    
                //Console.WriteLine(a.Length); // 10
                //Console.WriteLine((int)a[0]);
                //Console.WriteLine((int)b[0]);
    
                var watch = new Stopwatch();
                 
                // Test new string()
                watch.Start();
    
                for (var i = 0; i <= 200000; i++)
                {
                    string c = new string('\0', 100);
                }
                watch.Stop();
    
                Console.WriteLine("Test new string(): " + watch.ElapsedMilliseconds);
    
                watch.Reset();
    
                // Test FastAllocateString()
                var f = CreateInvokeDelegate(mi);
    
                watch.Start();
    
                var param = new object[] { 100 };
    
                for (var i = 0; i <= 200000; i++)
                {
                    //string d = (string)mi.Invoke(null, param);
                    string d = (string)f(null, param);
                }
                watch.Stop();
    
                Console.WriteLine("Test FastAllocateString(): " + watch.ElapsedMilliseconds);
    
                Console.ReadLine();
            }
    
            // From jeffz's FastReflectionLib:
            // http://fastreflectionlib.codeplex.com/SourceControl/ListDownloadableCommits.aspx
            private static Func<object, object[], object> CreateInvokeDelegate(MethodInfo methodInfo)
            {
                // Target: ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
    
                // parameters to execute
                var instanceParameter = Expression.Parameter(typeof(object), "instance");
                var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
    
                // build parameter list
                var parameterExpressions = new List<Expression>();
                var paramInfos = methodInfo.GetParameters();
                for (int i = 0; i < paramInfos.Length; i++)
                {
                    // (Ti)parameters[i]
                    BinaryExpression valueObj = Expression.ArrayIndex(
                        parametersParameter, Expression.Constant(i));
                    UnaryExpression valueCast = Expression.Convert(
                        valueObj, paramInfos[i].ParameterType);
    
                    parameterExpressions.Add(valueCast);
                }
    
                // non-instance for static method, or ((TInstance)instance)
                var instanceCast = methodInfo.IsStatic ? null :
                    Expression.Convert(instanceParameter, methodInfo.ReflectedType);
    
                // static invoke or ((TInstance)instance).Method
                var methodCall = Expression.Call(instanceCast, methodInfo, parameterExpressions);
    
                // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
                if (methodCall.Type == typeof(void))
                {
                    var lambda = Expression.Lambda<Action<object, object[]>>(
                            methodCall, instanceParameter, parametersParameter);
    
                    Action<object, object[]> execute = lambda.Compile();
                    return (instance, parameters) =>
                    {
                        execute(instance, parameters);
                        return null;
                    };
                }
    
    
    

  64. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 16:19:00

                else
                {
                    var castMethodCall = Expression.Convert(methodCall, typeof(object));
                    var lambda = Expression.Lambda<Func<object, object[], object>>(
                        castMethodCall, instanceParameter, parametersParameter);
    
                    return lambda.Compile();
                }
            }
        }
    }
    

  65. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 16:43:00

    木野狐(Neil Chen):
    * 为什么 StringBuilder 性能比 CharListBuilder 要高?

    CharListBuilder 内部用的是 List<char>,他在 AddRange 调用时,如果容量不够,使用的操作是重新分配一个 char[] 然后 Array.Copy 的做法,这里分配的效率可能比较低一点。

    而 StringBuilder 在同样的情况下使用了

    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern string FastAllocateString(int length);


    private static unsafe void wstrcpy(char* dmem, char* smem, int charCount)

    猜想这个可能要快一些。

    不知道说的对不对。


    虽说调用的方法是那几个,不过主要问题应该不是出在那里吧。

    List<char>的内部存储是char[]。它的复制是通过Array.CopyTo(),内部又是Array.Copy()来完成的。String的内部存储是它自己包含的一个char数组(注意我不是说char[]——char[]是一个类型,char数组在我这里的上下文中仅表示一个概念)。String的内部复制是用String.wstrcpy()。
    可以看出List<char>跟String在最耗空间的部分是相似的,元素类型都是char。这要跟后面StringListBuilder区分开:StringListBuilder里的List<string>装的是string[],这个数组里装的是引用而不是字符串内容本身。
    在32位CLRv2里,Array.Copy()与String.wstrcpy()里最耗时间的复制的逻辑其实是一样的:把头尾不对齐的部分单独复制,中间的循环里每个复制操作以DWORD(32位,4字节)为单位,循环一轮复制4个单位也就是16字节。所以这部分两者耗时不会差多少。

    CharListBuilder中的List<char>在遇到空间不足时要先分配char[],然后通过Array.CopyTo()复制;StringBuilder在遇到空间不足时要先通过FastAllocateString()分配新的String,然后通过String.wstrcpy()复制。这部分的逻辑本质上也是一样的。
    String之所以不new出来而是用FastAllocateString()是因为它是个CLR instrinsic的特殊对象,在分配空间时要“作弊”——从managed一侧看到的String的最后一个成员是char m_firstChar,但实际上从那个地方开始后面就是一个char数组。调用FastAllocateString()分配新String跟调用new char[len]分配新char[]在性能上不会差多少。

    但是List<char>.AddRange(IEnumerable<char>)在接受String为参数时会有性能问题:String的内容要抽取出来加到List<char>的char[]时,只能通过String提供的enumerator一个个char来做,每轮循环只复制1个char(2字节),与Array.Copy()/String.wstrcopy()相比效率就显得低下。

    老赵在出这篇文章的下半篇时,如果能像Rico Mariani的Performance Quiz系列那样,用profiler数据来配合分析的话,相信能拿出更有说服力、粒度更细的数据来 ^_^

  66. 老赵
    admin
    链接

    老赵 2009-11-26 16:46:00

    @RednaxelaFX
    其实我本只打算分析分析,测试测试代码的,最多和你一样“推测”一番……不过说不定可以尝试着profiler一下……

  67. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 16:47:00

    @Jeffrey Zhao
    现在老赵很有被逼上贼船的感觉么…… XDD

  68. 老赵
    admin
    链接

    老赵 2009-11-26 16:54:00

    @RednaxelaFX
    没错,逼我去学习了……人都是逼出来的!

  69. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-11-26 17:01:00

    RednaxelaFX 老神仙说的是对的. 才发现 AddRange(string) 会用到 enumerator. 刚才代码看的不仔细...

  70. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-26 18:10:00

    List<char>.AddRange(IEnumerable<char>)
    里面如果发现传进来的其实是Collection的话,就会用类似Array.Copy的方法来拷。难道说String没有实现ICollection,只实现了IEumerable? 这样的话……

  71. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-26 19:02:00

    前面几位都说了,AddRange()会对参数作判断,如果参数实现了ICollection<T>的话,会用Array.Copy();否则会用Enumerator.MoveNext()一个一个复制。因此CharListBuilder只要把Append的实现改成这样:
    m_list.AddRange(s.ToCharArray())
    就能提升不少性能。

  72. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 19:22:00

    装配脑袋:
    List<char>.AddRange(IEnumerable<char>)
    里面如果发现传进来的其实是Collection的话,就会用类似Array.Copy的方法来拷。难道说String没有实现ICollection,只实现了IEumerable? 这样的话……


    嗯,确实是没有实现ICollection<T>的……以前我还特别查过,印象深刻orz

  73. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 21:43:00

    其实我还有一个问题不明白,望大牛解答。

    string的charArray(也就是连续的char,不同于char[])是分配在哪里的。如果在托管堆上,我们都知道托管堆上对象由于GC的缘故会飘来飘去的,所以才要fixed来钉住对象,这样才能用指针。。。。。

    莫非是string分配在传说中的字符串缓冲区不受GC管辖?

  74. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-26 22:13:00

    @Ivony...
    string是分配在托管堆上,这和GC有什么冲突?

  75. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 22:31:00

    幸存者:
    @Ivony...
    string是分配在托管堆上,这和GC有什么冲突?




    托管堆上的对象会飘来飘去的。。。。
    因为GC的一个任务是合并内存碎片。

    http://msdn.microsoft.com/zh-cn/library/bb384618.aspx

  76. OwnWaterloo
    *.*.*.*
    链接

    OwnWaterloo 2009-11-26 22:35:00

    @Ivony...
    程序员能引用到的整个string对象,而不是string中的char数组

    btw: sscli我没看,上面只是猜的。

  77. 老赵
    admin
    链接

    老赵 2009-11-26 22:36:00

    @Ivony...
    所以StringBuilder在处理的时候自然会fix啊,这和单个String对象没有什么关系吧。

  78. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-11-26 22:50:00

    我想,String.Concat有可能是一个比较底层的方法,能够访问CLR的字符串驻留集,访问Raw Memory,所以性能好~~

  79. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 22:53:00

    @Jeffrey Zhao


    哈,我明白了,这个m_firstChar是一私有字段,NND,这把戏玩的。所以GC移动string对象的时候,&m_firstChar也就同步被移动了。

    我一时脑子短路以为string里面存的&m_firstChar。

  80. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-26 22:55:00

    @Ivony...
    我还没太明白你的意思。只有使用指针的时候才需要fixed,如果你只是引用一个string或者char[]之类的对象,完全不用考虑GC的行为啊。
    另外,虽然还是有办法得到并修改string内部的char数组,但那必须是unsafe代码,此时你自然要加上fixed。

  81. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 23:03:00

    幸存者:
    @Ivony...
    我还没太明白你的意思。只有使用指针的时候才需要fixed,如果你只是引用一个string或者char[]之类的对象,完全不用考虑GC的行为啊。
    另外,虽然还是有办法得到并修改string内部的char数组,但那必须是unsafe代码,此时你自然要加上fixed。




    是我脑子短路了,我以为String里面存的是&m_firstChar,这样这个charArray如果移动了,&m_firstChar就作废了。

    但实际上String里面存的是m_firstChar,换言之这个charArray其实就是String对象的一部分,只不过暴露给我们托管的看到的只有一个m_firstChar。而这个m_firstChar其实本身没意义,&m_firstChar才有意义,可以把这个m_firstChar看作fixed char[xxx]。

    正是由于m_firstChar是String的一个字段,所以GC在移动String对象的时候,显然这个字段也会移动,也就是同步移动了&m_firstChar。

  82. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-26 23:05:00

    @Ivony...
    虽然string类所有的构造函数都是CLR实现的,但是我们还是可以根据其在Managed Code部分的定义了解一些细节。string类内部持有一些保存string信息的字段,并且在内存中是连续分布的。其中最后一个字段也就是m_firstChar也就是其内部char数组的第一个字符,而Managed Code想访问char数组的数据就必须通过对m_firstChar取地址来获取内部char数组首地址的指针。

  83. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 23:09:00

    幸存者:
    @Ivony...
    虽然string类所有的构造函数都是CLR实现的,但是我们还是可以根据其在Managed Code部分的定义了解一些细节。string类内部持有一些保存string信息的字段,并且在内存中是连续分布的。其中最后一个字段也就是m_firstChar也就是其内部char数组的第一个字符,而Managed Code想访问char数组的数据就必须通过对m_firstChar取地址来获取内部char数组首地址的指针。




    嗯,和我想的一致,不过我刚刚去看了String的IL,并没有发现这三个私有字段有什么不寻常的或者关于内存分布的Attribute。可能是String的构造函数捣的鬼,是采用非常手段构建的,所以不需要在这些字段上加Attribute告诉CLR,而是直接从CLR接管构建对象的操作。

  84. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 23:13:00

    Ivony...:
    @Jeffrey Zhao


    哈,我明白了,这个m_firstChar是一私有字段,NND,这把戏玩的。所以GC移动string对象的时候,&m_firstChar也就同步被移动了。

    我一时脑子短路以为string里面存的&m_firstChar。


    嗯,m_firstChar只是个marker,方便引用。这就是传说中CLR里的String内部融合了char[]的真相……String内部没有对char[]实例的引用,也不是把一个char[]实例直接融合到自己内部(那样char[]实例自己的对象头就还存在),而是把char[]没用的对象头部分扔掉,留下了数组长度和数组内容融合到自己内部。

    CLR里有好些对象都用了这把戏,String是managed一侧比较典型的;而native一侧的MethodTable之类也是这样,在最后长度未定的部分的开头处打个marker方便访问。

    @JimLiu
    CLR中String.Concat()是使用了显式的指针运算,但managed code里的指针运算也不是直接操作raw memory,而是受到CLR的监管的。另外CLR中String.Concat()的实现方式与Intern()完全没关系。

  85. 老赵
    admin
    链接

    老赵 2009-11-26 23:17:00

    @RednaxelaFX
    其实我觉得你们把一切都说完了,下篇我没法写了,估计都变成总结你们的东西了。

  86. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-26 23:26:00

    Jeffrey Zhao:
    @RednaxelaFX
    其实我觉得你们把一切都说完了,下篇我没法写了,估计都变成总结你们的东西了。




    可以考虑写个UnsafeCharArrayBuilder试试性能。。。。。

  87. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-11-26 23:56:00

    @Jeffrey Zhao
    这不本来也还没点破的么……但老赵发帖的时候也应该猜到会有这种状况了吧 >_<
    不过至少profile数据还没人贴过,这个绝对有写的价值。各个知识点也可以展开写,能写出很多东西……

    提供灵感一个:HotSpot VM的一个未来发展方向就是允许VM把有包含关系的对象“融合”在一起。
    例如HotSpot里的java.lang.String里就有对char[]的引用,假设成员叫value。假设该成员位于对象的+8偏移量处。那么要实现String.charAt(0)的话,至少得这么做:(假设ECX里有指向一个String对象的指针)

    mov   eax, dword ptr [ecx + 8] ; 获取value,一次间接访问
    movzx eax, word ptr [eax + Ch] ; 获取数组中第一个char,又一次间接访问

    但如果一个char[]的实例与一个String实例融合,同时两者都保持原有的完整信息(包括两者各自的对象头)的话,String.charAt(0)的功能只要这样就可以实现:
    movzx eax, word ptr [ecx + 30h] ; 一次间接访问

    这样就能减少间接访问的次数,同时还能提高/维持locality,好处多多。实现有那么点麻烦就是了。

    这种由VM提供的“通用”融合功能,跟CLR里String手工的实现“融合”,也是个有趣的对比点,展开写也可以写不少。

  88. 幸存者
    *.*.*.*
    链接

    幸存者 2009-11-27 00:24:00

    @RednaxelaFX
    这样虽然“读”的性能会提高,但是也会大大增加“写”的开销。
    比如对“融合”的字段重新赋值则需要调整内存布局,还可能需要重新分配一块内存然后再copy。
    我在想究竟是什么样的应用场景可能会需要“融合”这样的特性。
    不过这个话题扯得有点远了,原本这篇文章只是对各种字符串连接方法的一个比较。

  89. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-27 00:29:00

    幸存者:
    @RednaxelaFX
    这样虽然“读”的性能会提高,但是也会大大增加“写”的开销。
    比如对“融合”的字段重新赋值则需要调整内存布局,还可能需要重新分配一块内存然后再copy。
    我在想究竟是什么样的应用场景可能会需要“融合”这样的特性。




    所以融合应该是用于确保对“融合”的字段不可能重新赋值的场景(我猜的),例如C#里面有个关键字叫readonly(Java似乎是final)。或者“融合”的字段的类型是封闭类型(C#关键字是sealed,Java的关键字还是final)。

    不过即使是封闭类型,如果重新赋值,还是存在将对象从融合的对象中拷贝出来,再把新对象拷贝进去的额外开销。

    也说不定CLR x.0就支持在readonly字段上加一个[Including]就支持融合。。。。。

  90. tc.net[未注册用户]
    *.*.*.*
    链接

    tc.net[未注册用户] 2009-11-27 08:28:00

    楼主测试的数据样本不具有普遍性!

    为什么用n个相同的字符串,应该用随机字符串

  91. 老赵
    admin
    链接

    老赵 2009-11-27 08:35:00

    @tc.net
    区别在哪里?

  92. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-11-27 11:10:00

    Ivony...:

    幸存者:
    @RednaxelaFX
    这样虽然“读”的性能会提高,但是也会大大增加“写”的开销。
    比如对“融合”的字段重新赋值则需要调整内存布局,还可能需要重新分配一块内存然后再copy。
    我在想究竟是什么样的应用场景可能会需要“融合”这样的特性。




    所以融合应该是用于确保对“融合”的字段不可能重新赋值的场景,例如C#里面有个关键字叫readonly(Java似乎是final)。或者“融合”的字段的类型是封闭类型(C#关键字是sealed,Java的关键字还是final)。

    不过即使是封闭类型,如果重新赋值,还是存在将对象从融合的对象中拷贝出来,再把新对象拷贝进去的额外开销。

    也说不定CLR x.0就支持在readonly字段上加一个[Including]就支持融合。。。。。



    你要的融合,是不是就是fixed buffer那样的东东啊……

  93. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-11-27 11:24:00

    装配脑袋:

    Ivony...:

    幸存者:
    @RednaxelaFX
    这样虽然“读”的性能会提高,但是也会大大增加“写”的开销。
    比如对“融合”的字段重新赋值则需要调整内存布局,还可能需要重新分配一块内存然后再copy。
    我在想究竟是什么样的应用场景可能会需要“融合”这样的特性。




    所以融合应该是用于确保对“融合”的字段不可能重新赋值的场景,例如C#里面有个关键字叫readonly(Java似乎是final)。或者“融合”的字段的类型是封闭类型(C#关键字是sealed,Java的关键字还是final)。

    不过即使是封闭类型,如果重新赋值,还是存在将对象从融合的对象中拷贝出来,再把新对象拷贝进去的额外开销。

    也说不定CLR x.0就支持在readonly字段上加一个[Including]就支持融合。。。。。



    你要的融合,是不是就是fixed buffer那样的东东啊……




    fixed buffer不能像引用对象一样的引用啊。。。。还有,不是强类型的。。。。

    如果CLR直接支持“融合”也不错啊,扯远了,不知道这个“融合”和HotSpot的“融合”是不是一回事了。


    fixed buffer看起来不过是把byte b1; byte b2; byte b3; ... byte b128;简化了而已。。。。

  94. 小师傅
    *.*.*.*
    链接

    小师傅 2009-11-27 13:55:00

    下集呢,期待中。。。。。

  95. 阿K&LiveCai
    *.*.*.*
    链接

    阿K&LiveCai 2009-11-27 23:42:00

    深入,,

  96. ddsaxxxxx[未注册用户]
    *.*.*.*
    链接

    ddsaxxxxx[未注册用户] 2009-12-10 00:53:00

    [quoe]JimLiu:我想,String.Concat有可能是一个比较底层的方法,能够访问CLR的字符串驻留集,访问Raw Memory,所以性能好~~


    shi de kbb

  97. ddsaxxxxx[未注册用户]
    *.*.*.*
    链接

    ddsaxxxxx[未注册用户] 2009-12-10 01:01:00

    小师傅:下集呢,期待中。。。。。


    shi de

  98. tianxd
    *.*.*.*
    链接

    tianxd 2009-12-20 00:59:00

    为什么我测试StringBuilder要比String.Conact(应该是StringListBuilder吧)效率要高?

    StringListBuilder.Append(string s)也是需要内存复制的,而StringBuidler.Append(string s)也一样

  99. 老赵
    admin
    链接

    老赵 2009-12-20 01:04:00

    @tianxd
    使用Release测的吗?测试代码一样吗?

  100. tianxd
    *.*.*.*
    链接

    tianxd 2009-12-20 07:41:00

    Release刚才也测试了一把,代码是用你的~~~效果跟Debug差不多,肉眼看上就像你说的“势均力敌”,没有绘制图表。不过如果StringBuilder比StringListBuilder快的话,那到底快在那里呢,不能光和String.Conact比较,他之前还有List<String>.Append操作呢

  101. 老赵
    admin
    链接

    老赵 2009-12-20 15:27:00

    @tianxd
    这个正常推断一下应该就知道原因了吧,“中”里有一些分析。

  102. 听风吹雨
    *.*.*.*
    链接

    听风吹雨 2009-12-31 12:30:00

    特别崇拜通过行动来证明猜想的人,而且其中乐趣很大。

  103. hoodlum1980
    58.246.135.*
    链接

    hoodlum1980 2010-07-27 23:58:46

    List 已经是一个包装过的类,当然不会比直接的内存操作效率高了。换句话说,为了效率期间,StringBuilder内部对内存的操作和管理 没有必要再使用其他任何类来完成。

  104. ss
    180.168.33.*
    链接

    ss 2011-05-25 11:25:22

    CharListBuilder写的真是一般,当然性能差了

  105. 老赵
    admin
    链接

    老赵 2011-05-25 16:36:34

    @ss

    你觉得CharListBuilder该怎么写呢?

  106. 杨曹贵
    218.4.149.*
    链接

    杨曹贵 2011-10-14 17:43:07

    老赵!你生成的数据是用什么工具看的!

  107. 老赵
    admin
    链接

    老赵 2011-10-24 19:59:03

    @杨曹贵

    用眼睛看。

  108. 啊汉
    218.249.160.*
    链接

    啊汉 2012-02-03 15:59:39

    如果StringBuilder能相对准确的预分配内存,速度好像比string.concat要快一点

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我