C# Tuple Versus KeyValuePair

Tuple: type name

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—from allocation to actual usage.

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

Performance optimization

This program benchmarks the Tuple type against the KeyValuePair type in 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 One instance is allocated.

Argument One instance is passed as an argument to another (not inlined) method.

Return One instance is passed as an argument to a method and then returned.

Load One instance is loaded from a referenced stored in a List.

Program that benchmarks Tuple/KeyValuePair [C#]

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

This section provides information

The Tuple type was faster in every test than the KeyValuePair except in allocation performance. Therefore, in my opinion, if your program does any significant work beyond allocating the collections, it is almost certainly a better idea to use Tuple instead of KeyValuePair. You can see that the nanoseconds lost from allocating a Tuple could be quickly made up after a few function calls you pass the Tuple as an argument to.

Tuple Tips KeyValuePair Hints

This test was useful to me because it provides yet more evidence that 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. This is because they are value types and must be copied on every invocation.

Struct Versus Class ValueType

Summary

The C# programming language

We compared the Tuple type to the KeyValuePair type in a collection that has two elements. The KeyValuePair was faster to allocate. The Tuple was faster in every other test. In most real-world programs, I would recommend avoiding KeyValuePair entirely and using Tuple or custom classes (which would be similar to Tuple in performance).

Collections
.NET