A = string.Concat("abc","def")
B = "abc" + "def"
A vs. B
最近我很困惑为什么许多人会说肯定 A 比 B 处理速度快得多。但是,事实是他们只是这样说,因为有人这样说或者因为它就是这样。我想我可以从这里听到更好的解释。
编译器如何处理这些字符串?
谢谢!
A = string.Concat("abc","def")
B = "abc" + "def"
A vs. B
最近我很困惑为什么许多人会说肯定 A 比 B 处理速度快得多。但是,事实是他们只是这样说,因为有人这样说或者因为它就是这样。我想我可以从这里听到更好的解释。
编译器如何处理这些字符串?
谢谢!
我加入C#编译器团队时,第一件事就是重写字符串连接的优化器。好时光。
正如已经提到的那样,常量字符串的字符串拼接在编译时完成。非常量字符串做了一些花式处理:
a + b --> String.Concat(a, b)
a + b + c --> String.Concat(a, b, c)
a + b + c + d --> String.Concat(a, b, c, d)
a + b + c + d + e --> String.Concat(new String[] { a, b, c, d, e })
这些优化的好处是String.Concat方法可以查看所有参数,确定它们长度的总和,然后创建一个大字符串,可以容纳所有结果。s = M() + "";
如果M()返回null,那么结果就是空字符串。 (null + 空字符串 = 空字符串)。如果M不返回null,则空字符串连接不会改变结果。因此,这实际上被优化为根本不需要调用String.Concat!它变成了:s = M() ?? ""
很整洁,是吧?
B = "abcdef"
。+
操作实际上会更快。stringA + stringB
变成了String.Concat(stringA, stringB)
。"abc" + "def"
变成了"abcdef
"String.Concat("abc", "def")
保持不变"abc" + "def" + "ghi
实际上被转换为String.Concat(String.Concat("abc", "def"), "ghi")
。B
只会被直接设置为 "abcdef"。 - LukeH阅读此文:微观优化剧院的悲剧(编码恐怖)
该文章与IT技术有关,旨在说明代码的微小优化对效率提升的影响非常有限,反而会导致代码可读性降低,开发周期延长等问题。作者建议开发者应该先着眼于系统架构和整体性能调优,避免过度追求微观优化。StringWriter
是StringBuilder
的包装器。因此,在优化代码时,如果已经讨论了StringBuilder
,就没有必要在上下文中提到StringWriter
。 - BrianB
的字符串的连接将在编译时完成。 你的例子翻译成了:string a = string.Concat("abc", "def");
string b = "abcdef";
+
运算符转换为 Concat
调用。string x = GetStringFromSomewhere();
string y = GetAnotherString();
string a = string.Concat(x, y);
string b = x + y;
...在编译时被翻译成这个:
string x = GetStringFromSomewhere();
string y = GetAnotherString();
string a = string.Concat(x, y);
string b = string.Concat(x, y);
+
运算符的那个)转换为对Concat(第一个变体)的调用。B = "abc" + "def";
实际上完全不需要连接,就可以转换成这样:
B = "abcdef";
这是可能的,因为加法的结果可以在编译时计算,所以编译器会执行此操作。
然而,如果您使用类似于以下内容的东西:
A = String.Concat(stringVariable1, stringVariable2);
B = stringVariable1 + stringVariable2;
那么这两个将会生成相同的代码。
然而,我想确切地知道那些“许多人”说了什么,因为我认为它是不同的。
我认为他们说的是字符串拼接很糟糕,应该使用 StringBuilder 或类似的东西。
例如,如果你这样做:
String s = "test";
for (int index = 1; index <= 10000; index++)
s = s + "test";
然后发生的是,对于每次循环迭代,您将构建一个新字符串,并使旧字符串有资格进行垃圾回收。
此外,每个这样的新字符串都将复制旧字符串的所有内容,这意味着您将移动大量内存。
而以下代码:
StringBuilder sb = new StringBuilder("test");
for (int index = 1; index <= 10000; index++)
sb.Append("test");
相反地,我们将使用一个比实际需要更大的内部缓冲区,以防您需要将更多文本附加到其中。当该缓冲区变满时,将分配一个更大的新缓冲区,并将旧缓冲区留给垃圾回收。
因此,就内存使用和 CPU 使用而言,后一种变体要好得多。
除此之外,我会尽量避免过于关注“代码变体 X 是否比 Y 更好”,超出您已经有的经验范围。例如,现在我只是因为知道这个案例才使用 StringBuilder,但这并不意味着我编写的所有使用它的代码都实际上需要它。
在您知道自己存在瓶颈之前,请尽量避免花费时间进行微观优化代码。在那时,通常的建议是先测量,再进行裁剪。
B ="abcdef"
,而对于 A,连接操作会推迟到执行时刻。+
运算符将被转换为单个调用 string.Concat()
。 - Joey