Search code examples
c#performance.net-coretypesvaluetuple

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.


Solution

  • 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