E.g。我需要提取
bool xIsNull = x == null
来自我检查x == null
的循环?
据我所知if (a == true)
和if (x == null)
都使用相同的IL指令。但指针由32位或64位组成。 CLR是否应检查每一位以与null进行比较?
更新
快速测试显示没有区别,但我仍然希望有人解释这一点。
UPDATE2
我使用IL发射,所以我不能指望编译器优化我的代码。只有JIT。
答案 0 :(得分:9)
记住“过早优化是所有邪恶的根源”,优化的第一条规则是“不要”(第二条,仅针对专业人士,是“不要做它”),这是发生的事情
<强> TL; DR 强>
如果您不想深入研究某些汇编代码,我不会责怪您;)结果表明,使用临时变量不会得到优化并生成更多指令。总而言之,除非您编写非常时间关键任务,否则它不会有任何区别。
考虑以下代码:
string x = null;
bool a = x == null;
if ( a == true ) { Console.WriteLine( ); }
if ( x == null ) { Console.WriteLine( ); }
这是 Debug 模式中生成的IL(我添加了一些注释):
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 46 (0x2e)
.maxstack 2
.locals init ([0] string x,
[1] bool a,
[2] bool CS$4$0000)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0 // string x = null
IL_0003: ldloc.0
IL_0004: ldnull
IL_0005: ceq // compare x and null
IL_0007: stloc.1 // and store the result in a
IL_0008: ldloc.1
IL_0009: ldc.i4.0
IL_000a: ceq
IL_000c: stloc.2 // compare a and false
IL_000d: ldloc.2
IL_000e: brtrue.s IL_0018 // if true (that is, a is false), skip
IL_0010: nop
IL_0011: call void [mscorlib]System.Console::WriteLine()
IL_0016: nop
IL_0017: nop
IL_0018: ldloc.0
IL_0019: ldnull
IL_001a: ceq // compare x and null
IL_001c: ldc.i4.0
IL_001d: ceq // and compare with false
IL_001f: stloc.2
IL_0020: ldloc.2
IL_0021: brtrue.s IL_002b // if true (that is, x == null), skip
IL_0023: nop
IL_0024: call void [mscorlib]System.Console::WriteLine()
IL_0029: nop
IL_002a: nop
IL_002b: br.s IL_002d
IL_002d: ret
} // end of method Program::Main
总的来说,有很多ldloc和stloc可以读写数据到内存;它们非常有用,可以帮助调试器。但是你可以看到有一个隐藏的局部变量,它具有与a完全相同的功能:所以如果你不使用临时变量,编译器就会为你使用它。另请注意使用通用null
。
现在这里是启用了优化的 Release IL:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 24 (0x18)
.maxstack 2
.locals init ([0] string x,
[1] bool a)
IL_0000: ldnull
IL_0001: stloc.0 // set x to null
IL_0002: ldloc.0
IL_0003: ldnull
IL_0004: ceq
IL_0006: stloc.1 // bool a = x == null
IL_0007: ldloc.1
IL_0008: brfalse.s IL_000f // if false skip
IL_000a: call void [mscorlib]System.Console::WriteLine()
IL_000f: ldloc.0
IL_0010: brtrue.s IL_0017 // if true (so x != null) skip
IL_0012: call void [mscorlib]System.Console::WriteLine()
IL_0017: ret
} // end of method Program::Main
在优化版本中,编译器不执行显式比较,也不使用临时变量。尽管如此,在未经优化的版本中,它会存储并在此之后加载它以检查条件;这是因为stloc
弹出一个堆栈,所以它必须再次推送它。
现在,让我们比较一下JITter生成的代码(我设置x = Console.Readline()以防止整个代码被优化掉)。这适用于调试配置(如Visual Studio中所示):
string x = null;
00000043 xor edx,edx
00000045 mov dword ptr [ebp-40h],edx
bool a = x == null;
00000048 cmp dword ptr [ebp-40h],0
0000004c sete al
0000004f movzx eax,al
00000052 mov dword ptr [ebp-44h],eax
if ( a == true ) { Console.WriteLine( ); }
00000055 cmp dword ptr [ebp-44h],0
00000059 sete al
0000005c movzx eax,al
0000005f mov dword ptr [ebp-48h],eax
00000062 cmp dword ptr [ebp-48h],0
00000066 jne 00000070
00000068 nop
00000069 call 6027B57C
0000006e nop
0000006f nop
if ( x == null ) { Console.WriteLine( ); }
00000054 cmp dword ptr [ebp-0Ch],0
00000058 jne 00000065
0000005a mov ecx,dword ptr ds:[0350208Ch]
00000060 call 602DD5E0
return;
00000065 nop
00000066 mov esp,ebp
00000068 pop ebp
00000069 ret
如您所见,此代码紧跟相应的未优化IL,并在检查a的条件时使用临时变量。另一方面,由于null在我的机器上实现为0,因此比较x
和null
会更快。
这是通过OllyDbg看到的发布代码:
string x = Console.ReadLine( );
002F0075 E8 EA808A60 CALL mscorlib_ni.60B98164
002F007A 8BC8 MOV ECX, EAX
002F007C 8B01 MOV EAX, DWORD PTR DS:[ECX]
002F007E 8B40 2C MOV EAX, DWORD PTR DS:[EAX+2C]
002F0081 FF50 1C CALL DWORD PTR DS:[EAX+1C]
bool a = x == null;
002F0084 8BF0 MOV ESI, EAX
002F0086 85F6 TEST ESI, ESI
002F0088 0F94C0 SETE AL
002F008B 0FB6C0 MOVZX EAX, AL
002F008E 8BF8 MOV EDI, EAX
Systed.Diagnostics.Debugger.Break( );
002F0090 E8 E37C8E60 CALL mscorlib_ni.60BD7D78
if ( a == true ) { Console.ReadLine( ); }
002F0095 85FF TEST EDI, EDI
002F0097 74 0E JE SHORT 002F00A7
002F0099 E8 A6F92D60 CALL mscorlib_ni.605CFA44
002F009E 8BC8 MOV ECX, EAX
002F00A0 8B01 MOV EAX, DWORD PTR DS:[ECX]
002F00A2 8B40 38 MOV EAX, DWORD PTR DS:[EAX+38]
002F00A5 FF10 CALL DWORD PTR DS:[EAX]
if ( x == null ) { Console.ReadLine( ); }
002F00A7 85F6 TEST ESI, ESI
002F00A9 75 0E JNE SHORT 002F00B9
002F00AB E8 94F92D60 CALL mscorlib_ni.605CFA44
002F00B0 8BC8 MOV ECX, EAX
002F00B2 8B01 MOV EAX, DWORD PTR DS:[ECX]
002F00B4 8B40 38 MOV EAX, DWORD PTR DS:[EAX+38]
002F00B7 FF10 CALL DWORD PTR DS:[EAX]
return;
002F00B9 5E POP ESI
002F00BA 5F POP EDI
002F00BB 5D POP EBP
002F00BC C3 RETN
在此代码中,a
保存在edi
中,x
保存在esi
中,并且有一些调用mscorlib来检索指向ReadLine和WriteLine的指针。话虽如此,这两种方法实际上存在差异;在将x
与null
(test esi, esi
)进行比较后,结果将从零标记移至al(sete al
),然后扩展为整个eax
({ {1}})。
所以,即使在如此简单的情况下,JITter也没有做好工作;因此,如果没有临时变量,您可以获得次要性能增益。
答案 1 :(得分:2)
因此,通过现代编译和硬件优化,我怀疑是否会有足够的性能差异来制造足够的差异(如果有的话)。
像C#这样的高级语言的一部分是将这些微不足道的优化细节从app devs手中拿走,并将它们交给编译器开发人员,他们会更好地完成它并释放app devs以使他们的决策脱离可读性/ keepablity并将更多时间花在高级算法效率上而不是低级别的东西上。如果您遇到性能问题,这可能是您最不担心的问题。
底线我建议使用您认为使代码最具可读性的任何内容。
答案 2 :(得分:0)
不要关心微观优化。无论哪个“最快”,无疑对您的网络应用程序没有任何影响。使用最适合您和其他程序员阅读的内容。
除非您已经测量并发现代码存在问题,并且您确切知道代码中的确切位置,否则您会浪费时间。
程序员时间比计算机时间贵很多个数量级。