编辑 3
如果我在修复了时序错误的情况下返回原始测试,我会得到与此类似的输出。
有条件的耗时 67ms
正常耗时 83ms
缓存耗时 73ms
这表明三元/条件运算符在for
循环中可以稍微快一些。鉴于先前的发现,当逻辑分支被抽象出循环时,该if
块优于三元/条件运算符,我们可以推断,当迭代使用条件/三元运算符时,编译器能够进行额外的优化,至少在一些案例。
我不清楚为什么这些优化不适用于或不适用于标准if
块。实际差异相当小,我认为这是一个有争议的问题。
编辑 2
此处突出显示的测试代码中有一个明显的错误
Stopwatch
调用之间没有重置,当我使用而Stopwatch.Restart
不是Stopwatch.Start
迭代到 1000000000 时,我得到了结果
条件耗时 22404ms
正常耗时 21403ms
这更像是我所期待的结果,并被提取的 CIL 所证实。因此,当与周围代码隔离时,“正常”if
实际上比三元\条件运算符快一点。
编辑
经过下面概述的调查后,我建议使用逻辑条件在两个常量或文字之间进行选择时,条件/三元运算符可能比标准if
块快得多。在我的测试中,它的速度大约是原来的两倍。
但是我不能完全弄清楚为什么。正常生成的 CILif
更长,但对于这两个函数,平均执行路径似乎是 6 行,包括 3 次加载和 1 或 2 次跳转,有什么想法吗?.
使用此代码,
using System.Diagnostics;
class Program
{
static void Main()
{
var stopwatch = new Stopwatch();
var conditional = Conditional(10);
var normal = Normal(10);
var cached = Cached(10);
if (new[] { conditional, normal }.Any(x => x != cached))
{
throw new Exception();
}
stopwatch.Start();
conditional = Conditional(10000000);
stopWatch.Stop();
Console.WriteLine(
"Conditional took {0}ms",
stopwatch.ElapsedMilliseconds);
////stopwatch.Start(); incorrect
stopwatch.Restart();
normal = Normal(10000000);
stopWatch.Stop();
Console.WriteLine(
"Normal took {0}ms",
stopwatch.ElapsedMilliseconds);
////stopwatch.Start(); incorrect
stopwatch.Restart();
cached = Cached(10000000);
stopWatch.Stop();
Console.WriteLine(
"Cached took {0}ms",
stopwatch.ElapsedMilliseconds);
if (new[] { conditional, normal }.Any(x => x != cached))
{
throw new Exception();
}
Console.ReadKey();
}
static int Conditional(int iterations)
{
var ret = 0;
for (int j = 0; j < iterations; j++)
{
ret = (j * 11 / 3 % 5) + (ret % 11 == 4 ? 2 : 1);
}
return ret;
}
static int Normal(int iterations)
{
var ret = 0;
for (int j = 0; j < iterations; j++)
{
if (ret % 11 == 4)
{
ret = 2 + (j * 11 / 3 % 5);
}
else
{
ret = 1 + (j * 11 / 3 % 5);
}
}
return ret;
}
static int Cached(int iterations)
{
var ret = 0;
for (int j = 0; j < iterations; j++)
{
var tmp = j * 11 / 3 % 5;
if (ret % 11 == 4)
{
ret = 2 + tmp;
}
else
{
ret = 1 + tmp;
}
}
return ret;
}
}
在 x64 发布模式下编译,经过优化,无需附加调试器即可运行。我得到这个输出,
有条件的耗时 65ms
正常耗时 148ms
缓存耗时 217ms
并且没有抛出异常。
使用 ILDASM 反汇编代码,我可以确认这三种方法的 CIL 不同,方法的代码Conditional
稍微短一些。
要真正回答“为什么”的问题,我需要了解编译器的代码。我可能需要知道为什么编译器是这样写的。
您可以进一步分解它,以便您实际上只比较逻辑函数而忽略所有其他活动。
static int Conditional(bool condition, int value)
{
return value + (condition ? 2 : 1);
}
static int Normal(bool condition, int value)
{
if (condition)
{
return 2 + value;
}
return 1 + value;
}
你可以用它迭代
static int Looper(int iterations, Func<bool, int, int> operation)
{
var ret = 0;
for (var j = 0; j < iterations; j++)
{
var condition = ret % 11 == 4;
var value = ((j * 11) / 3) % 5;
ret = operation(condition, value);
}
}
该测试仍然显示出性能差异,但现在以另一种方式简化了下面的 IL。
... Conditional ...
{
: ldarg.1 // push second arg
: ldarg.0 // push first arg
: brtrue.s T // if first arg is true jump to T
: ldc.i4.1 // push int32(1)
: br.s F // jump to F
T: ldc.i4.2 // push int32(2)
F: add // add either 1 or 2 to second arg
: ret // return result
}
... Normal ...
{
: ldarg.0 // push first arg
: brfalse.s F // if first arg is false jump to F
: ldc.i4.2 // push int32(2)
: ldarg.1 // push second arg
: add // add second arg to 2
: ret // return result
F: ldc.i4.1 // push int32(1)
: ldarg.1 // push second arg
: add // add second arg to 1
: ret // return result
}