C-Sharp | Java | Python | Swift | GO | WPF | Ruby | Scala | F# | JavaScript | SQL | PHP | Angular | HTML
Tuple and KeyValuePair have different performance. You wonder which is more efficient: the Tuple type with two items, or the KeyValuePair type with those same two items. We benchmark these two collections in a variety of program contexts.
Tuple/KeyValuePair performance results Tuple<string, string> KeyValuePair<string, string> 8.23 ns -- Allocate Tuple 0.32 ns -- Allocate KeyValuePair 1.93 ns -- Pass Tuple as argument 2.57 ns -- Pass KeyValuePair as argument 1.91 ns -- Return Tuple 6.09 ns -- Return KeyValuePair 2.79 ns -- Load Tuple from List 4.18 ns -- Load KeyValuePair from List
Benchmark. We see four separate tests. The tests use different numbers of iterations to test the types, but the average time in nanoseconds for each operation is computed. Let's look at each test with a short description.
Allocation: In this test, one instance of a Tuple and a KeyValuePair is allocated. The new-keyword is used.
Argument: Here, one instance is passed as an argument to another (not inlined) method.
Return: In this test, one instance is passed as an argument to a method and then returned.
Load: In this method, one instance is loaded from a reference stored in a List collection.
C# program that benchmarks Tuple/KeyValuePair using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; class Program { static void Main() { Allocation(); Argument(); Return(); Load(); Console.Read(); } static void Allocation() { const int max = 1000000; var a = new Tuple<string, string>("", ""); var b = new KeyValuePair<string, string>("", ""); var s1 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { var tuple = new Tuple<string, string>("cat", "dog"); } s1.Stop(); var s2 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { var pair = new KeyValuePair<string, string>("cat", "dog"); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(); } static void Argument() { const int max = 10000000; var a = new Tuple<string, string>("", ""); var b = new KeyValuePair<string, string>("", ""); X(a); X(b); var s1 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { X(a); } s1.Stop(); var s2 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { X(b); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(); } static void Return() { const int max = 10000000; var a = new Tuple<string, string>("", ""); var b = new KeyValuePair<string, string>("", ""); Y(a); Y(b); var s1 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { Y(a); } s1.Stop(); var s2 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { Y(b); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(); } static void Load() { const int max = 10000000; var a = new Tuple<string, string>("cat", "dog"); var b = new KeyValuePair<string, string>("cat", "dog"); List<Tuple<string, string>> list1 = new List<Tuple<string, string>>(); list1.Add(a); Z(list1); List<KeyValuePair<string, string>> list2 = new List<KeyValuePair<string, string>>(); list2.Add(b); Z(list2); var s1 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { Z(list1); } s1.Stop(); var s2 = Stopwatch.StartNew(); for (int i = 0; i < max; i++) { Z(list2); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / max).ToString("0.00 ns")); Console.WriteLine(); } [MethodImpl(MethodImplOptions.NoInlining)] static void X(Tuple<string, string> a) { } [MethodImpl(MethodImplOptions.NoInlining)] static void X(KeyValuePair<string, string> a) { } [MethodImpl(MethodImplOptions.NoInlining)] static Tuple<string, string> Y(Tuple<string, string> a) { return a; } [MethodImpl(MethodImplOptions.NoInlining)] static KeyValuePair<string, string> Y(KeyValuePair<string, string> a) { return a; } static char Z(List<Tuple<string, string>> list) { return list[0].Item1[0]; } static char Z(List<KeyValuePair<string, string>> list) { return list[0].Key[0]; } } Output 8.23 ns 0.32 ns 1.93 ns 2.57 ns 1.91 ns 6.09 ns 2.79 ns 4.18 ns
Discussion. Tuple was faster in every test than KeyValuePair except in allocation performance. Therefore, if your program does any work beyond allocating the collections, it is a better idea to use Tuple instead of KeyValuePair.
And: The nanoseconds lost from allocating a Tuple could be quickly made up after a few function calls with a Tuple argument.
Using structs (such as KeyValuePair) is usually a bad idea. While structs are faster to allocate, they slow down many other important parts of your program. With structs, simple method calls suddenly become many times slower.
And: This is because they are value types and must be copied on every invocation.
Struct Versus ClassValueType Examples: Int, DateTime
Summary. We compared the Tuple type to the KeyValuePair type in a collection that has two elements. The KeyValuePair was faster to allocate. But the Tuple was faster in every other test. This is an important performance result.
Therefore: I recommend avoiding KeyValuePair entirely and using Tuple or custom classes (which would be similar to Tuple in performance).