Resposta ao Desafio da Semana #3 [Performance - Comparando Expressões em VB.NET]
Por: Roberto Alexis Farah
Oi pessoal!
Eis a resposta do Desafio da Semana #3 https://blogs.technet.com/latam/archive/2006/05/05/427415.aspx .
O desafio ilustrou uma situação que pode ocorrer quando projetos são migrados de VB.NET para C# ou vice-versa.
PROBLEMA
O C# faz o que chamamos de curto-circuito ao resolver expressões lógicas. Ou seja a expressão pode não ser inteiramente analizada se parte da expressão for suficiente para se concluir o resultado.
Essa é uma herança do C/C++ e funciona assim:
AND OR XOR
1 0 = 0 1 0 = 1 1 0 = 1
0 1 = 0 0 1 = 1 0 1 = 1
1 1 = 1 1 1 = 1 1 1 = 0
0 0 = 0 0 0 = 0 0 0 = 0
Portanto em C# (ou C/C++) em situações como no exemplo abaixo resolver a primeira expressão é suficiente para se chegar a correta conclusão lógica. Se a primeira expressão (10 == numA) for falsa (False, 0) não é necessário se analisar a segunda expressão pois, independente do resultado da segunda expressão, a expressão toda será falsa, de acordo com a tabela acima.
If((10 == numA) && (20 == numB))
{
// Executa…
}
O mesmo ocorre no exemplo abaixo. Se a primeira expressão for verdadeira a segunda não é analizada pois não vai influenciar no resultado final:
If((10 == numA) || (20 == numB))
{
// Executa...
}
Pois bem, VB.NET por default mantém compatibilidade com VB 6.0 portanto, não faz esse tipo de análise otimizada de expressões booleanas. Entretanto, há operadores especiais do VB.NET usados justamente para isso.
Há um artigo que explica isso:
Description of "short-circuit" evaluation in Visual Basic
https://support.microsoft.com/kb/817250/en-us#EKACAAA
Portanto, VB.NET age como Visual Basic 6 quando usando os operadores herdados do Visual Basic 6, logo, a expressão é sempre completamente analizada.
E é esse comportamento que faz com que o resultado das expressões seja diferente no desafio proposto.
Entretanto, o VB.NET tem operadores especiais que fazem a análise da expressão do mesmo modo que o C#, conforme explicado no artigo acima.
Usando a ferramenta ILDASM que vem com o Visual Studio é possível ver o código Intermediate Language que é onde deveriam haver diferenças.
Código VB.NET original:
.method /*06000012*/ public static void Main() cil managed
// SIG: 00 00 01
{
.entrypoint
.custom /*0C000045:0A00001F*/ instance void [mscorlib/*23000001*/]System.STAThreadAttribute/*0100001A*/::.ctor() /* 0A00001F */ = ( 01 00 00 00 )
// Method begins at RVA 0x22d4
// Code size 138 (0x8a)
.maxstack 3
.locals /*1100000C*/ init ([0] bool VB$CG$t_bool$S0)
.language '{3A12D0B8-C26C-11D0-B442-00A0244A1DD2}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
// Source File 'C:\Development\My Tools\BLOG Articles\Article #7\CSharp\VB\Module1.vb'
.line 5,5 : 5,15 'C:\\Development\\My Tools\\BLOG Articles\\Article #7\\CSharp\\VB\\Module1.vb'
//000005: Sub Main()
IL_0000: /* 00 | */ nop
.line 6,6 : 3,49 ''
//000006: If (DoSubtraction() > 0) Or (DoSum() > 0) Then
IL_0001: /* 28 | (06)000013 */ call int32 VB.Module1/*02000007*/::DoSubtraction() /* 06000013 */
IL_0006: /* 16 | */ ldc.i4.0
IL_0007: /* FE02 | */ cgt
IL_0009: /* 28 | (06)000014 */ call int32 VB.Module1/*02000007*/::DoSum() /* 06000014 */
IL_000e: /* 16 | */ ldc.i4.0
IL_000f: /* FE02 | */ cgt
IL_0011: /* 60 | */ or
IL_0012: /* 0A | */ stloc.0
IL_0013: /* 06 | */ ldloc.0
IL_0014: /* 2C | 1B */ brfalse.s IL_0031
.line 7,7 : 7,82 ''
//000007: Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)
IL_0016: /* 72 | (70)000001 */ ldstr "Valor da soma das variaveis = {0:d}" /* 70000001 */
IL_001b: /* 7E | (04)000006 */ ldsfld int32 VB.Module1/*02000007*/::sum /* 04000006 */
IL_0020: /* 7E | (04)000007 */ ldsfld int32 VB.Module1/*02000007*/::subtraction /* 04000007 */
IL_0025: /* D6 | */ add.ovf
IL_0026: /* 8C | (01)000018 */ box [mscorlib/*23000001*/]System.Int32/*01000018*/
IL_002b: /* 28 | (0A)00001E */ call void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,
object) /* 0A00001E */
IL_0030: /* 00 | */ nop
.line 8,8 : 3,9 ''
//000008: End If
IL_0031: /* 00 | */ nop
.line 10,10 : 3,11 ''
//000009:
//000010: sum = -5
IL_0032: /* 1F | FB */ ldc.i4.s -5
IL_0034: /* 80 | (04)000006 */ stsfld int32 VB.Module1/*02000007*/::sum /* 04000006 */
.line 12,12 : 3,50 ''
//000011:
//000012: If (DoSubtraction() < 0) And (DoSum() < 0) Then
IL_0039: /* 28 | (06)000013 */ call int32 VB.Module1/*02000007*/::DoSubtraction() /* 06000013 */
IL_003e: /* 16 | */ ldc.i4.0
IL_003f: /* FE04 | */ clt
IL_0041: /* 28 | (06)000014 */ call int32 VB.Module1/*02000007*/::DoSum() /* 06000014 */
IL_0046: /* 16 | */ ldc.i4.0
IL_0047: /* FE04 | */ clt
IL_0049: /* 5F | */ and
IL_004a: /* 0A | */ stloc.0
IL_004b: /* 06 | */ ldloc.0
IL_004c: /* 2C | 1D */ brfalse.s IL_006b
.line 13,13 : 5,80 ''
//000013: Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)
IL_004e: /* 72 | (70)000001 */ ldstr "Valor da soma das variaveis = {0:d}" /* 70000001 */
IL_0053: /* 7E | (04)000006 */ ldsfld int32 VB.Module1/*02000007*/::sum /* 04000006 */
IL_0058: /* 7E | (04)000007 */ ldsfld int32 VB.Module1/*02000007*/::subtraction /* 04000007 */
IL_005d: /* D6 | */ add.ovf
IL_005e: /* 8C | (01)000018 */ box [mscorlib/*23000001*/]System.Int32/*01000018*/
IL_0063: /* 28 | (0A)00001E */ call void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,
object) /* 0A00001E */
IL_0068: /* 00 | */ nop
IL_0069: /* 2B | 1C */ br.s IL_0087
.line 14,14 : 3,7 ''
//000014: Else
IL_006b: /* 00 | */ nop
.line 15,15 : 4,84 ''
//000015: Console.WriteLine("Valor da subtracao das variaveis = {0:d}", sum - subtraction)
IL_006c: /* 72 | (70)000049 */ ldstr "Valor da subtracao das variaveis = {0:d}" /* 70000049 */
IL_0071: /* 7E | (04)000006 */ ldsfld int32 VB.Module1/*02000007*/::sum /* 04000006 */
IL_0076: /* 7E | (04)000007 */ ldsfld int32 VB.Module1/*02000007*/::subtraction /* 04000007 */
IL_007b: /* DA | */ sub.ovf
IL_007c: /* 8C | (01)000018 */ box [mscorlib/*23000001*/]System.Int32/*01000018*/
IL_0081: /* 28 | (0A)00001E */ call void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,
object) /* 0A00001E */
IL_0086: /* 00 | */ nop
.line 16,16 : 3,9 ''
//000016: End If
IL_0087: /* 00 | */ nop
.line 18,18 : 5,12 ''
//000017:
//000018: End Sub
IL_0088: /* 00 | */ nop
IL_0089: /* 2A | */ ret
} // end of method Module1::Main
Código C# :
.method /*06000001*/ private hidebysig static
void Main(string[] args) cil managed
// SIG: 00 01 01 1D 0E
{
.entrypoint
// Method begins at RVA 0x2050
// Code size 119 (0x77)
.maxstack 3
.language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
// Source File 'C:\Development\My Tools\BLOG Articles\Article #7\CSharp\CSharp\Program.cs'
.line 15,15 : 12,54 'C:\\Development\\My Tools\\BLOG Articles\\Article #7\\CSharp\\CSharp\\Program.cs'
//000015: if((DoSubtraction() > 0) || (DoSum() > 0))
IL_0000: /* 28 | (06)000002 */ call int32 CSharp.Program/*02000002*/::DoSubtraction() /* 06000002 */
IL_0005: /* 16 | */ ldc.i4.0
IL_0006: /* 30 | 08 */ bgt.s IL_0010
IL_0008: /* 28 | (06)000003 */ call int32 CSharp.Program/*02000002*/::DoSum() /* 06000003 */
IL_000d: /* 16 | */ ldc.i4.0
IL_000e: /* 31 | 1A */ ble.s IL_002a
.line 17,17 : 7,85 ''
//000016: {
//000017: Console.WriteLine("Valor da soma das variaveis = {0:d}\n", sum + subtraction);
IL_0010: /* 72 | (70)000001 */ ldstr "Valor da soma das variaveis = {0:d}\n" /* 70000001 */
IL_0015: /* 7E | (04)000001 */ ldsfld int32 CSharp.Program/*02000002*/::sum /* 04000001 */
IL_001a: /* 7E | (04)000002 */ ldsfld int32 CSharp.Program/*02000002*/::subtraction /* 04000002 */
IL_001f: /* 58 | */ add
IL_0020: /* 8C | (01)000012 */ box [mscorlib/*23000001*/]System.Int32/*01000012*/
IL_0025: /* 28 | (0A)000010 */ call void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string,
object) /* 0A000010 */
.line 20,20 : 10,19 ''
//000018: }
//000019:
//000020: sum = -5;
IL_002a: /* 1F | FB */ ldc.i4.s -5
IL_002c: /* 80 | (04)000001 */ stsfld int32 CSharp.Program/*02000002*/::sum /* 04000001 */
.line 22,22 : 11,53 ''
//000021:
//000022: if((DoSubtraction() < 0) && (DoSum() < 0))
IL_0031: /* 28 | (06)000002 */ call int32 CSharp.Program/*02000002*/::DoSubtraction() /* 06000002 */
IL_0036: /* 16 | */ ldc.i4.0
IL_0037: /* 2F | 23 */ bge.s IL_005c
IL_0039: /* 28 | (06)000003 */ call int32 CSharp.Program/*02000002*/::DoSum() /* 06000003 */
IL_003e: /* 16 | */ ldc.i4.0
IL_003f: /* 2F | 1B */ bge.s IL_005c
.line 24,24 : 6,84 ''
//000023: {
//000024: Console.WriteLine("Valor da soma das variaveis = {0:d}\n", sum + subtraction);
IL_0041: /* 72 | (70)000001 */ ldstr "Valor da soma das variaveis = {0:d}\n" /* 70000001 */
IL_0046: /* 7E | (04)000001 */ ldsfld int32 CSharp.Program/*02000002*/::sum /* 04000001 */
IL_004b: /* 7E | (04)000002 */ ldsfld int32 CSharp.Program/*02000002*/::subtraction /* 04000002 */
IL_0050: /* 58 | */ add
IL_0051: /* 8C | (01)000012 */ box [mscorlib/*23000001*/]System.Int32/*01000012*/
IL_0056: /* 28 | (0A)000010 */ call void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string,
object) /* 0A000010 */
IL_005b: /* 2A | */ ret
.line 28,28 : 5,88 ''
//000025: }
//000026: else
//000027: {
//000028: Console.WriteLine("Valor da subtracao das variaveis = {0:d}\n", sum - subtraction);
IL_005c: /* 72 | (70)00004B */ ldstr "Valor da subtracao das variaveis = {0:d}\n" /* 7000004B */
IL_0061: /* 7E | (04)000001 */ ldsfld int32 CSharp.Program/*02000002*/::sum /* 04000001 */
IL_0066: /* 7E | (04)000002 */ ldsfld int32 CSharp.Program/*02000002*/::subtraction /* 04000002 */
IL_006b: /* 59 | */ sub
IL_006c: /* 8C | (01)000012 */ box [mscorlib/*23000001*/]System.Int32/*01000012*/
IL_0071: /* 28 | (0A)000010 */ call void [mscorlib/*23000001*/]System.Console/*01000013*/::WriteLine(string,
object) /* 0A000010 */
.line 31,31 : 9,10 ''
//000029: }
//000030:
//000031: }
IL_0076: /* 2A | */ ret
} // end of method Program::Main
SOLUÇÃO
Module Module1
Dim sum As Integer = 50
Dim subtraction As Integer = 100
Sub Main()
If (DoSubtraction() > 0) OrElse (DoSum() > 0) Then
Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)
End If
sum = -5
If (DoSubtraction() < 0) AndAlso (DoSum() < 0) Then
Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)
Else
Console.WriteLine("Valor da subtracao das variaveis = {0:d}", sum - subtraction)
End If
End Sub
Function DoSubtraction() As Integer
subtraction -= sum
DoSubtraction = subtraction
Exit Function
End Function
Function DoSum() As Integer
sum += subtraction
DoSum = sum
Exit Function
End Function
End Module
Intermediate Language do código VB.NET usando os operadores que resolvem a expressão como em C# :
.method /*06000012*/ public static void Main() cil managed
// SIG: 00 00 01
{
.entrypoint
.custom /*0C000045:0A00001F*/ instance void [mscorlib/*23000001*/]System.STAThreadAttribute/*0100001A*/::.ctor() /* 0A00001F */ = ( 01 00 00 00 )
// Method begins at RVA 0x22d4
// Code size 144 (0x90)
.maxstack 3
.locals /*1100000C*/ init ([0] bool VB$CG$t_bool$S0)
.language '{3A12D0B8-C26C-11D0-B442-00A0244A1DD2}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
// Source File 'C:\Development\My Tools\BLOG Articles\Article #7\CSharp\Solution\Module1.vb'
.line 5,5 : 2,12 'C:\\Development\\My Tools\\BLOG Articles\\Article #7\\CSharp\\Solution\\Module1.vb'
//000005: Sub Main()
IL_0000: /* 00 | */ nop
.line 6,6 : 3,53 ''
//000006: If (DoSubtraction() > 0) OrElse (DoSum() > 0) Then
IL_0001: /* 28 | (06)000013 */ call int32 Solution.Module1/*02000007*/::DoSubtraction() /* 06000013 */
IL_0006: /* 16 | */ ldc.i4.0
IL_0007: /* 30 | 0B */ bgt.s IL_0014
IL_0009: /* 28 | (06)000014 */ call int32 Solution.Module1/*02000007*/::DoSum() /* 06000014 */
IL_000e: /* 16 | */ ldc.i4.0
IL_000f: /* 30 | 03 */ bgt.s IL_0014
IL_0011: /* 16 | */ ldc.i4.0
IL_0012: /* 2B | 01 */ br.s IL_0015
IL_0014: /* 17 | */ ldc.i4.1
IL_0015: /* 0A | */ stloc.0
IL_0016: /* 06 | */ ldloc.0
IL_0017: /* 2C | 1B */ brfalse.s IL_0034
.line 7,7 : 7,82 ''
//000007: Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)
IL_0019: /* 72 | (70)000001 */ ldstr "Valor da soma das variaveis = {0:d}" /* 70000001 */
IL_001e: /* 7E | (04)000006 */ ldsfld int32 Solution.Module1/*02000007*/::sum /* 04000006 */
IL_0023: /* 7E | (04)000007 */ ldsfld int32 Solution.Module1/*02000007*/::subtraction /* 04000007 */
IL_0028: /* D6 | */ add.ovf
IL_0029: /* 8C | (01)000018 */ box [mscorlib/*23000001*/]System.Int32/*01000018*/
IL_002e: /* 28 | (0A)00001E */ call void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,
object) /* 0A00001E */
IL_0033: /* 00 | */ nop
.line 8,8 : 3,9 ''
//000008: End If
IL_0034: /* 00 | */ nop
.line 10,10 : 3,11 ''
//000009:
//000010: sum = -5
IL_0035: /* 1F | FB */ ldc.i4.s -5
IL_0037: /* 80 | (04)000006 */ stsfld int32 Solution.Module1/*02000007*/::sum /* 04000006 */
.line 12,12 : 3,54 ''
//000011:
//000012: If (DoSubtraction() < 0) AndAlso (DoSum() < 0) Then
IL_003c: /* 28 | (06)000013 */ call int32 Solution.Module1/*02000007*/::DoSubtraction() /* 06000013 */
IL_0041: /* 16 | */ ldc.i4.0
IL_0042: /* 2F | 08 */ bge.s IL_004c
IL_0044: /* 28 | (06)000014 */ call int32 Solution.Module1/*02000007*/::DoSum() /* 06000014 */
IL_0049: /* 16 | */ ldc.i4.0
IL_004a: /* 32 | 03 */ blt.s IL_004f
IL_004c: /* 16 | */ ldc.i4.0
IL_004d: /* 2B | 01 */ br.s IL_0050
IL_004f: /* 17 | */ ldc.i4.1
IL_0050: /* 0A | */ stloc.0
IL_0051: /* 06 | */ ldloc.0
IL_0052: /* 2C | 1D */ brfalse.s IL_0071
.line 13,13 : 5,80 ''
//000013: Console.WriteLine("Valor da soma das variaveis = {0:d}", sum + subtraction)
IL_0054: /* 72 | (70)000001 */ ldstr "Valor da soma das variaveis = {0:d}" /* 70000001 */
IL_0059: /* 7E | (04)000006 */ ldsfld int32 Solution.Module1/*02000007*/::sum /* 04000006 */
IL_005e: /* 7E | (04)000007 */ ldsfld int32 Solution.Module1/*02000007*/::subtraction /* 04000007 */
IL_0063: /* D6 | */ add.ovf
IL_0064: /* 8C | (01)000018 */ box [mscorlib/*23000001*/]System.Int32/*01000018*/
IL_0069: /* 28 | (0A)00001E */ call void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,
object) /* 0A00001E */
IL_006e: /* 00 | */ nop
IL_006f: /* 2B | 1C */ br.s IL_008d
.line 14,14 : 3,7 ''
//000014: Else
IL_0071: /* 00 | */ nop
.line 15,15 : 4,84 ''
//000015: Console.WriteLine("Valor da subtracao das variaveis = {0:d}", sum - subtraction)
IL_0072: /* 72 | (70)000049 */ ldstr "Valor da subtracao das variaveis = {0:d}" /* 70000049 */
IL_0077: /* 7E | (04)000006 */ ldsfld int32 Solution.Module1/*02000007*/::sum /* 04000006 */
IL_007c: /* 7E | (04)000007 */ ldsfld int32 Solution.Module1/*02000007*/::subtraction /* 04000007 */
IL_0081: /* DA | */ sub.ovf
IL_0082: /* 8C | (01)000018 */ box [mscorlib/*23000001*/]System.Int32/*01000018*/
IL_0087: /* 28 | (0A)00001E */ call void [mscorlib/*23000001*/]System.Console/*01000019*/::WriteLine(string,
object) /* 0A00001E */
IL_008c: /* 00 | */ nop
.line 16,16 : 3,9 ''
//000016: End If
IL_008d: /* 00 | */ nop
.line 18,18 : 2,9 ''
//000017:
//000018: End Sub
IL_008e: /* 00 | */ nop
IL_008f: /* 2A | */ ret
} // end of method Module1::Main
Comparando o código IL da solução em VB.NET ou do código C# contra o código original VB.NET notamos, em vermelho acima, os seguintes mnemônicos:
OR VB.NET original C# Solução em VB.NET
cgt bgt bgt
or
AND VB.NET original C# Solução em VB.NET
clt bge bge
and
A principal diferença é que o código C# e a solução em VB.NET usam instruções de branch, ou seja, instruções IL que mudam o fluxo de execução de acordo com uma condição. Os comandos de branch são identificados pela letra b inicial. No caso temos:
bgt = branch if greater than
bge = branch if greater or equal
Ainda olhando o IL notamos que há desvios baseados em condições que fazem com que a segunda comparação dos if() possa ou não ser executada.
E olhando o código VB.NET da solução original, tanto no fragmento usando OR quanto no fragmento usando AND notamos que não há desvios e que ambas condições de cada comparação são sempre executadas!
Note que o comando clt e cgt iniciam com c, são comandos de check, ou seja, eles não desviam o fluxo de execução como um branch apenas colocam a condição da checagem na pilha.
Se você quiser entender o código disassemblado em IL, eis uma boa referência:
https://msdn2.microsoft.com/en-us/library/system.reflection.emit(VS.80).aspx
Embora esse seja um problema simples com uma solução conhecida e documentada, ter conhecimento dessa particularidade dos operadores pode salvá-lo de bugs difíceis de serem detectados.
Além disso, AndAlso e OrElse geram um código mais otimizado, portanto, é preferível usar eles ao invés dos correspondentes And e Or em VB.NET.