C# 条件运算符速度慢吗?
我在看一些代码,每种情况下都有一个巨大的switch语句和if-else语句,我立刻感到了优化的冲动。作为一名优秀的开发人员,我应该始终做到这一点。我开始了解一些艰难的时间事实,并从三个变体开始:C# 条件运算符速度慢吗?,c#,performance,if-statement,conditional-operator,micro-optimization,C#,Performance,If Statement,Conditional Operator,Micro Optimization,我在看一些代码,每种情况下都有一个巨大的switch语句和if-else语句,我立刻感到了优化的冲动。作为一名优秀的开发人员,我应该始终做到这一点。我开始了解一些艰难的时间事实,并从三个变体开始: 原始代码如下所示: public static bool SwitchIfElse(Key inKey, out char key, bool shift) { switch (inKey) { case Key.A: if (shift) { key = 'A'; }
public static bool SwitchIfElse(Key inKey, out char key, bool shift)
{
switch (inKey)
{
case Key.A: if (shift) { key = 'A'; } else { key = 'a'; } return true;
case Key.B: if (shift) { key = 'B'; } else { key = 'b'; } return true;
case Key.C: if (shift) { key = 'C'; } else { key = 'c'; } return true;
...
case Key.Y: if (shift) { key = 'Y'; } else { key = 'y'; } return true;
case Key.Z: if (shift) { key = 'Z'; } else { key = 'z'; } return true;
...
//some more cases with special keys...
}
key = (char)0;
return false;
}
for (int i = 0; i < iterations / 4; i++)
{
method(Key.Space, key, true);
method(Key.A, key, true);
method(Key.Space, key, false);
method(Key.A, key, false);
}
public static bool SwitchConditionalOperator(Key inKey, out char key, bool shift)
{
switch (inKey)
{
case Key.A: key = shift ? 'A' : 'a'; return true;
case Key.B: key = shift ? 'B' : 'b'; return true;
case Key.C: key = shift ? 'C' : 'c'; return true;
...
case Key.Y: key = shift ? 'Y' : 'y'; return true;
case Key.Z: key = shift ? 'Z' : 'z'; return true;
...
//some more cases with special keys...
}
key = (char)0;
return false;
}
public static bool DictionaryLookup(Key inKey, out char key, bool shift)
{
key = '\0';
if (shift)
return _upperKeys.TryGetValue(inKey, out key);
else
return _lowerKeys.TryGetValue(inKey, out key);
}
shift
设置为true和false,并选择一组输入键:
Run(method);
var stopwatch = Stopwatch.StartNew();
Run(method);
stopwatch.Stop();
var measure = stopwatch.ElapsedMilliseconds / iterations;
Run方法如下所示:
public static bool SwitchIfElse(Key inKey, out char key, bool shift)
{
switch (inKey)
{
case Key.A: if (shift) { key = 'A'; } else { key = 'a'; } return true;
case Key.B: if (shift) { key = 'B'; } else { key = 'b'; } return true;
case Key.C: if (shift) { key = 'C'; } else { key = 'c'; } return true;
...
case Key.Y: if (shift) { key = 'Y'; } else { key = 'y'; } return true;
case Key.Z: if (shift) { key = 'Z'; } else { key = 'z'; } return true;
...
//some more cases with special keys...
}
key = (char)0;
return false;
}
for (int i = 0; i < iterations / 4; i++)
{
method(Key.Space, key, true);
method(Key.A, key, true);
method(Key.Space, key, false);
method(Key.A, key, false);
}
2) 条件运算符:
L_0165: ldarg.1
L_0166: ldarg.2
L_0167: brtrue.s L_016d
L_0169: ldc.i4.s 0x62
L_016b: br.s L_016f
L_016d: ldc.i4.s 0x42
L_016f: stind.i2
L_0170: ldc.i4.1
L_0171: ret
一些意见:
- 当
等于true时,条件运算符分支,而当shift
为false时,if/else分支李>shift
- 虽然1)实际编译为比2)多几个指令,但当
为true或false时执行的指令数对于这两个指令是相等的shift
- 1)的指令顺序是,始终只占用一个堆栈插槽,而2)始终加载两个堆栈插槽
在对实时系统进行编码时,我们总是使用一个查找表(一个简单的数组)进行转换,如您的示例所示。当输入范围相当小时,它的速度最快。我会选择第三个选项,只是因为它更易于阅读/维护。
我打赌这段代码不是您应用程序性能的瓶颈。非常奇怪,也许.NET优化会适得其反: 作者拆开了几个 三元表达式和 发现它们与 if语句,用一个小的 差别。三元语句 有时生成测试 相反的条件,你会 期望,因为在它的测试中 子表达式为false,而不是 测试它是否为真。这将重新排序 一些说明和可以 偶尔提高性能
您可能会考虑枚举值上的ToStin(对于非特殊情况):
编辑:我将if-else方法与三值运算符进行了比较,对于1000000个循环,三值运算符始终至少与if-else方法一样快(有时快几毫秒,支持上面的文本)。我认为您在测量所用时间时犯了某种错误。有趣的是,我在这里开发了一个小类
IfElseTernaryTest
,好的,代码不是真正的“优化”或很好的示例,但是……为了讨论:
public class IfElseTernaryTest
{
private bool bigX;
public void RunIfElse()
{
int x = 4; int y = 5;
if (x > y) bigX = false;
else if (x < y) bigX = true;
}
public void RunTernary()
{
int x = 4; int y = 5;
bigX = (x > y) ? false : ((x < y) ? true : false);
}
}
这是代码的IL转储…有趣的是,IL中的三元指令实际上比if
短
.class /*02000003*/ public auto ansi beforefieldinit ConTern.IfElseTernaryTest
extends [mscorlib/*23000001*/]System.Object/*01000001*/
{
.field /*04000001*/ private bool bigX
.method /*06000003*/ public hidebysig instance void
RunIfElse() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x205c
// Code size 44 (0x2c)
.maxstack 2
.locals /*11000001*/ init ([0] int32 x,
[1] int32 y,
[2] bool CS$4$0000)
.line 19,19 : 9,10 ''
//000013: }
//000014:
//000015: public class IfElseTernaryTest
//000016: {
//000017: private bool bigX;
//000018: public void RunIfElse()
//000019: {
IL_0000: /* 00 | */ nop
.line 20,20 : 13,23 ''
//000020: int x = 4; int y = 5;
IL_0001: /* 1A | */ ldc.i4.4
IL_0002: /* 0A | */ stloc.0
.line 20,20 : 24,34 ''
IL_0003: /* 1B | */ ldc.i4.5
IL_0004: /* 0B | */ stloc.1
.line 21,21 : 13,23 ''
//000021: if (x > y) bigX = false;
IL_0005: /* 06 | */ ldloc.0
IL_0006: /* 07 | */ ldloc.1
IL_0007: /* FE02 | */ cgt
IL_0009: /* 16 | */ ldc.i4.0
IL_000a: /* FE01 | */ ceq
IL_000c: /* 0C | */ stloc.2
IL_000d: /* 08 | */ ldloc.2
IL_000e: /* 2D | 09 */ brtrue.s IL_0019
.line 21,21 : 24,37 ''
IL_0010: /* 02 | */ ldarg.0
IL_0011: /* 16 | */ ldc.i4.0
IL_0012: /* 7D | (04)000001 */ stfld bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
IL_0017: /* 2B | 12 */ br.s IL_002b
.line 22,22 : 18,28 ''
//000022: else if (x < y) bigX = true;
IL_0019: /* 06 | */ ldloc.0
IL_001a: /* 07 | */ ldloc.1
IL_001b: /* FE04 | */ clt
IL_001d: /* 16 | */ ldc.i4.0
IL_001e: /* FE01 | */ ceq
IL_0020: /* 0C | */ stloc.2
IL_0021: /* 08 | */ ldloc.2
IL_0022: /* 2D | 07 */ brtrue.s IL_002b
.line 22,22 : 29,41 ''
IL_0024: /* 02 | */ ldarg.0
IL_0025: /* 17 | */ ldc.i4.1
IL_0026: /* 7D | (04)000001 */ stfld bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
.line 23,23 : 9,10 ''
//000023: }
IL_002b: /* 2A | */ ret
} // end of method IfElseTernaryTest::RunIfElse
.method /*06000004*/ public hidebysig instance void
RunTernary() cil managed
// SIG: 20 00 01
{
// Method begins at RVA 0x2094
// Code size 27 (0x1b)
.maxstack 3
.locals /*11000002*/ init ([0] int32 x,
[1] int32 y)
.line 25,25 : 9,10 ''
//000024: public void RunTernary()
//000025: {
IL_0000: /* 00 | */ nop
.line 26,26 : 13,23 ''
//000026: int x = 4; int y = 5;
IL_0001: /* 1A | */ ldc.i4.4
IL_0002: /* 0A | */ stloc.0
.line 26,26 : 24,34 ''
IL_0003: /* 1B | */ ldc.i4.5
IL_0004: /* 0B | */ stloc.1
.line 27,27 : 13,63 ''
//000027: bigX = (x > y) ? false : ((x < y) ? true : false);
IL_0005: /* 02 | */ ldarg.0
IL_0006: /* 06 | */ ldloc.0
IL_0007: /* 07 | */ ldloc.1
IL_0008: /* 30 | 0A */ bgt.s IL_0014
IL_000a: /* 06 | */ ldloc.0
IL_000b: /* 07 | */ ldloc.1
IL_000c: /* 32 | 03 */ blt.s IL_0011
IL_000e: /* 16 | */ ldc.i4.0
IL_000f: /* 2B | 01 */ br.s IL_0012
IL_0011: /* 17 | */ ldc.i4.1
IL_0012: /* 2B | 01 */ br.s IL_0015
IL_0014: /* 16 | */ ldc.i4.0
IL_0015: /* 7D | (04)000001 */ stfld bool ConTern.IfElseTernaryTest/*02000003*/::bigX /* 04000001 */
.line 28,28 : 9,10 ''
//000028: }
IL_001a: /* 2A | */ ret
} // end of method IfElseTernaryTest::RunTernary
看来,三元运算符显然更短,而且我猜,指令越少,运算速度越快……但在此基础上,它似乎与您的案例#2相矛盾,这是令人惊讶的
编辑:在Sky的评论之后,建议“代码膨胀为#2”,这将反驳Sky的说法!!!好的,代码不同,上下文不同,这是一个示例练习,检查IL转储以查看…我不太明白为什么您会期望if语句比字典查找慢。至少需要计算一个hashcode,然后在列表中查找它。我不明白为什么你会认为这比cmp/jmp快
具体来说,我甚至不认为你正在优化的方法是那么好;似乎在调用阶段可以做得更好(尽管我不能确定,因为您没有提供上下文)。我很想知道您是否正在使用调试或发布版本来测试它。如果是调试生成,那么差异很可能是由于使用发布模式(或手动禁用调试模式并启用编译器优化)时编译器添加的低级优化不足造成的 但是,我希望通过优化,三元运算符的速度与if/else语句相同或稍快,而字典查找速度最慢。以下是我的结果,1000万次预热迭代,然后1000万次定时,每一次: 调试模式
If/Else: 00:00:00.7211259
Ternary: 00:00:00.7923924
Dictionary: 00:00:02.3319567
If/Else: 00:00:00.5217478
Ternary: 00:00:00.5050474
Dictionary: 00:00:02.7389423
释放模式
If/Else: 00:00:00.7211259
Ternary: 00:00:00.7923924
Dictionary: 00:00:02.3319567
If/Else: 00:00:00.5217478
Ternary: 00:00:00.5050474
Dictionary: 00:00:02.7389423
我认为这里值得注意的是,在启用优化之前,三元计算比if/else慢,而在启用优化之后,三元计算慢