gpt4 book ai didi

C#:为什么 .ToString() 将文本更快地附加到转换为字符串的 int 中?

转载 作者:太空狗 更新时间:2023-10-29 19:54:55 27 4
gpt4 key购买 nike

这是来自 C# 简而言之

StringBuilder sb = new StringBuilder();
for(int i = 0; i < 50; i++)
sb.Append (i + ",");

//Outputs 0,1,2,3.............49,

然而,它接着说“表达式 i + ”,意味着我们仍然反复连接字符串,但是这只会导致很小的性能成本,因为字符串很小”

然后它说将其更改为下面的行使其更快
for(int i = 0; i < 50; i++) {
sb.Append(i.ToString());
sb.Append(",");
}

但是为什么会更快呢?现在我们有一个额外的步骤,其中 i正在转换为字符串?这里到底发生了什么?在本章的其余部分没有更多的解释。

最佳答案

您问题的前两个答案不太正确。 sb.Append(i + ",");声明不调用i.ToString() ,它实际上做的是

StringBuilder.Append(string.Concat((object)i, (object)","));

内部在 string.Concat函数,它调用 ToString()上二 object s 传入。此语句中的关键性能问题是 (object)i .这是装箱 - 将值类型包装在引用中。这是一个(相对)相当大的性能损失,因为它需要额外的周期和内存分配来装箱,然后需要额外的垃圾收集。

您可以在 (Release) 编译代码的 IL 中看到这种情况:
IL_000c:  box        [mscorlib]System.Int32
IL_0011: ldstr ","
IL_0016: call string [mscorlib]System.String::Concat(object,
object)
IL_001b: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)

看到第一行是 box调用,然后是 Concat通话,最后以通话结束 Append .

如果您拨打 i.ToString()相反,如下所示,你放弃了拳击,还有 string.Concat()称呼。
for (int i = 0; i < 50; i++)
{
sb.Append(i.ToString());
sb.Append(",");
}

此调用产生以下 IL:
IL_000b:  ldloca.s   i
IL_000d: call instance string [mscorlib]System.Int32::ToString()
IL_0012: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)
IL_0017: pop
IL_0018: ldloc.0
IL_0019: ldstr ","
IL_001e: callvirt instance class [mscorlib]System.Text.StringBuilder
[mscorlib]System.Text.StringBuilder::Append(string)

请注意,这里没有拳击,也没有 String.Concat ,因此需要收集的资源更少,浪费在拳击上的周期更少,代价是增加一个 Append()打电话,相对​​来说便宜很多。

这就是为什么第二组代码性能更好的原因。

您可以将此想法扩展到许多其他事情 - 任何对字符串进行操作的地方,您将值类型传递给未明确将该类型作为参数的函数(将 object 作为参数的调用,例如 string.Format() 例如),最好拨打 <valuetype>.ToString()传入值类型参数时。

回应 Theodoros 在评论中的问题:

编译器团队当然可以决定进行这样的优化,但我的猜测是他们认为成本(在额外的复杂性、时间、额外的测试等方面)使得这种更改的值(value)不值得投资。

基本上,他们将不得不为表面上在 string 上运行的函数添加特殊情况分支。 s,但提供了 object 的过载在其中(基本上, if (boxing occurs && overload has string) )。在该分支内,编译器还必须检查以验证 object函数重载的作用与 string 相同除调用 ToString() 外的重载在参数上 - 它需要这样做,因为用户可以创建函数重载,其中一个函数采用 string另一个需要 object ,但是这两个重载对参数执行不同的工作。

在我看来,这就像对一些字符串操作函数进行小幅优化的复杂性和分析。此外,这将与核心编译器函数解析代码混淆,该代码已经有一些人们一直误解的非常精确的规则(看看 Eric Lippert 的一些答案 - 很多都围绕函数解析问题)。如果返回最小,则使用“它像这样工作,除非您遇到这种情况”类型规则使其更加复杂,这当然是应该避免的。

更便宜和更简单的解决方案是使用基本函数解析规则,让编译器解析您传入的值类型(如 int )到函数中,并让它找出唯一适合的函数签名这是一个需要 object ,并做一个盒子。然后靠用户做 ToString()的优化当他们分析他们的代码并确定有必要时(或者只是知道这种行为并在他们遇到这种情况时一直这样做,我就是这样做的)。

他们本可以做的一个更可能的选择是拥有多个 string.Concat采取 int 的重载s, double s 等(如 string.Concat(int, int) ),只需拨打 ToString关于内部的论据,他们不会被装箱。这样做的优点是优化是在类库中而不是编译器中,但随后您不可避免地会遇到想要在串联中混合类型的情况,就像这里的原始问题 string.Concat(int, string) .排列会爆炸,这可能是他们没有这样做的原因。他们还可以确定将使用此类重载的最常用情况并执行前 5 名,但我猜他们决定将它们向问“好吧,你做了 (int, string),为什么不”的人开放你做 (string, int) 吗?”。

关于C#:为什么 .ToString() 将文本更快地附加到转换为字符串的 int 中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18293932/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com