Performance of the different ways to swap two values (2024)

Swapping values is probably one of the simplest algorithms which can be imagined - we learn about it when starting our programming story. There are two popular ways to accomplish this: using a temporary variable and XORing (with some restrictions). In the newest C# versions there is also a third way, about which you can read in this article.

Theory

A few weeks ago, one of my friends sent me this tweet from which we will start:

(and yes, this works, for both value types and reference types, it's not a sh*tpost I promise~)

it defines a tuple on the right, then immediately deconstructs it into the already defined a and b variableshttps://t.co/i0UeEyBfZA

— Freya 🔜✈️ Guadalindie, Málaga, 2-5th May (@FreyaHolmer) July 14, 2020

What do we see here is the usage of the value tuples syntax (introduced in the C# 7) to swap two values. As the description says, first we create a tuple with our two variables (on the right side of the assignment) and then it’s instantly deconstructed into the second tuple (on the left side of the assignment) but in reversed order. It was quite surprising as I never thought about using tuples this way, but at the same time, I was curious if this solution has some performance overhead.

On the one side, we use here ValueTuple struct, so it’s not an overhead for garbage collector - values are stored on the stack and will be just wiped after exiting from the method. On the other hand, we don’t know if the compiler will generate some extra IL instructions compared to the traditional swap using a temporary variable. Let’s check it on the simple example.

Benchmark

As in the previous articles, I used BenchmarkDotNet library which is extremally handful when measuring performance and checking output generated by the JIT. Our benchmark is very simple and swaps two variables utilizing three different methods: through temporary variable, through XOR operations, and tuples (like on the tweet above). Additional assignments on the beginning and the end of each test are made to ensure, that compiler/JIT will make these operations using registers to maximize performance.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Jobs;using BenchmarkDotNet.Running;namespace SwapBenchmark{ [DisassemblyDiagnoser] [SimpleJob(RuntimeMoniker.NetCoreApp31)] public class SwapTest { private static volatile int A = 10; private static volatile int B = 20; [Benchmark] public void SwapTraditional() { var a = A; var b = B; var temp = a; a = b; b = temp; A = a; B = b; } [Benchmark] public void SwapXor() { var a = A; var b = B; a ^= b; b ^= a; a ^= b; A = a; B = b; } [Benchmark] public void SwapTuples() { var a = A; var b = B; (a, b) = (b, a); A = a; B = b; } } class Program { public static void Main(string[] args) { BenchmarkRunner.Run<SwapTest>(); } }}

Let’s check the IL output first. Our first method SwapTraditional is straightforward and in fact uses only four instructions:

  • ldsfld - loads static field onto the stack.
  • stsfld - pops variable from the stack and stores it into the static field.
  • ldloc.x - loads local variable and stores it onto the stack at the specified index (x).
  • stloc.x - pops variable from the stack and stores it into the local variables list at the specified index (x).
 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243
 .method public hidebysig instance void SwapTraditional() cil managed { .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor() = (01 00 00 00 ) .maxstack 2 .locals init ( [0] int32 a, [1] int32 temp ) // [17 13 - 17 23] IL_0000: volatile. IL_0002: ldsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::A IL_0007: stloc.0 // a // [18 13 - 18 23] IL_0008: volatile. IL_000a: ldsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::B // [20 13 - 20 26] IL_000f: ldloc.0 // a IL_0010: stloc.1 // temp // [21 13 - 21 19] IL_0011: stloc.0 // a // [22 13 - 22 22] IL_0012: ldloc.1 // temp // [24 13 - 24 19] IL_0013: ldloc.0 // a IL_0014: volatile. IL_0016: stsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::A // [25 13 - 25 19] IL_001b: volatile. IL_001d: stsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::B // [26 9 - 26 10] IL_0022: ret } // end of method SwapTest::SwapTraditional

We can see the sequence of the following operations: first, the compiler loads static field A onto the stack and then pops it into the local variable a. Static field B is also loaded in a similar way, but it stays in the stack. Then, the local variable a is loaded onto the stack and immediately popped into the temp. Just after it, the value of the static field B (which has been loaded earlier) is assigned to the a. At this point, the local variable a contains value of the static field B, and the local variable temp contains the value of the a. The last operations are loading both of them onto the stack and popping into A and B.

Now time for the second test, which uses XOR operations. The compiler uses here the new instruction xor which does exactly what the name indicates - pops two variables from the stack, XORs them and stores the result again on the stack.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
 .method public hidebysig instance void SwapXor() cil managed { .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor() = (01 00 00 00 ) .maxstack 2 .locals init ( [0] int32 a, [1] int32 b ) // [31 13 - 31 23] IL_0000: volatile. IL_0002: ldsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::A IL_0007: stloc.0 // a // [32 13 - 32 23] IL_0008: volatile. IL_000a: ldsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::B IL_000f: stloc.1 // b // [34 13 - 34 20] IL_0010: ldloc.0 // a IL_0011: ldloc.1 // b IL_0012: xor IL_0013: stloc.0 // a // [35 13 - 35 20] IL_0014: ldloc.1 // b IL_0015: ldloc.0 // a IL_0016: xor IL_0017: stloc.1 // b // [36 13 - 36 20] IL_0018: ldloc.0 // a IL_0019: ldloc.1 // b IL_001a: xor IL_001b: stloc.0 // a // [38 13 - 38 19] IL_001c: ldloc.0 // a IL_001d: volatile. IL_001f: stsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::A // [39 13 - 39 19] IL_0024: ldloc.1 // b IL_0025: volatile. IL_0027: stsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::B // [40 9 - 40 10] IL_002c: ret } // end of method SwapTest::SwapXor

The thrid test is finally our tuple-based swap. Surprisingly, it’s quite similar to the first one (where the temporary variable was used). First, both static fields A and B are loaded into the local variables a and b, then we have a series of ldloc and stloc instructions which also use a temporary variable called V_2 to swap values.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344
 .method public hidebysig instance void SwapTuples() cil managed { .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor() = (01 00 00 00 ) .maxstack 2 .locals init ( [0] int32 a, [1] int32 b, [2] int32 V_2 ) // [45 13 - 45 23] IL_0000: volatile. IL_0002: ldsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::A IL_0007: stloc.0 // a // [46 13 - 46 23] IL_0008: volatile. IL_000a: ldsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::B IL_000f: stloc.1 // b // [48 13 - 48 29] IL_0010: ldloc.1 // b IL_0011: ldloc.0 // a IL_0012: stloc.2 // V_2 IL_0013: stloc.0 // a IL_0014: ldloc.2 // V_2 IL_0015: stloc.1 // b // [50 13 - 50 19] IL_0016: ldloc.0 // a IL_0017: volatile. IL_0019: stsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::A // [51 13 - 51 19] IL_001e: ldloc.1 // b IL_001f: volatile. IL_0021: stsfld int32 modreq ([System.Runtime]System.Runtime.CompilerServices.IsVolatile) SwapBenchmark.SwapTest::B // [52 9 - 52 10] IL_0026: ret } // end of method SwapTest::SwapTuples

In theory, this method is a bit slower because utilizes more IL instructions than in the first example (5 ldloc/5 stloc vs 3 ldloc/3 stloc). Will this affect the final output generated by the JIT? The answer is: no!

 1 2 3 4 5 6 7 8 91011121314151617181920212223
; SwapBenchmark.SwapTest.SwapTraditional() mov eax,[7FFC4CC5B7AC] mov edx,[7FFC4CC5B7B0] mov [7FFC4CC5B7AC],edx mov [7FFC4CC5B7B0],eax ret; SwapBenchmark.SwapTest.SwapXor() mov eax,[7FFC4CC2B7AC] mov edx,[7FFC4CC2B7B0] xor eax,edx xor edx,eax xor eax,edx mov [7FFC4CC2B7AC],eax mov [7FFC4CC2B7B0],edx ret; SwapBenchmark.SwapTest.SwapTuples() mov eax,[7FFC4CC4B7AC] mov edx,[7FFC4CC4B7B0] mov [7FFC4CC4B7AC],edx mov [7FFC4CC4B7B0],eax ret

Both first and third tests have exactly the same outputs - we can see that they have been optimized to use fast registers instead of using a slower stack. So the most important conclusion is: using tuples to swap values is exactly as fast as using a temporary variable.

The method which uses XOR operations is a bit more complex - three additional xor instructions won’t speed up the whole process for sure. In the next chapter, we will do a more life example with a real algorithm to check the times.

Bubble sort

One of the best algorithms to test the execution time of swaps is a bubble sort, so we will use it to check the performance of our three methods. In this benchmark, there are three tests where each performs a sort for the array with 10000 elements (initialized with values from 10000 to 1 to get the most pessimistic case).

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
using System.Linq;using BenchmarkDotNet.Attributes;using BenchmarkDotNet.Jobs;using BenchmarkDotNet.Running;namespace BubbleSortSwapBenchmark{ [DisassemblyDiagnoser] [SimpleJob(RuntimeMoniker.NetCoreApp31)] public class BubbleSortSwapTest { private const int N = 10000; [Benchmark] public void BubbleSortSwapTraditional() { var array = Enumerable.Range(0, N).Reverse().ToArray(); var sorted = false; while (!sorted) { sorted = true; for (var i = 0; i < array.Length - 1; i++) { if (array[i] > array[i + 1]) { var temp = array[i]; array[i] = array[i + 1]; array[i + 1] = temp; sorted = false; } } } } [Benchmark] public void BubbleSortSwapXor() { var array = Enumerable.Range(0, N).Reverse().ToArray(); var sorted = false; while (!sorted) { sorted = true; for (var i = 0; i < array.Length - 1; i++) { if (array[i] > array[i + 1]) { array[i] ^= array[i + 1]; array[i + 1] ^= array[i]; array[i] ^= array[i + 1]; sorted = false; } } } } [Benchmark] public void BubbleSortSwapTuples() { var array = Enumerable.Range(0, N).Reverse().ToArray(); var sorted = false; while (!sorted) { sorted = true; for (var i = 0; i < array.Length - 1; i++) { if (array[i] > array[i + 1]) { (array[i], array[i + 1]) = (array[i + 1], array[i]); sorted = false; } } } } } class Program { public static void Main(string[] args) { BenchmarkRunner.Run<BubbleSortSwapTest>(); } }}
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2)Intel Core i5-8300H CPU 2.30GHz (Coffee Lake), 1 CPU, 8 logical and 4 physical cores.NET Core SDK=5.0.100-preview.5.20279.10 [Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT .NET Core 3.1 : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJITJob=.NET Core 3.1 Runtime=.NET Core 3.1
MethodMeanErrorStdDevCode Size
BubbleSortSwapTraditional91.88 ms0.355 ms0.315 ms522 B
BubbleSortSwapXor133.82 ms0.668 ms0.624 ms540 B
BubbleSortSwapTuples91.33 ms0.359 ms0.336 ms528 B

The results are clear and valid with our earlier observations: both traditional and tuple-based swaps have exactly the same performance (differences in the mean time are within the error margin).

Summary

Sometimes, it’s not easy to tell if some solution has performance overhead, especially when we think about JIT with its complex optimization processes. As we saw in this article, a tuple-based algorithm is an excellent way to swap two elements, providing better readability and the same performance as the traditional algorithm using a temporary variable. Definitely worth to try and use in our projects.

Performance of the different ways to swap two values (2024)

FAQs

What is the best way to swap values between two variables? ›

The simplest way to swap two variables is to use a third variable as temporary storage: Object a, b; Object temp; temp = a; a = b; b = temp; This method is particularly easy to read and understand, even for beginners.

Can you exchange two values in different variables? ›

Swapping the values of two variables is a common task in programming. While the most straightforward approach involves using a temporary variable, Python provides several elegant techniques to achieve the same result without the need for that extra variable.

What is the basic concept behind swapping values in two variables? ›

Explanation. We swap two numbers by storing the value of one number in a temporary variable, assigning the value of the second number to the first number, and then assigning the value stored in the temporary variable to the second number.

What is an example of a swap? ›

For example, a company paying a variable rate of interest may swap its interest payments with another company that will then pay the first company a fixed rate. Swaps can also be used to exchange other kinds of value or risk like the potential for a credit default in a bond.

How to swap two values without using third variable? ›

We can swap two numbers without using third variable using Bitwise XOR Operator. Let's first see an example of an XOR operation on two binary numbers. a = 3 => 0011 in binary. b = 5 => 0101 in binary.

What does it mean to swap two numbers? ›

Swapping two numbers means exchanging the values of two variables with each other.

What is swapping values? ›

Places the value of the y input into the x' output and the value of the x input into the y' output without allocating memory to perform the operation.

What do you understand by swapping? ›

1. to trade or exchange (something or someone) for another. noun. 2. an exchange.

What is the concept of swapping? ›

swapping is a memory management technique for swapping data between main memory and secondary memory for better memory utilization. This article explains swapping concept in detail with real-life example. Swapping is a memory management technique that can be used to increase the operating system's performance.

How to write a swap function? ›

The syntax of the swap function is swap(a,b). The swap C++ function takes two variables as parameters. The value of the variables passed as the parameter will be swapped. The swap C++ swaps the value of the variables passed as the parameter.

How do you swap two variables on one line? ›

Two variables can be swapped in one line in Java. This is done by using the given statement. x = x ^ y ^ (y = x);

When two variables change in? ›

Answer. Explanation: The term that describes when two variables change in constant proportion is "linear correlation".

What is the fastest way to swap two variables in c? ›

Most Quickest way to swap two numbers is by using pre-defined swap(int, int) function in c and c++, even you don't have to include any other header file in your code to call this function.

How to swap values of 2 variables without using third variable? ›

The bitwise XOR operator can be used to swap two variables. The XOR of two numbers x and y returns a number which has all the bits as 1 wherever bits of x and y differ. For example, XOR of 10 (In Binary 1010) and 5 (In Binary 0101) is 1111 and XOR of 7 (0111) and 5 (0101) is (0010).

References

Top Articles
Latest Posts
Article information

Author: Tuan Roob DDS

Last Updated:

Views: 6419

Rating: 4.1 / 5 (42 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Tuan Roob DDS

Birthday: 1999-11-20

Address: Suite 592 642 Pfannerstill Island, South Keila, LA 74970-3076

Phone: +9617721773649

Job: Marketing Producer

Hobby: Skydiving, Flag Football, Knitting, Running, Lego building, Hunting, Juggling

Introduction: My name is Tuan Roob DDS, I am a friendly, good, energetic, faithful, fantastic, gentle, enchanting person who loves writing and wants to share my knowledge and understanding with you.