Skip to content

Swapping with tuple deconstruction compiles to inferior IL code compared to using a temporary variable #53300

@hamarb123

Description

@hamarb123

Description

Swapping / reordering variables compiles to slower IL code when using tuple destruction than a temporary variable.
I tested this in Release mode.

The following C# code

int x = 1;
int y = 2;
int z = 3;
(x, y)=(y, x);
Console.WriteLine((x,y,z)); //this line is to stop compiling out the variables

should be equivalent in IL output to

int x = 1;
int y = 2;
int z = 3;
int temp = x;
x = y;
y = temp;
Console.WriteLine((x,y,z)); //this line is to stop compiling out the variables

, but the first compiles to:

// Code size       31 (0x1f)
.maxstack  3
.locals init (int32 V_0,
         int32 V_1,
         int32 V_2,
         int32 V_3)
ldc.i4.1
stloc.0
ldc.i4.2
stloc.1
ldc.i4.3
stloc.2
ldloc.1
ldloc.0
stloc.3
stloc.0
ldloc.3
stloc.1
ldloc.0
ldloc.1
ldloc.2
newobj     instance void valuetype [System.Runtime]System.ValueTuple`3<int32,int32,int32>::.ctor(!0,
                                                                                                           !1,
                                                                                                           !2)
box        valuetype [System.Runtime]System.ValueTuple`3<int32,int32,int32>
call       void [System.Console]System.Console::WriteLine(object)
ret

and the second compiles to

// Code size       29 (0x1d)
.maxstack  3
.locals init (int32 V_0,
         int32 V_1,
         int32 V_2)
ldc.i4.1
stloc.0
ldc.i4.2
stloc.1
ldc.i4.3
stloc.2
ldloc.0
ldloc.1
stloc.0
stloc.1
ldloc.0
ldloc.1
ldloc.2
newobj     instance void valuetype [System.Runtime]System.ValueTuple`3<int32,int32,int32>::.ctor(!0,
                                                                                                           !1,
                                                                                                           !2)
box        valuetype [System.Runtime]System.ValueTuple`3<int32,int32,int32>
call       void [System.Console]System.Console::WriteLine(object)
ret

With the essential code being for (1):

ldloc.1
ldloc.0
stloc.3
stloc.0
ldloc.3
stloc.1

, and for (2):

ldloc.0
ldloc.1
stloc.0
stloc.1

I did test this on .NET 5 locally, but also on an online .NET 6 compiler at https://sharplab.io/, which uses a very recent version.
I also imagine that a similar thing happens for other variants, such as:

(x, y) = (x, x);
(x, y, z, w) = (w, y, z, x);

etc.
Surely these destruction assignments should be as simple as ld all of the variables, and then st them all (as long as there's not too many).
Interestingly, the tuple destruction code essentially uses a temporary variable in IL, whereas the temporary variable code eliminates it.
Not sure if this belongs on roslyn or on here.

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions