本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作。
文章是哥(mephisto)写的,SourceLink
本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作。
文章是哥(mephisto)写的,SourceLink
自己对String和StringBuilder的处理机制略懂,大胆的设想下两者的性能对比会出现什么样的令人意外的情况。于是便有此文,结果我也不知道,那么我们就根据设计的测试用例来看看现象吧。本文耗时5个小时(包括测试用例),希望大家给点汗水的推荐。
由于要测试多个Framework下的性能,所以就创建了个解决方案,版本包括:2.0,3.0,3.5,4.0,4.5。这么多的工程,测试起来也比较麻烦,俺还是耐心的进行了测试。
IDE采用的VS2013,所以大家要下载后自己运行Demo,还是要注意下IDE版本,如是低版本,自己将4.5的移除即可。
1:TestUtil类
用于String和StringBuilder的创建的工具类
1.1:CreateString()
创建String方法
1 public string CreateString(int num, string value) 2 { 3 string temp = string.Empty; 4 while (num > 0) 5 { 6 temp += value; 7 num--; 8 } 9 10 return temp; 11 }1.2:CreateStringBuilder()
创建StringBuilder方法
1 public string CreateStringBuilder(int num, string value) 2 { 3 StringBuilder sb = new StringBuilder(); 4 while (num > 0) 5 { 6 sb.Append(value); 7 num--; 8 } 9 10 return sb.ToString(); 11 }2:Test类
2.1:RunStringMinInF2()
运行String最小次数测试
1 public static void RunStringMinInF2(TestUtil testUtil, string word) 2 { 3 DateTime date1 = DateTime.Now; 4 5 int i = runCount * 5; 6 while (i > 0) 7 { 8 testUtil.CreateString(2, word); 9 i--; 10 } 11 DateTime date2 = DateTime.Now; 12 Console.WriteLine("stringMin:" + (date2 - date1).TotalMilliseconds.ToString()); 13 }2.2:RunSBMinInF2()
运行StringBuilder最小次数测试
1 public static void RunSBMinInF2(TestUtil testUtil, string word) 2 { 3 DateTime date1 = DateTime.Now; 4 5 int i = runCount * 5; 6 while (i > 0) 7 { 8 testUtil.CreateString(2, word); 9 i--; 10 } 11 DateTime date2 = DateTime.Now; 12 Console.WriteLine("stringbuilderMin:" + (date2 - date1).TotalMilliseconds.ToString()); 13 }2.3:RunString10InF2()
运行单个String实体调用10次相加测试
1 public static void RunString10InF2(TestUtil testUtil, string word) 2 { 3 DateTime date1 = DateTime.Now; 4 5 int i = runCount; 6 while (i > 0) 7 { 8 testUtil.CreateString(10, word); 9 i--; 10 } 11 DateTime date2 = DateTime.Now; 12 Console.WriteLine("stringMin:" + (date2 - date1).TotalMilliseconds.ToString()); 13 }2.4:RunSB10InF2()
运行单个StringBuilder实体调用10次相加测试
1 public static void RunSB10InF2(TestUtil testUtil, string word) 2 { 3 DateTime date1 = DateTime.Now; 4 5 int i = runCount; 6 while (i > 0) 7 { 8 testUtil.CreateString(10, word); 9 i--; 10 } 11 DateTime date2 = DateTime.Now; 12 Console.WriteLine("stringbuilderMin:" + (date2 - date1).TotalMilliseconds.ToString()); 13 }
测试字符串:“a”
单个实例测试数据次数:2
被调用总次数:10000000
测试代码如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TestUtil testUtil = new TestUtil(); 6 7 Test.RunStringMinInF2(testUtil, "a"); 8 Test.RunSBMinInF2(testUtil, "a"); 9 Test.RunSBMinInF2(testUtil, "a"); 10 Test.RunStringMinInF2(testUtil, "a"); 11 12 Console.Read(); 13 } 14 }
防止由于先后调用原因,所以测试代码中同一个方法出现2次。多次运行,基本上结果差不多,同一次运行中也有数据相差的,但差别很小(几十ms)。
运行截图如下:
测试字符串:“abcdefghijklmn1234567890opqrst~!@#^&*()”
其他测试步骤:同测试方案一
测试代码如下:
1 static void Main(string[] args) 2 { 3 TestUtil testUtil = new TestUtil(); 4 5 Test.RunStringMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()"); 6 Test.RunSBMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()"); 7 Test.RunSBMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()"); 8 Test.RunStringMinInF2(testUtil, "abcdefghijklmn1234567890opqrst~!@#^&*()"); 9 10 Console.Read(); 11 }
运行截图如下:
结果很惊奇的发现第2个String比StringBuilder还快点,多次运行,基本结果都是最后一个要快点,为什么列,大家思考下。
其他测试步骤:同测试方案一
测试结果如下:
发现在4.0和4.5的版本中StringBuilder是比原来的版本有一定优势的,4.0优势很小,4.5就有很比较大的优势,4.5的String处理也优势很大。看来微软也是在进步啊,大家是不是很期待开源了的Framework,很多牛人可以对其进行贡献,那性能不是又进步了。
四:在多Framework下最少字段数相加(字符串增加)
其他测试步骤:同测试方案二
测试结果如下:
几个Framework版本结果差不多,只不过4.5的StringBuilder比String要快那么一点点。
测试字符串:“a”
单个实例测试数据次数:10
被调用总次数:10000000
测试代码如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 TestUtil testUtil = new TestUtil(); 6 7 Test.RunString10InF2(testUtil, "a"); 8 Test.RunSB10InF2(testUtil, "a"); 9 Test.RunSB10InF2(testUtil, "a"); 10 Test.RunString10InF2(testUtil, "a"); 11 12 Console.Read(); 13 } 14 }
测试结果如下:
注:由于疏忽,方法体内的输出忘记改前缀了,所以还是stringMin,stringbuilderMin,见谅。
对单个实例调用次数增加,String比StringBuilder略微快点。因为String实例化要比StringBuilder快点。
六:在Framework2.0下10字段数相加(字符串增加)
测试字符串:“abcdefghijklmn1234567890opqrst~!@#^&*()”
其他测试步骤:同测试方案五
测试结果如下:
这个测试很出乎意料啊,跟测试方案二,五比起来,性能差的是好几倍啊。大家可以思考下这个问题。
七:在多Framework下10字段数相加
其他测试步骤:同测试方案五
测试结果如下:
在众多Framework中,4.5和4.0拖妥妥的排在前2位。
八:在多Framework下10字段数相加(字符串增加)
其他测试步骤:同测试方案六
测试结果如下:
是不是让大家大跌眼睛的结果,4.0的速度最快,在前面领先的4.5基本最慢,这是为什么列。
虽然 StringBuilder 和 String 两个表示字符序列,但它们以不同的方式实现。 String 是不可变的类型。 can expand the current instance." data-guid="021c68f4daffc47aa86df5f9eea631a8">即看似修改 String 对象的每个操作实际创建新的字符串。
has a <span class="mtpsTagOuterHtml" >String parameter or display it in the user interface." data-guid="cdc60c9b46566489be610ab895f2064f"> 对执行广泛的字符串操作实例 (如修改循环中的字符串) ,修改字符串能重复精确严重的性能损失。 方法是使用 StringBuilder,它是易失的字符串选件类。 operation always creates a new object from the existing string and the new data." data-guid="6197e930d7f530adb696df935b8f3bd8">可变性意味着,一旦选件类的实例创建的,则可以将其追加,取消,替换或插入字符修改。 StringBuilder 对象维护缓冲区容纳扩展到该字符串。 original buffer is copied to the new buffer, and the new data is then appended to the new buffer." data-guid="4ccd8a27fb3e64d4d28c9739ef33dbf6">如果有足够的空间,新数据将被追加到缓冲区;否则,将分配一个新的、更大的缓冲区,原始缓冲区中的数据被复制到新的缓冲区,然后将新数据追加到新的缓冲区。
虽然 StringBuilder 选件类比 String 选件类通常提供更好的性能,当不应使用 StringBuilder 自动替换 String 时,就要操作字符串。 性能取决于该字符串的大小,对新的字符串将分配的内存量,您的系统 app 的执行和操作的类型。 您应准备测试您的应用程序确定 StringBuilder 实际上是否可显着提高性能。
在这些条件下考虑使用 String 选件类:
当您的应用程序将对字符串更改的数量很小。 在这些情况下,StringBuilder 不可能提供在 String的忽略或性能改进。
当执行串联运算的内置的数字,尤其是对于字符串文本。 在这种情况下,编译器可能将串联运算到单个操作。
当您生成字符串时,当您必须执行广泛的搜索操作。 StringBuilder 选件类没有搜索方法 ,如 IndexOf 或 StartsWith。 您必须转换为 String 的 StringBuilder 对象这些操作的,这样,可以对从使用 StringBuilder的性能。 many of the methods defined by the <span class="selflink">StringBuilder class." data-guid="d490728da5b824b0bd10b9938f104995">有关详细信息,请参阅 搜索在 StringBuilder 对象的文本 部分。
在这些条件下考虑使用 StringBuilder 选件类:
当您希望您的应用程序创建一个未知的设置为字符串的更改在设计时 (例如,当您使用循环连接包含用户输入的随机数字符串)。
当您希望您的应用程序创建一个大量为字符串的更改。
StringBuilder 对象的默认值容量为 16 个字符。
虽然String是引用类型,但是MS处理的时候,比如“+=”的运算符处理String的时候,是重新申请一块托管堆,用来存储处理后的新String,这样新String的HashCode(地址)也会随着变化,毕竟是一个新的地址引用,所以表面上很像是个“值类型”。
因此,MS为了处理Sring带来的潜在性能问题,就加入了StringBuilder,当然我们这里自只用到了StringBuilder很少一部分功能(字符串相加),StringBuilder还有很多其他性能很好的功能(这里不表)。个人StringBuilder肯定比String复杂,初始化应该是要慢一点,但是我们在上面测试中的用例1,3,5,7告诉我们,两者实例化差不多快。但在处理某个字符串比较长的相加的时候,性能就会比String差那么一点点,我的理解是:StringBulider的对面默认值容量是16个字符(Framework4.5),所以当处理的字符串大于16的时候,就会重新申请开辟另一块当前StringBuilder字符长度,(每当追加操作导致 StringBuilder 对象的长度超过其容量,其现有的容量翻倍),一个很极端情况就是用两个个很长很长的字符串,这样StringBuilder就会很多次得去申请容量,这是占一定的性能损耗的,所以就有可能比String会慢,这是可以理解的,毕竟设计一个类是有使用场景的,而已StringBuilder也提供了修改默认容量的方法,只是我们在使用的时候没有根据现实场景对这个进行设置。所以极小的字符串相加处理,就可以直接用String来用,但是如果非常频繁的处理,比如webApi被调用,那么是不是考虑用StringBuilder来,并且,通过测试来预估StringBuilder的默认容量范围,这样来达到性能的极致。
在不同的Framework中,2.0比1.0是有个革命性的变化,比如泛型等等。3.0和3.5加了很多功能,比如WPF,WCF,WFF,Linq等,但他们只是在2.0的基础上做了扩展,丰富,实际底层改变不大,大家从3.0和3.5的安装版就可以看出,他们都是100多M,都是通过打补丁的方式来弄的,所以安装包很大。到了4.0,Framework发生的大变化,个人觉得最实质的变化就是引入了System.Core。并且把所有的都重新实现了一遍,所以安装版只有40多M,加了这么多东西,从28->40很是可以的。4.5没有研究,但从4.5引用的还是4.0的System.Core,就应该是底层变化不大,如果大家有知道,了解的,希望告诉下我们。所以上面结果中,有时候4.0,4.5领先很多,在有的时候落后很多,也没什么的,估计只是策略变了而已。
Framework也开源了,大家都可以去关注下,我也会抽时间去研究研究,看什么时候能在将来的Framework中看到自己提交的代码,那是多么愉悦的事情啊,多谢了github这个分布式版本管理,我们就可以修改提交了,审核过不过无所谓,毕竟提交过,哈哈。如果有一天能审核过,那可以面试的时候或者跟猿们聊天的时候,多BB啊。
一写就到了23:30了,明天还要上班,收笔把,还准备把反射出的不同版本的String和StringBuilder的代码贴出来的,打住了,身体也很重要。
源码下载
本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作。
文章是哥(mephisto)写的,SourceLink