C# 为什么可以';是否在循环外优化只读字段检查?

C# 为什么可以';是否在循环外优化只读字段检查?,c#,compiler-optimization,C#,Compiler Optimization,当我有一个只读变量时: public readonly bool myVar = true; 并在如下代码中进行检查: for(int i = 0; i != 10; i++) { if(myVar) DoStuff(); else DoOtherStuff(); } 查看发出的IL,我可以看到检查是在循环的每个迭代中执行的。我希望上面的代码产生与此相同的IL: if (myVar) { for(int i = 0; i != 10;

当我有一个只读变量时:

public readonly bool myVar = true;
并在如下代码中进行检查:

for(int i = 0; i != 10; i++)
{
    if(myVar)
        DoStuff();
    else
        DoOtherStuff();
}
查看发出的IL,我可以看到检查是在循环的每个迭代中执行的。我希望上面的代码产生与此相同的IL:

if (myVar)
{
    for(int i = 0; i != 10; i++)
    {
        DoStuff();
    }
}
else
{
    for(int i = 0; i != 10; i++)
    {
        DoOtherStuff();
    }
}

既然字段是只读的,在迭代之间不能更改,那么为什么检查没有优化到循环的外部呢?

因为它可以使用反射来更改:

using System;
using System.Reflection;

public class Program
{
    public static void Main()
    {
        var t = new Test();
        var field = typeof(Test).GetField("myVar", BindingFlags.Instance | BindingFlags.Public);

        Console.WriteLine(t.myVar); // prints True

        field.SetValue(t, false);
        Console.WriteLine(t.myVar); // prints False

        // Trying to use t.myVar = false or true; <-- does not compile
    }
}

public class Test
{
    public readonly bool myVar = true;
}
使用系统;
运用系统反思;
公共课程
{
公共静态void Main()
{
var t=新测试();
var field=typeof(Test).GetField(“myVar”,BindingFlags.Instance | BindingFlags.Public);
Console.WriteLine(t.myVar);//打印为True
字段设置值(t,false);
Console.WriteLine(t.myVar);//打印为False

//尝试使用t.myVar=false或true;A
readonly
字段可以在代码中的不同点初始化为不同的值(多个构造函数)。无法优化它,因为多个构造函数允许有问题的字段的多个值。 如果你只有一个构造函数,没有相关分支,因此只有一个可能的值,对于那个字段,我希望从优化器中得到更多。然而,这种分析是C++比C语言花费更长的编译时间的一个原因,而且这些任务通常不被委托给运行时。 更清楚的解释是:

只读关键字与常量关键字不同。常量 只能在声明字段时初始化字段。A 只读字段可以在字段声明中分配多次 因此,只读字段可以具有不同的 值,具体取决于所使用的构造函数。此外,在 作为编译时常量,只读字段可用于运行时 常数,如下例所示:

对于您的具体情况,我建议使用
const
而不是
readonly
,尽管我没有寻找生成IL的差异


老实说,我想知道这是否真的有什么不同。任何执行分支预测并具有一半好缓存的CPU在这两种情况下都可能产生相同的性能。(注意,我还没有测试过这一点;这只是一种怀疑。)

您提出的优化实际上是两个单独的简单转换的组合。第一个是将成员访问权拉到循环之外。从

for(int i = 0; i != 10; i++)
{
    var localVar = this.memberVar;
    if(localVar)
        DoStuff();
    else
        DoOtherStuff();
}

第二个是将循环条件与if条件互换

var localVar = this.memberVar;
for(int i = 0; i != 10; i++)
{
    if(localVar)
        DoStuff();
    else
        DoOtherStuff();
}

第一个受
readonly
的影响。要做到这一点,编译器必须证明
memberVar
不能在循环内更改,并且
readonly
保证这一点1——即使此循环可以在构造函数内调用,
memberVar
的值可以在循环后在构造函数中更改最后,它不能在循环体中更改--
DoStuff()
不是当前对象的构造函数,也不是
DoOtherStuff()
。反射不起作用,虽然可以使用反射来打破不变量,但不允许这样做。线程起作用,请参见脚注

第二个是一个简单的转换,但对于编译器来说,这是一个更难做出的决定,因为很难预测它是否真的会提高性能。当然,您可以通过自己对代码进行第一个转换,并查看生成的代码来单独查看它

也许更重要的考虑是,在.NET中,优化过程发生在MSIL和机器代码之间,而不是在C#to IL的编译过程中。因此通过查看MSIL,您无法看到正在进行哪些优化!



net内存模型比C++模型要宽容得多,因为任何数据竞争都很快地导致未定义的行为,除非对象定义为“代码>易失性/代码/原子”。如果这个循环在从对象构造函数中生成的辅助线程中运行,并且在生成线程之后,构造函数继续运行。(我将其称为“后半部分”)来更改
readonly
成员?内存模型是否要求工作线程看到该更改?如果
DoStuff()
和构造器的后半部分强制内存隔离,例如访问
易失性的其他成员,或者获取锁?因此
只读
只允许在单线程环境中进行优化

这是一个经过深思熟虑的答案,并且提出了一个有效的观点,但是:我还没有开始n在一种情况下,故意禁用优化只是为了让反射满意。还有其他示例吗?@3Dave它可能有点做作,但它确实给出了编译器无法优化它的一个很好的理由。使用反射破坏不变量会导致未定义的行为。请参阅示例,因为
readonly
仅为编译时规则。它实际上并不能保证任何东西。请参见重复。无论多么不明智,优化都不能破坏合法代码。还要注意,检查生成的CIL并不能真正显示任何内容。生成的本机代码才是重要的,我相当肯定我在那里看到过这种优化。我不希望它是优化与const类似的函数(const在IL中仅由其值表示,而整个else部分将是noop,因为我们在编译时就知道该值)。我希望它被优化为仅在循环外进行一次检查,至少如果我们不在构造函数或静态方法中,因为此时该值已被赋值。可以在循环内修改I,但不会影响检查结果。@DefaultUserName您不能基于
var localVar = this.memberVar;
for(int i = 0; i != 10; i++)
{
    if(localVar)
        DoStuff();
    else
        DoOtherStuff();
}
var localVar = this.memberVar;
if (localVar) {
    for(int i = 0; i != 10; i++)
        DoStuff();
}
else {
    for(int i = 0; i != 10; i++)
        DoOtherStuff();
}