所以一位大学教授刚刚告诉我,在C#中使用字符串串联(即使用加号运算符时)会造成内存碎片,我应该使用string.Format
。
现在,我在堆栈溢出中搜索了很多,发现有很多关于性能的线程,连接字符串可以轻而易举地获胜。(其中一些包括这个、这个和这个)
不过,我找不到一个谈论记忆碎片的人。我使用ILspy打开了.NET的string.Format
,显然它使用的与string.Concat
方法相同的字符串生成器(如果我理解的话,+
符号就是重载的)。事实上:它使用了string.Concat
中的代码!
我在2007年发现了这篇文章,但我怀疑它今天(或永远)是否准确。显然,编译器足够聪明,今天可以避免这种情况,因为我似乎无法重现这个问题。使用string.format和加号添加字符串最终在内部使用相同的代码。如前所述,字符串。格式使用相同的代码字符串。Concat使用。
所以现在我开始怀疑他的说法。这是真的吗?
所以大学里的一位教授刚刚告诉我,在C#中使用字符串串联(即当你使用加号运算符时)会产生内存碎片,我应该使用字符串。改为格式化。
不,您应该做的是进行用户研究,设置以用户为中心的真实世界性能指标,并根据这些指标衡量您的程序的性能当并且仅当您发现性能问题时,您应该使用适当的分析工具来确定性能问题的原因。如果原因是"内存碎片",则通过识别"碎片"的原因并尝试实验来确定哪些技术可以减轻这种影响。
性能不是通过像"避免字符串串联"这样的"提示和技巧"来实现的。性能是通过将工程学科应用于现实问题来实现的。
为了解决您更具体的问题:我从来没有听说过出于性能原因,为了格式化而避免串联的建议。通常给出的建议是避免迭代级联,而选择构建器。迭代串联在时间和空间上是二次的,并产生收集压力。构建器分配不必要的内存,但在典型场景中是线性的。两者都不会造成托管堆的碎片化;迭代级联往往会产生连续的垃圾块。
我遇到的性能问题归结为托管堆不必要的碎片的次数正好是一次;在Roslyn的早期版本中,我们有一种模式,我们会分配一个小的长期对象,然后是一个小短期对象,然后再是一个小型长期对象。。。连续几十万次,由此产生的最大碎片堆导致用户影响集合的性能问题;我们通过仔细测量相关场景中的性能来确定这一点,而不是通过在舒适的椅子上对代码进行特别分析。
通常的建议不是避免碎片化,而是避免压力。在Roslyn的设计过程中,我们发现,一旦我们前面提到的分配模式问题得到解决,压力对GC性能的影响远大于碎片化。
我给你的建议是,要么向你的教授施压,要求他做出解释,要么找一位对绩效指标有更严格方法的教授。
话虽如此,应该使用格式化而不是级联,但不是出于性能的原因。相反,为了代码的可读性、可本地化性和类似的风格问题。一个格式字符串可以被制成一个资源,它可以被本地化,等等
最后,我提醒您,如果您将字符串组合在一起,以便构建类似SQL查询或HTML块的内容来提供给用户,那么您需要使用这些技术中的none。这些字符串构建应用程序在出错时会对安全性产生严重影响。使用专门为构建这些对象而设计的库和工具,而不是使用字符串来滚动自己的库。
我运行了一个快速基准测试。我认为这证明了这一点:)
StringBuilder sb = new StringBuilder();
string st;
Stopwatch sw;
sw = Stopwatch.StartNew();
for (int i = 0 ; i < 100000 ; i++)
{
sb.Append("a");
}
st = sb.ToString();
sw.Stop();
Debug.WriteLine($"Elapsed: {sw.Elapsed}");
st = "";
sw = Stopwatch.StartNew();
for (int i = 0 ; i < 100000 ; i++)
{
st = st + "a";
}
sw.Stop();
Debug.WriteLine($"Elapsed: {sw.Elapsed}");
控制台输出:
运行时间:00:00:00.00011883(StringBuilder.Append())
运行时间:00:00:01.779839(+操作员)