浅谈C# StringBuilder内存碎片对性能的影响

 更新时间:2020年03月22日 11:13:04   作者:.NET操作  
这篇文章主要介绍了浅谈StringBuilder内存碎片对性能的影响,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

StringBuilder内部是由多段char[]组成的半自动链表,因此频繁从中间修改StringBuilder,会将原本连续的内存分隔为多段,从而影响读取/遍历性能。

连续内存与不连续内存的性能差,可能高达1600倍。

背景

用StringBuilder的用户可能大都想用StringBuilder拼接html/json模板、组装动态SQL等正常操作。但在一些特殊场景中——如为某种编程语言写语言服务,或者写一个富文本编辑器时,StringBuilder依然也有用武之地,通过里面的Insert/Remove两个方法来修改。

测试方法

Talk is cheap, show me the code:

int docLength = 10000;
void Main()
{
  (from power in Enumerable.Range (1, 16)
  let mutations = (int) Math.Pow (2, power)
  select new
  {
    mutations,
    PerformanceRatio = Math.Round (GetPerformanceRatio (docLength, mutations), 1)
  }).Dump();
}

float GetPerformanceRatio (int docLength, int mutations)
{
  var sb = new StringBuilder ("".PadRight (docLength));
  var before = GetPerformance (sb);
  FragmentStringBuilder (sb, mutations);
  var after = GetPerformance (sb);
  return (float) after.Ticks / before.Ticks;
}

void FragmentStringBuilder (StringBuilder sb, int mutations)
{
  var r = new Random(42);
  for (int i = 0; i < mutations; i++)
  {
    sb.Insert (r.Next (sb.Length), 'x');
    sb.Remove (r.Next (sb.Length), 1);
  }
}

TimeSpan GetPerformance (StringBuilder sb)
{
  var sw = Stopwatch.StartNew();
  long tot = 0;
  for (int i = 0; i < sb.Length; i++)
  {
    char c = sb[i];
    tot += (int) c;
  }
  sw.Stop();
  return sw.Elapsed;
}

关于这段代码,请注意以下几点:

  • 通过.PadRight(n)来直接创建长度为n的空白字符串,可以用new string(' ', n)来代替;
  • new Random(42)处,我指定了一个随机因子,确保每次分隔后分隔的位置完全相同,有利于做对照组;
  • 我分别对字符串进行了2^1 ~ 2^16次修改,分别比较经过这么多次修改之后的性能差异;
  • 我使用sb[i]来逐一访问StringBuilder中的位置,使内存不连续性更加突显。

运行结果

mutationsPerformanceRatio
21
41
81
161
321
641.1
1281.2
2561.8
5125.2
102419.9
204881.3
4096274.5
8192745.8
163841578.8
327681630.4
65536930.8

可见如果在StringBuilder中间进行大量修改,其性能会急据下降,注意看32768次修改的情况下,遍历时会产生高达1630.4倍的性能差!

解决方式

如果一定要用StringBuilder,可以考虑在修改一定次数后,重新创建一个新的StringBuilder,以使得访问时获得最佳的内存连续性,即可解决此问题:

void FragmentStringBuilder (StringBuilder sb, int mutations)
{
  var r = new Random(42);
  for (int i = 0; i < mutations; i++)
  {
    sb.Insert (r.Next (sb.Length), 'x');
    sb.Remove (r.Next (sb.Length), 1);
    
    // 重点
    const int defragmentCount = 250;
    if (i % defragmentCount == defragmentCount - 1)
    {
      string buf = sb.ToString();
      sb.Clear();
      sb.Append(buf);
    }
  }
}

如上,每经过250次修改,即将原StringBuilder删除,然后重新创建一个新的StringBuilder,此时运行效果如下:

mutationsPerformanceRatio
21.2
40.7
81
161
321
641.1
1281.2
2561
5121
10241
20481
40961.1
81921.5
163841.3
327681
655361

可见,在几乎所有情况下,受内存不连续造成的访问性能问题,解决——同时250可能是一个相对比较合理的数字,在插入性能与查询/遍历性能中,获得平衡。

反思与总结

众所周知,由于string的不可变性,拼接大量字符串时,会浪费大量内存。但使用StringBuilder也需要了解它的结构。

StringBuilder这样做成链式的结构并非没有原因,如果考虑插入性能,做成链式接口是最优秀的。但如果考虑查询性能,链式结构就非常不利了,如果设计为非链式结构,从中间插入时,StringBuilder的内存空间可能不够,因此需要重新分配内存,这样相当于将StringBuilder降格为string,因此完全丧失了StringBuilder适合做“频繁插入”的优势。

本文说的其实是一个非常特殊的例子,现实中除了语言服务、编辑器外,很少会需要这种即要频繁插入快,也要频繁修改快的场景。如果想简单点搞,用StringBuilder会是一个有条件合适的解决方案。更适合的解决方案当然是专门的数据结构——PieceTable,微软在VSCode编辑器中,为了确保大文件编辑性能,使用了该数据结构,取得了非常不错的成果,参考链接:Text Buffer Reimplementation

到此这篇关于浅谈StringBuilder内存碎片对性能的影响的文章就介绍到这了,更多相关StringBuilder 内存碎片内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C# winform实现右下角弹出窗口结果的方法

    C# winform实现右下角弹出窗口结果的方法

    这篇文章主要介绍了C# winform实现右下角弹出窗口结果的方法,结合实例形式分析了C#窗口操作的相关技巧,需要的朋友可以参考下
    2017-06-06
  • Unity 制作一个分数统计系统

    Unity 制作一个分数统计系统

    项目中经常遇到分数统计的需求,例如操作正确则计分,相反则不计分失去该项分数,为了应对需求需要一个分数统计系统。本文主要介绍了通过Unity实现这样的一个计分系统,快来跟随小编一起学习吧
    2021-12-12
  • C#中使用FilleStream实现视频文件的复制功能

    C#中使用FilleStream实现视频文件的复制功能

    这篇文章主要介绍了C#中使用FilleStream实现视频文件的复制功能,本文通过实例代码给大家介绍的非常想详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • WPF实现html中的table控件的示例代码

    WPF实现html中的table控件的示例代码

    相信很多做WPF开发的小伙伴都遇到过表格类的需求,虽然现有的Grid控件也能实现,但是使用起来的体验感并不好,所以本文我们就来用WPF自己实现一个html中的table控件吧
    2024-03-03
  • C#异步执行任务的方法

    C#异步执行任务的方法

    这篇文章主要介绍了C#异步执行任务的方法,以一个简单实例形式分析了C#异步执行的实现技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • C#使用linq计算执行元素在列表中出现次数的方法

    C#使用linq计算执行元素在列表中出现次数的方法

    这篇文章主要介绍了C#使用linq计算执行元素在列表中出现次数的方法,涉及C#使用linq扩展进行列表查询的技巧,需要的朋友可以参考下
    2015-04-04
  • C#实现String字符串转化为SQL语句中的In后接的参数详解

    C#实现String字符串转化为SQL语句中的In后接的参数详解

    在本篇文章中小编给大家分享的是一篇关于C#实现String字符串转化为SQL语句中的In后接的实例内容和代码,需要的朋友们参考下。
    2020-01-01
  • C# Unity使用正则表达式去除部分富文本的代码示例

    C# Unity使用正则表达式去除部分富文本的代码示例

    正则表达式在我们日常开发中的用处不用多说了吧,下面这篇文章主要给大家介绍了关于C# Unity使用正则表达式去除部分富文本的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • 利用C#编写扫雷游戏(附源码)

    利用C#编写扫雷游戏(附源码)

    扫雷游戏相信不用给大家过多介绍,大家基本都玩过,下面这篇文章主要给大家介绍了关于如何利用C#编写扫雷游戏的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2019-01-01
  • C#文件操作类分享

    C#文件操作类分享

    这篇文章主要为大家分享了C#文件操作类的相关代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-06-06

最新评论