连接或添加字符串但不与字符串连接时出现内存碎片.总体安排



所以一位大学教授刚刚告诉我,在C#中使用字符串串联(即使用加号运算符时)会造成内存碎片,我应该使用string.Format

现在,我在堆栈溢出中搜索了很多,发现有很多关于性能的线程,连接字符串可以轻而易举地获胜。(其中一些包括这个、这个和这个)

不过,我找不到一个谈论记忆碎片的人。我使用ILspy打开了.NET的string.Format,显然它使用的string.Concat方法相同的字符串生成器(如果我理解的话,+符号就是重载的)。事实上:它使用了string.Concat中的代码!

我在2007年发现了这篇文章,但我怀疑它今天(或永远)是否准确。显然,编译器足够聪明,今天可以避免这种情况,因为我似乎无法重现这个问题。使用string.format和加号添加字符串最终在内部使用相同的代码。如前所述,字符串。格式使用相同的代码字符串。Concat使用。

所以现在我开始怀疑他的说法。这是真的吗?

所以大学里的一位教授刚刚告诉我,在C#中使用字符串串联(即当你使用加号运算符时)会产生内存碎片,我应该使用字符串。改为格式化。

不,您应该做的是进行用户研究,设置以用户为中心的真实世界性能指标,并根据这些指标衡量您的程序的性能当并且仅当您发现性能问题时,您应该使用适当的分析工具来确定性能问题的原因。如果原因是"内存碎片",则通过识别"碎片"的原因并尝试实验来确定哪些技术可以减轻这种影响。

性能不是通过像"避免字符串串联"这样的"提示和技巧"来实现的。性能是通过将工程学科应用于现实问题来实现的。

为了解决您更具体的问题:我从来没有听说过出于性能原因,为了格式化而避免串联的建议。通常给出的建议是避免迭代级联,而选择构建器。迭代串联在时间和空间上是二次的,并产生收集压力。构建器分配不必要的内存,但在典型场景中是线性的。两者都不会造成托管堆的碎片化;迭代级联往往会产生连续的垃圾块。

我遇到的性能问题归结为托管堆不必要的碎片的次数正好是一次;在Roslyn的早期版本中,我们有一种模式,我们会分配一个小的长期对象,然后是一个小短期对象,然后再是一个小型长期对象。。。连续几十万次,由此产生的最大碎片堆导致用户影响集合的性能问题;我们通过仔细测量相关场景中的性能来确定这一点,而不是通过在舒适的椅子上对代码进行特别分析。

通常的建议不是避免碎片化,而是避免压力。在Roslyn的设计过程中,我们发现,一旦我们前面提到的分配模式问题得到解决,压力对GC性能的影响远大于碎片化。

我给你的建议是,要么向你的教授施压,要求他做出解释,要么找一位对绩效指标有更严格方法的教授。

话虽如此,应该使用格式化而不是级联,但不是出于性能的原因。相反,为了代码的可读性、可本地化性和类似的风格问题。一个格式字符串可以被制成一个资源,它可以被本地化,等等

最后,我提醒您,如果您将字符串组合在一起,以便构建类似SQL查询或HTML块的内容来提供给用户,那么您需要使用这些技术中的none。这些字符串构建应用程序在出错时会对安全性产生严重影响。使用专门为构建这些对象而设计的库和工具,而不是使用字符串来滚动自己的库。

字符串串联的问题在于字符串是不可变的。string1+string2不会将string2连接到string1上,而是创建一个全新的字符串。使用StringBuilder(或string.Format)不会出现此问题。在内部,StringBuilder持有一个char[],它会对其进行过度分配。向StringBuilder追加某些内容不会创建任何新对象,除非它在char[]中的空间不足(在这种情况下,它会过度分配一个新对象)。

我运行了一个快速基准测试。我认为这证明了这一点:)

        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(+操作员)

相关内容

  • 没有找到相关文章

最新更新