C# 以这种方式使用内联if语句是否有效?

C# 以这种方式使用内联if语句是否有效?,c#,C#,如果我给一个变量赋值,然后想给它赋值,但只有当它满足一个条件时,使用缩写If语句是否同样有效?这里有一个例子 这样更有效吗 int x = GetInt(); if (x < 5) x = 5; intx=GetInt(); if(x

如果我给一个变量赋值,然后想给它赋值,但只有当它满足一个条件时,使用缩写If语句是否同样有效?这里有一个例子

这样更有效吗

int x = GetInt();
if (x < 5)
    x = 5;
intx=GetInt();
if(x<5)
x=5;
比这个

int x = GetInt();
x = x < 5 ? 5 : x;
intx=GetInt();
x=x<5?5:x;

我想我真正要问的是,如果
x
不满足条件,那么else语句中的
x=x
会影响性能吗?

如果x与getter/setter没有关联,编译器应该以x=x的形式抛出任何语句。因此,任何其他版本也将消失。

我喜欢这个版本:

int x = Math.Max(5, GetInt());
但请记住,所有这些都是过早的优化。今天快的东西明天可能会慢一些,比如一个Windows更新,它可以改变框架以添加新的或不同的JIT优化

如果您在一个大循环中运行这样的检查,我可能会花一些时间来查找:

 var items = Enumerable.Range(0, 1000000);

 foreach(int item in items)
 {
     if (item % 3 == 0)
     { 
         //...
     }
     else
     {
         //...
     }
 }
检查循环的原因并不是因为代码将以尽可能快的速度运行多次,而是因为无论是使用
if
还是
else
都会在整个循环中来回变化

我认为这段代码效率很低,因为现代cpu的一个特性是分支预测。如果
If
else
的内容足够显著和足够不同,则可以通过在前端执行所有这些检查(并使所有预测失败),然后一起运行所有
If
s,然后再运行所有
else
s,使代码运行得更快。它会更快,因为在运行if和else的第二阶段(运行成本可能要高得多)的分支预测会更准确

这里有一个小程序来演示这种差异:

class Program
{
    static int samplesize = 1000000;
    //ensure these are big enough that we don't spend time allocating new buffers while the stopwatch is running
    static Dictionary<int, string> ints = new Dictionary<int,string>(samplesize * 4); 
    static Dictionary<double,string> doubles = new Dictionary<double,string>(samplesize * 4);

    static void Main(string[] args)
    {
        var items = Enumerable.Range(0, samplesize).ToArray() ;
        var clock = new Stopwatch();

        test1(items); //jit hit, discard first run. Also ensure all keys already exist in the dictionary for both tests
        clock.Restart();
        test1(items);
        clock.Stop();
        Console.WriteLine("Time for naive unsorted: " + clock.ElapsedTicks.ToString());

        test2(items); //jit hit
        clock.Restart();
        test2(items);
        clock.Stop();
        Console.WriteLine("Time for separated/branch prediction friendly: " + clock.ElapsedTicks.ToString());

        Console.ReadKey(true);
    }

    static void test1(IEnumerable<int> items)
    {
        foreach(int item in items)
        {
            //different code branches that still do significant work in the cpu
            // doing more work here results in a larger branch-prediction win, to a point
            if (item % 3 == 0)
            {   //force hash computation and multiplication op (both cpu-bound)
                ints[item] = (item * 2).ToString();
            }
            else
            {
                doubles[(double)item] = (item * 3).ToString();
            }
        }
    }

    static void test2(IEnumerable<int> items)
    {
        //doing MORE work: need to evaluate our items two ways, allocate arrays
        var intItems = items.Where(i => i % 3 == 0).ToArray();
        var doubleItems = items.Where(i => i % 3 != 0).ToArray();

        // but now there is no branching... adding all the ints, then adding all the doubles.
        foreach (var item in intItems) { ints[item] = (item * 2).ToString(); }
        foreach (var item in doubleItems) { doubles[(double)item] = (item * 3).ToString(); }
    }
}
类程序
{
静态int-samplesize=1000000;
//确保这些缓冲区足够大,在秒表运行时,我们不会花时间分配新的缓冲区
静态字典ints=新字典(示例大小*4);
静态字典加倍=新字典(示例大小*4);
静态void Main(字符串[]参数)
{
var items=Enumerable.Range(0,samplesize.ToArray();
var时钟=新秒表();
test1(items);//jit命中,放弃第一次运行。还要确保字典中已存在两个测试的所有键
clock.Restart();
测试1(项目);
时钟停止();
WriteLine(“天真未排序的时间:+clock.ElapsedTicks.ToString());
test2(项目);//jit命中
clock.Restart();
测试2(项目);
时钟停止();
Console.WriteLine(“分离/分支预测时间:+clock.ElapsedTicks.ToString());
Console.ReadKey(true);
}
静态无效测试1(IEnumerable项)
{
foreach(items中的int项)
{
//不同的代码分支仍然在cpu中执行重要的工作
//在这里做更多的工作可以在一定程度上获得更大的分支预测胜利
如果(项目%3==0)
{//强制哈希计算和乘法运算(都是cpu限制的)
ints[item]=(item*2).ToString();
}
其他的
{
double[(双)项]=(项*3).ToString();
}
}
}
静态无效测试2(IEnumerable项)
{
//做更多的工作:需要以两种方式评估项目,分配数组
var intItems=items.Where(i=>i%3==0.ToArray();
var doubleItems=items.Where(i=>i%3!=0).ToArray();
//但是现在没有分支了…添加所有整数,然后添加所有双精度。
foreach(intItems中的var item){ints[item]=(item*2).ToString();}
foreach(doubleItems中的var项){doubles[(double)项]=(项*3).ToString();}
}
}
在我的机器上的结果是,第二次测试做了更多的工作,运行得更快:

天真未分类的时间:1118652
分离/分支预测时间:1005190

这里重要的是,您不需要回头看看是否所有的循环都能从分支预测中受益。这只是众多CPU特性中的一个,这些特性可以使性能结果让您感到惊讶。这里需要了解的重要一点是,要确定代码将如何执行,实际上需要测量性能。如果你不仔细地构建它,天真的技术仍然可以获胜(我的第一次尝试没有得到我预期的速度)

此外,我需要指出的是,在这些情况下并没有太大的区别。这样的绩效提升值不值得,或者你在其他地方花费时间会做得更好?要知道这一点,唯一的办法就是从整体上衡量你的应用程序的性能,并找出它真正花在哪里的时间。哪里的速度比应该的慢?这被称为评测,有一些工具可以帮助您准确地完成这项工作。

这段代码

void Main()
{
        int x = GetInt();
        x = x < 5 ?  5 : x;
}

int GetInt()
{return 5;}
而这个

void Main()
{
    int x = GetInt();
    if (x < 5) x = 5;
}            

int GetInt()
{return 5;}
所以这看起来更‘有效’(?)

但这实际上是一个微观优化,它永远不会对代码产生任何影响,因此,我建议使用可读性最好的一个(我认为这与上一个一致)

编辑 JoelCoehoorn的答案肯定是最好的:(至少在可读性和代码大小方面)


我看不出这对性能有任何严重影响,但是上面的代码更容易阅读/一眼就能看出发生了什么如果x与getter/setter没有关联,编译器应该以
x的形式抛出任何语句
void Main()
{
    int x = GetInt();
    if (x < 5) x = 5;
}            

int GetInt()
{return 5;}
IL_0000:  ldarg.0     
IL_0001:  call        UserQuery.GetInt
IL_0006:  stloc.0     // x
IL_0007:  ldloc.0     // x
IL_0008:  ldc.i4.5    
IL_0009:  bge.s       IL_000D
IL_000B:  ldc.i4.5    
IL_000C:  stloc.0     // x

GetInt:
IL_0000:  ldc.i4.5    
IL_0001:  ret         
IL_0000:  ldc.i4.5    
IL_0001:  ldarg.0     
IL_0002:  call        UserQuery.GetInt
IL_0007:  call        System.Math.Max

GetInt:
IL_0000:  ldc.i4.5    
IL_0001:  ret