C# .净回报值优化

C# .净回报值优化,c#,C#,.NET编译器是否会对此进行优化: public MyObject GetNewObject() { var newCurrentObject = myObjectFactory.CreateNew( DateTime.Now, "Frank", 41,

.NET编译器是否会对此进行优化:

public MyObject GetNewObject()
{
    var newCurrentObject = myObjectFactory.CreateNew(
                                 DateTime.Now, 
                                 "Frank", 
                                 41, 
                                 secretPassword);

    return newCurrentObject;
}
要使用与此相同数量的指令/内存执行:

public MyObject GetNewObject()
{
    return myObjectFactory.CreateNew(
                        DateTime.Now, 
                        "Frank", 
                        41, 
                        secretPassword);
}
或者局部变量会导致额外的时间和内存被用来创建对MyObject的引用(newObject),一旦超出范围,只会在下一行销毁它


我这样问是因为,尽管性能相同,我发现第一个更具可读性,因为局部变量名通常可以为下一个开发人员提供一些关于我们在这里所做工作的上下文。

对象是通过引用返回的。在这两种情况下都会创建引用,它位于堆栈的顶部。它是一个引用,然后返回该引用,因此它是相同的。对象未移动到堆中的其他位置,因此引用无法更改。您可能需要克隆对象,然后将引用返回到克隆的对象。然后,您将在堆上分配一个额外的引用和一个额外的空间

如果使用的不是MyObject,而是int,但返回类型仍然是Object,则会出现唯一的问题:

public object BoxingOccurs()
{
    int i = 5;
    return i; // or shortly return 5;
}

然后,原语类型将被装箱,这意味着值类型将转换为对象类型。原语类型被包装并移动到托管堆。可以找到有关装箱和取消装箱的更多信息。

假设
MyObject
是引用类型,则将为这两种情况生成相同的x86。JIT在优化时间和任务方面非常有能力。这是最基本的优化之一。几乎所有优化器都在内部使用SSA表单,这种优化几乎不属于SSA表单


假设
MyObject
是一个
struct
:我已经对.NET4.5JIT和新的RyuJIT进行了广泛的结构优化测试。NET JIT通常不会优化结构分配和(本地)变量。除了这里不适用的一个小情况外,代码是逐字翻译的。应该是完全文字化的机器代码。即使你说
a=a
a.x=1;a、 x=1你得到的正是机器代码。如果结构对您很重要,请向团队发送邮件。现在仍然是进行更改的时候。

newObject
是一个变量,而不是引用(该变量包含对对象的引用)。我已经尝试过代码(在发布模式下),编译器不会像这样优化代码。我不确定,但规范甚至可能不允许这样做,因为变量是反射的一部分,这可能会影响预期的行为。但是,这并不意味着代码不会因为抖动而以这种方式运行,这可能会对代码进行优化。

通常,当您在C编译器中启用优化时。因此,您不应该考虑尽量减少变量的数量,因为编译器会为您做这件事。如果没有优化,将会有一些差异,因为它们的目标是增强调试体验并尽可能多地保留信息

在这种特殊情况下,当启用优化时,两种方法都会发出完全相同的IL。以下是一个完整的代码段,其中包括用于比较的值类型和引用类型:

using System;

public class C {

    private static MyStruct NewStructLocal()
    {
        var s = new MyStruct();
        return s;
    }

    private static MyStruct NewStructReturn()
    {
        return new MyStruct();
    }

    private static MyClass NewClassLocal()
    {
        var s = new MyClass();
        return s;
    }

    private static MyClass NewClassReturn()
    {
        return new MyClass();
    }
}

public struct MyStruct
{
    public int I;
}

public class MyClass
{
    public int I;
}
发射的IL为:

.method private hidebysig static 
    valuetype MyStruct NewStructLocal () cil managed 
{
    // Method begins at RVA 0x2054
    // Code size 10 (0xa)
    .maxstack 1
    .locals init (
        [0] valuetype MyStruct
    )

    IL_0000: ldloca.s 0
    IL_0002: initobj MyStruct
    IL_0008: ldloc.0
    IL_0009: ret
} // end of method C::NewStructLocal

.method private hidebysig static 
    valuetype MyStruct NewStructReturn () cil managed 
{
    // Method begins at RVA 0x206c
    // Code size 10 (0xa)
    .maxstack 1
    .locals init (
        [0] valuetype MyStruct
    )

    IL_0000: ldloca.s 0
    IL_0002: initobj MyStruct
    IL_0008: ldloc.0
    IL_0009: ret
} // end of method C::NewStructReturn

.method private hidebysig static 
    class MyClass NewClassLocal () cil managed 
{
    // Method begins at RVA 0x2082
    // Code size 6 (0x6)
    .maxstack 8

    IL_0000: newobj instance void MyClass::.ctor()
    IL_0005: ret
} // end of method C::NewClassLocal

.method private hidebysig static 
    class MyClass NewClassReturn () cil managed 
{
    // Method begins at RVA 0x2082
    // Code size 6 (0x6)
    .maxstack 8

    IL_0000: newobj instance void MyClass::.ctor()
    IL_0005: ret
} // end of method C::NewClassReturn
因此,就性能而言,它们是相同的

对于引用类型,将创建对象,将引用放在求值堆栈的顶部,并立即从那里返回

对于值类型,声明本地存储位置,初始化存储位置中的值,再次加载值,然后返回。对于不是基元类型的值类型,这是非常典型的

需要考虑的一个更一般的问题是,在任何非常复杂的方法中,编译器都会生成许多未命名的临时对象,这些临时对象的名称程序员无法访问。就性能而言,担心在代码中可以看到多少命名的局部变量是在花时间担心错误的事情

注意


自从我第一次写下这个答案以来,这个问题已经有所改变,但是我认为所有这些分析仍然有效。在特定情况下,假设您在使用优化编译时在两种方法中获得完全相同的IL。局部变量对发出的IL没有影响。现在,如果要为其他参数添加更多的局部变量,则会有不同的IL,并且需要检查反汇编以确定差异的完整程度。

实际上也不会,您没有尝试在第一个块中返回
newObject
。因为它是一个引用类型,当它在第二个块中返回
new MyObject()
时,它只会返回一个对它预先存在的内存分配的引用,因此它不会创建/销毁和额外变量。@AlexeiLevenkov不是所有的优化。一些优化是由C#编译器本身执行的。想知道为什么我的评论被删除了。。。问题甚至是编译器是否会对此进行优化,那么我们为什么要讨论JIT而不是IL?我不确定这是如何回答这个问题的。问题的实质是局部变量存储是否优化。而不是返回相同的引用还是副本。最后一行措辞混乱;但这并不是问题的答案,你的观点是正确的。实际发生的是语句(两者)在堆栈顶部创建一个引用,该引用引用堆中类的实例,然后返回引用。很可能的情况是,引用存储在某个注册表中,并一直保留在那里,直到当前不需要它进行计算为止。但是,这是特定于机器的,取决于MSIL如何转换为所使用处理器的汇编程序,以及处理器如何处理此类操作