StringBuilder内存碎片对性能的影响

TL;DR:

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

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

背景

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

测试方法

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;
}

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

  1. 通过 .PadRight(n)来直接创建长度为 n的空白字符串,可以用 newstring(' ',n)来代替;

  2. newRandom(42)处,我指定了一个随机因子42,确保每次分隔后分隔的位置完全相同,有利于做对照组;

  3. 我分别对字符串进行了 2^1~2^16次修改,分别比较经过这么多次修改之后的性能差异;

  4. 我使用 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 - https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation。

喜欢的朋友请关注我的微信公众号:【DotNet骚操作】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 双击_利用java开发一个双击执行的小程序

之前我们利用java写了很多东西&#xff0c;但是好像都没有什么实际意义。因为有意义桌面小程序怎么都得有个界面&#xff0c;可是界面又不太好搞。或者 了解到这一层的人就少之又少了。呀&#xff0c;是不是还得开辟一些版面来介绍awt和 swing。。。算了 先把这个 双击执行的小…

开发人员如何学习 Kubernetes

虽然“容器编排平台”还没有被整个行业大范围采用&#xff0c;但在这一领域 Kubernetes 已经战胜其他选手&#xff0c;成为了事实标准。近两年的 Web 开发技术社区&#xff0c;随便打开一两个群&#xff0c;你都能看到人们在谈 Kubernetes。很多开发人员&#xff0c;包括曾经的…

安装 java decompiler_Eclipse离线安装Java Decompiler插件(反编译)

Java Decompiler是Java语言的反编译工具&#xff0c;具体介绍见博客Java Decompiler(Java反编译工具)1、下载插件Eclipe的Java Decompiler插件名为JD-Eclipse&#xff0c;2、安装插件Ecipse安装JD-Eclipse(即Java Decompiler)插件步骤如下&#xff1a;打开Help --> Install …

给 ABP vNext 应用安装私信模块

在上一节五分钟完成 ABP vNext 通讯录 App 开发 中&#xff0c;我们用完成了通讯录 App 的基础开发。这本章节&#xff0c;我们会给通讯录 App 安装私信模块&#xff0c;使不同用户能够通过相互发送消息&#xff0c;并接收新私信的通知。在章节的最后&#xff0c;笔者将演示模块…

《C++ Primer》7.5.2节练习

练习7.41: #include <iostream> #include <string> using namespace std;class Sales_data {friend std::istream &read(std::istream &is, Sales_data &item);friend std::ostream &print(std::ostream &os, const Sales_data &item);pu…

零基础玩视频号?创作运营变现,你要的干货都在这了!

点击蓝字“大白技术控”关注我哟加个“星标★”&#xff0c;每日良时&#xff0c;好文必达&#xff01;不少小伙伴应该已经听说过视频号这个新功能了&#xff0c;视频号是微信内测的短视频功能&#xff0c;本人已经在视频号里刷了2个月了。3月中旬正式开通了视频号 「大白技术控…

Asp.Net Core 中IdentityServer4 实战之 Claim详解

一、前言由于疫情原因&#xff0c;让我开始了以博客的方式来学习和分享技术&#xff08;持续分享的过程也是自己学习成长的过程&#xff09;&#xff0c;同时也让更多的初学者学习到相关知识&#xff0c;如果我的文章中有分析不到位的地方&#xff0c;还请大家多多指教&#xf…

程序员还有35岁的坎吗?

昨天晚上和多年未见的前同事聊天&#xff0c;提到了程序员的年龄歧视问题&#xff1a;自己年龄也 30 出头了&#xff0c;在思考 IT 届流传的 35 岁是一个坎的问题&#xff1b;开始注重提升管理能力&#xff0c;担心35岁之后&#xff0c;一线写代码的岗位不能胜任&#xff1b;公…

java 左移 返回值_java左移右移运算符详解

在阅读源码的过程中&#xff0c;经常会看到这些符号<< &#xff0c;>>&#xff0c;>>>&#xff0c;这些符号在Java中叫移位运算符&#xff0c;在写代码的过程中&#xff0c;虽然我们基本上不会去写这些符号&#xff0c;但需要明白这些符号的运算原理&…

人与人的差距在于认知

作者介绍findyi&#xff0c;腾讯、360码农&#xff0c;前哒哒少儿英语技术VP&#xff0c;现任土豆教育CTO。工作和生活中不光要埋头干活&#xff0c;还要抬头看天。思考总结方法论是提升认知的必备途径&#xff0c;是将碎片化知识总结为动态的智慧的过程。认知有多重要&#xf…

.NET5来了你别慌

近日微软.Net大咖Scott在博客中对外宣传.NET5首个预览版&#xff0c;并且我们可以通过微软的官网下载SDK5和运行库。很多朋友感觉.NetCore3.1还没搞明白&#xff0c;.NET5就来了感觉一下子慌了神。在这里我提醒朋友们&#xff0c;瞬息万变的世界中&#xff0c;总有相对不变的真…

java8 stream 最大值_JDK8-Stream流常用方法

Stream流的使用流操作是Java8提供一个重要新特性&#xff0c;它允许开发人员以声明性方式处理集合&#xff0c;其核心类库主要改进了对集合类的 API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中&#xff0c;能 让代码更加简…

周三晚6点半!盛派首席架构师“苏老师”在线解密内部系统框架!

工作中有些事&#xff0c;看起来只用一会会儿就能完成&#xff0c;但真正完成起来&#xff0c;总会遇到一些意想不到的困难&#xff01;你一定碰到过这样的情况——开发时间 2 周的项目&#xff0c;搭框架就要用 1 周&#xff0c;刚开发完&#xff0c;各种调试和修 bug又花去 2…

给微软的日志框架写一个基于委托的日志提供者

动手造轮子&#xff1a;给微软的日志框架写一个基于委托的日志提供者Intro微软的日志框架现在已经比较通用&#xff0c;有时候我们不想使用外部的日志提供者&#xff0c;但又希望提供一个比较简单的委托就可以实现日志记录&#xff0c;于是就有了后面的探索和实现。Solution基于…

C++分析使用拷贝控制成员和调用构造函数的时机

我们来分析下面这段代码&#xff1a; #include <iostream> #include <vector>using namespace std;struct X {X() {cout << "构造函数X()" << endl;}X(const X &) {cout << "拷贝构造函数X(const X&)" << en…

《C++ Primer》13.1.4节练习

练习13.14: 这是一个典型的应该定义拷贝控制成员的场合。如果不定义拷贝构造函数和拷贝赋值运算符&#xff0c;依赖合成的版本&#xff0c;则在拷贝构造和赋值时&#xff0c;会简单复制数据成员。对本问题来说&#xff0c;就是将序号简单复制给新对象。 因此&#xff0c;代码中…

十问十答 CDDL 许可证

今天我们来整理一下通用开发和发行许可证 CDDL 的十大问题清单。通用开发与发行许可证&#xff08;Common Development and Distribution License&#xff0c;CDDL&#xff09;由已被甲骨文公司收购的太阳微系统公司&#xff08;Sun Microsystems&#xff09;发布的一种开源许可…

Http Server API路由请求到web程序

引言接上文&#xff0c;容器内web程序一般会绑定到http://0.0.0.0:{某监听端口}或http://:{某监听端口}&#xff0c;以确保使用容器IP可以访问到web应用。正如我们在ASP.NET Core官方镜像显示的&#xff0c;ASP.NET Core程序在容器内80端口监听请求This image sets the ASPNETC…

《C++ Primer》13.1.6节练习(部分)

练习13.18: #include <iostream> #include <string> using namespace std;class Employee {private:static int sn;public:Employee() {mysn sn;}Employee(const string &s) {name s;mysn sn;}const string &get_name() {return name;}int get_mysn() …

用Azure Custom Vision 零代码创建一个口罩识别模型

新冠肺炎下&#xff0c;地球是一家&#xff0c;不分国籍&#xff0c;不分种族&#xff0c;或者现在只能呆在家中&#xff0c;但是也是一种对抗疫的支持。停课不停学留在家中&#xff0c;不仅是对学生&#xff0c;对于所有人都是有用的。在现阶段&#xff0c;大家可能最需要的不…