返回字符串时的编译器优化

5

如果我拥有

private string Foo(string decrypted)
{
    return decrypted.Substring(blah);
}

并且

private string Foo(string decrypted)
{
    string s = decrypted.Substring(blah);
    return s;
}

这是否相同?编译器能否删除s

那么

private string Foo(string decrypted)
{
    string s = decrypted.Substring(blah);
    string t = s;
    return t;
}

?

Thanks.


2
为什么这很重要?string是一个引用类型,所以即使它没有被优化,最坏的情况下也只会有两个额外的字长副本。这微不足道。 - user395760
1
它的时间复杂度为O(1),所以无论哪种方式都没有时间差异。通常第一种方式更易读,因此使用它。但是如果第二种方式更易读,则使用第二种方式。 - Frames Catherine White
2个回答

4

我很好奇,所以我试了一下(使用优化后的LINQPad 4)。这是我得到的结果:

private string Foo(string decrypted)
{
    return decrypted.Substring(0);
}
Foo:
IL_0000:  ldarg.1     
IL_0001:  ldc.i4.0    
IL_0002:  callvirt    System.String.Substring
IL_0007:  ret        

private string Foo(string decrypted)
{
    string s = decrypted.Substring(0);
    return s;
}
Foo:
IL_0000:  ldarg.1     
IL_0001:  ldc.i4.0    
IL_0002:  callvirt    System.String.Substring
IL_0007:  stloc.0     // s
IL_0008:  ldloc.0     // s
IL_0009:  ret     

private string Foo(string decrypted)
{
    string s = decrypted.Substring(0);
    string t = s;
    return t;
}
Foo:
IL_0000:  ldarg.1     
IL_0001:  ldc.i4.0    
IL_0002:  callvirt    System.String.Substring
IL_0007:  stloc.0     // s
IL_0008:  ldloc.0     // s
IL_0009:  stloc.1     // t
IL_000A:  ldloc.1     // t
IL_000B:  ret    

看起来编译器没有对st进行优化,但我猜测JIT解释器会这样做。


1
从Visual Studio发布的版本绝对会删除额外的变量。(我刚测试过)所以看起来Linqpad说的不是真的。在Reflector中,它只显示:return decrypted.Substring(0); - Matthew Watson
@p.s.w.g 我刚才发现我的错误:我在 Reflector 中看的是 C# 视图,它隐藏了其他的本地变量,只显示了 return decrypted.Substring(0);,就像我之前说的一样——但是当我查看 IL 代码时,我可以看到额外的本地变量。所以 Reflector 隐藏了额外的变量!我以前没有注意到 Reflector 会做出这样的处理,所以这是个好消息! - Matthew Watson
1
所以,为了明确起见:当我说“从Visual Studio发布构建肯定会删除额外的变量”时,实际上并不是这样。Resharper只是从C#视图中删除它,这就是发生的全部情况。 - Matthew Watson
s/ReSharper/Reflector/ -- JetBrains dotPeek如果有PDB文件,则显示带名称的中间变量;否则它会像Reflector一样删除中间变量。 - Roger Lipscombe
@RogerLipscombe 哎呀,我的大脑在想反射器,但我的手指却背叛了我!; - Matthew Watson
显示剩余3条评论

1

我觉得问题基本上是“C#编译器是否支持命名返回值优化(NRVO)?”

这是一个合理的问题,但是就像delnan指出的那样,并不特别相关,因为在C#中,字符串是引用类型。如果你是一个C ++程序员,你可以将引用类型视为等同于指针。因此,尽管您通过值返回字符串,但您实际上只返回引用而不是对象本身。这就好像您通过值返回指针地址一样。不需要复制整个字符串对象,因此优化只会带来很少的收益。您需要NRVO的唯一原因是避免大量且昂贵的拷贝带来的性能损失,但是复制指针/引用并不昂贵。

然而,如果您使用值类型而不是引用类型(例如,一个大结构体)给出不同的示例,则通过实现NRVO可能会获得显着的性能提升。不幸的是,即使在这种情况下,我认为C#编译器也没有这样做。我想JITer可能会这样做;我不能确定。(如果现在没有完成,可以在将来的版本中添加。尽管这样的大型结构仅在C#代码中很少出现,因此这不太可能是性能团队目标列表的首要目标。)
有关更多详细信息,建议阅读Jon Skeet在C#中有关 引用和值的文章。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接