Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance penalties to deconstructing tuples in c#?

If we do

var (hello, world) = GetHelloAndWorldStrings();
if (hello == "hello" && world == "world")
  Environment.Exit(0);

Does that incur any additional costs than just doing the following:

var helloAndWorld = GetHelloAndWorldStrings();
if (helloAndWorld.Hello == "hello" && helloAndWorld.World == "world")
  Environment.Exit(0);

Or is it all syntatic sugar - the code that ends up being generated always uses Item1 and Item2.

like image 703
SpiritBob Avatar asked Aug 19 '20 07:08

SpiritBob


2 Answers

They are effectively the same generated IL with only minor emitted differences, However... they would likely be jitted and optimized to exactly the same instructions.

Given

private (String hello,string world) GetHelloAndWorldStrings() 
{ 
   return ("asdsd","sadfsdf");
}
    
...
        

public int Test1()
{
   var asd = GetHelloAndWorldStrings();
   if (asd.hello == "hello" && asd.world == "world")
      return 1;
   return 0;
}

public int Test2()
{
   var (hello, world) = GetHelloAndWorldStrings();
   if (hello == "hello" && world == "world")
      return 1;
   return 0;
}

Would basically be emitted as

public int Test1()
{
    ValueTuple<string, string> helloAndWorldStrings = GetHelloAndWorldStrings();
    if (helloAndWorldStrings.Item1 == "hello" && helloAndWorldStrings.Item2 == "world")
    {
        return 1;
    }
    return 0;
}

public int Test2()
{
    ValueTuple<string, string> helloAndWorldStrings = GetHelloAndWorldStrings();
    string item = helloAndWorldStrings.Item1;
    string item2 = helloAndWorldStrings.Item2;
    if (item == "hello" && item2 == "world")
    {
        return 1;
    }
    return 0;
}

You can check the IL out here

Here is an example of the JIT ASM in release

C.Test1()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: push esi
    L0004: mov ecx, [0x11198648]
    L000a: mov esi, [0x1119864c]
    L0010: mov edx, [0x11198650]
    L0016: call System.String.Equals(System.String, System.String)
    L001b: test eax, eax
    L001d: je short L0038
    L001f: mov edx, [0x11198654]
    L0025: mov ecx, esi
    L0027: call System.String.Equals(System.String, System.String)
    L002c: test eax, eax
    L002e: je short L0038
    L0030: mov eax, 1
    L0035: pop esi
    L0036: pop ebp
    L0037: ret
    L0038: xor eax, eax
    L003a: pop esi
    L003b: pop ebp
    L003c: ret

vs

C.Test2()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: push esi
    L0004: mov ecx, [0x11198648]
    L000a: mov esi, [0x1119864c]
    L0010: mov edx, [0x11198650]
    L0016: call System.String.Equals(System.String, System.String)
    L001b: test eax, eax
    L001d: je short L0038
    L001f: mov edx, [0x11198654]
    L0025: mov ecx, esi
    L0027: call System.String.Equals(System.String, System.String)
    L002c: test eax, eax
    L002e: je short L0038
    L0030: mov eax, 1
    L0035: pop esi
    L0036: pop ebp
    L0037: ret
    L0038: xor eax, eax
    L003a: pop esi
    L003b: pop ebp
    L003c: ret

In short, the net gain of worrying about this.. minus 5 minutes of your life you'll never get back

like image 85
TheGeneral Avatar answered Sep 22 '22 09:09

TheGeneral


There's a third option that is equivalent to the first:

  public int Test3()
  {
     var asd = GetHelloAndWorldStrings();
     if (asd == ("hello", "world"))
        return 1;
     return 0;
  }

It equally thranslates to (sharplab.io):

public int Test3()
{
    ValueTuple<string, string> helloAndWorldStrings = GetHelloAndWorldStrings();
    if (helloAndWorldStrings.Item1 == "hello" && helloAndWorldStrings.Item2 == "world")
    {
        return 1;
    }
    return 0;
}

(sharplab.io)

.method public hidebysig 
    instance int32 Test3 () cil managed 
{
    // Method begins at RVA 0x2064
    // Code size 47 (0x2f)
    .maxstack 2
    .locals init (
        [0] valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string>
    )

    IL_0000: ldarg.0
    IL_0001: call instance valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string> C::GetHelloAndWorldStrings()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: ldfld !0 valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string>::Item1
    IL_000d: ldstr "hello"
    IL_0012: call bool [System.Private.CoreLib]System.String::op_Equality(string, string)
    IL_0017: brfalse.s IL_002d

    IL_0019: ldloc.0
    IL_001a: ldfld !1 valuetype [System.Private.CoreLib]System.ValueTuple`2<string, string>::Item2
    IL_001f: ldstr "world"
    IL_0024: call bool [System.Private.CoreLib]System.String::op_Equality(string, string)
    IL_0029: brfalse.s IL_002d

    IL_002b: ldc.i4.1
    IL_002c: ret

    IL_002d: ldc.i4.0
    IL_002e: ret
} // end of method C::Test3
like image 31
Paulo Morgado Avatar answered Sep 24 '22 09:09

Paulo Morgado