Skip to content

Instantly share code, notes, and snippets.

@zpqrtbnk
Last active May 31, 2020 07:57
Show Gist options
  • Save zpqrtbnk/277b9ad0e4fa960ef9be174ff9760eb3 to your computer and use it in GitHub Desktop.
Save zpqrtbnk/277b9ad0e4fa960ef9be174ff9760eb3 to your computer and use it in GitHub Desktop.
ValueTuple Benchmark
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Validators;
// here are the results I get:
//
// Method | Job | Jit | Platform | LaunchCount | Mean | StdErr | StdDev | Scaled | Scaled-StdDev | Gen 0 | Allocated |
// ------------------------ |---------- |---------- |--------- |------------ |----------- |---------- |---------- |------- |-------------- |---------- |---------- |
// ValueTuple | Default | LegacyJit | X86 | 1 | 49.9423 ms | 0.4863 ms | 1.9451 ms | 1.00 | 0.00 | 783.3333 | 6.24 MB |
// DirectCompositeKey | Default | LegacyJit | X86 | 1 | 42.5161 ms | 0.4235 ms | 2.0749 ms | 0.85 | 0.05 | 1125.0000 | 7.84 MB |
// OfCompositeKey | Default | LegacyJit | X86 | 1 | 41.5015 ms | 0.0773 ms | 0.2786 ms | 0.83 | 0.03 | 1346.1538 | 7.84 MB |
// OfInnerCompositeKey | Default | LegacyJit | X86 | 1 | 39.2377 ms | 0.2115 ms | 0.7912 ms | 0.79 | 0.03 | 1058.3333 | 7.84 MB |
// OfEquatableCompositeKey | Default | LegacyJit | X86 | 1 | 38.2334 ms | 0.1296 ms | 0.4849 ms | 0.77 | 0.03 | 833.3333 | 6.24 MB |
// ValueTuple | RyuJitX64 | RyuJit | X64 | Default | 40.9851 ms | 0.1817 ms | 0.6800 ms | 1.00 | 0.00 | 1360.2941 | 9.04 MB |
// DirectCompositeKey | RyuJitX64 | RyuJit | X64 | Default | 33.2560 ms | 0.1137 ms | 0.4254 ms | 0.81 | 0.02 | 2137.5000 | 12.24 MB |
// OfCompositeKey | RyuJitX64 | RyuJit | X64 | Default | 34.0799 ms | 0.4368 ms | 1.7473 ms | 0.83 | 0.04 | 2070.8333 | 12.24 MB |
// OfInnerCompositeKey | RyuJitX64 | RyuJit | X64 | Default | 33.1676 ms | 0.1328 ms | 0.4969 ms | 0.81 | 0.02 | 2137.5000 | 12.24 MB |
// OfEquatableCompositeKey | RyuJitX64 | RyuJit | X64 | Default | 31.4083 ms | 0.1895 ms | 0.6833 ms | 0.77 | 0.02 | 1446.4286 | 9.04 MB |
//
//
// note
// source for ValueTuple: http://source.roslyn.io/#microsoft.codeanalysis/InternalUtilities/ValueTuple%25602.cs,e55f0b8265181ab3
namespace Bench
{
[Config(typeof(Config))]
public class ValueTuple3Benchmark
{
private class Config : ManualConfig
{
public Config()
{
Add(Job.Clr.WithLaunchCount(1));
Add(new MemoryDiagnoser());
Add(JitOptimizationsValidator.FailOnError);
Add(Job.RyuJitX64);
}
}
private struct CompositeKey
{
private readonly string _stringKey;
private readonly int _intKey;
public CompositeKey(string stringKey, int intKey)
{
_stringKey = stringKey;
_intKey = intKey;
}
public override bool Equals(object obj)
=> obj is CompositeKey other && _stringKey == other._stringKey && _intKey == other._intKey;
public override int GetHashCode()
=> _stringKey.GetHashCode() * 31 + _intKey;
}
private class DictionaryOfCompositeKey : Dictionary<CompositeKey, int>
{
public bool TryGetValue(string key1, int key2, out int value)
=> TryGetValue(new CompositeKey(key1, key2), out value);
public int this[string key1, int key2]
{
get => this[new CompositeKey(key1, key2)];
set => this[new CompositeKey(key1, key2)] = value;
}
}
public class DictionaryOfInnerCompositeKey : Dictionary<DictionaryOfInnerCompositeKey.CompositeKey, int>
{
public bool TryGetValue(string key1, int key2, out int value)
=> TryGetValue(new CompositeKey(key1, key2), out value);
public int this[string key1, int key2]
{
get => this[new CompositeKey(key1, key2)];
set => this[new CompositeKey(key1, key2)] = value;
}
public struct CompositeKey
{
private readonly string _stringKey;
private readonly int _intKey;
public CompositeKey(string stringKey, int intKey)
{
_stringKey = stringKey;
_intKey = intKey;
}
public override bool Equals(object obj)
=> obj is CompositeKey other && _stringKey == other._stringKey && _intKey == other._intKey;
public override int GetHashCode()
=> _stringKey.GetHashCode() * 31 + _intKey;
//=> Hash.Combine(_stringKey.GetHashCode(), _intKey);
}
}
public class DictionaryOfEquatableCompositeKey : Dictionary<DictionaryOfEquatableCompositeKey.CompositeKey, int>
{
public bool TryGetValue(string key1, int key2, out int value)
=> TryGetValue(new CompositeKey(key1, key2), out value);
public int this[string key1, int key2]
{
get => this[new CompositeKey(key1, key2)];
set => this[new CompositeKey(key1, key2)] = value;
}
public struct CompositeKey : IEquatable<CompositeKey>
{
private readonly string _stringKey;
private readonly int _intKey;
public CompositeKey(string stringKey, int intKey)
{
_stringKey = stringKey;
_intKey = intKey;
}
public bool Equals(CompositeKey other)
=> _stringKey == other._stringKey && _intKey == other._intKey;
public override bool Equals(object obj)
=> obj is CompositeKey other && _stringKey == other._stringKey && _intKey == other._intKey;
public override int GetHashCode()
=> _stringKey.GetHashCode() * 31 + _intKey;
//=> Hash.Combine(_stringKey.GetHashCode(), _intKey);
public static bool operator ==(CompositeKey key1, CompositeKey key2)
{
return key1._stringKey == key2._stringKey && key1._intKey == key2._intKey;
}
public static bool operator !=(CompositeKey key1, CompositeKey key2)
{
return key1._stringKey != key2._stringKey || key1._intKey != key2._intKey;
}
}
}
private readonly Dictionary<(string, int), int> _d1 = new Dictionary<(string, int), int>();
private readonly Dictionary<CompositeKey, int> _d2 = new Dictionary<CompositeKey, int>();
private readonly DictionaryOfCompositeKey _d3 = new DictionaryOfCompositeKey();
private readonly DictionaryOfInnerCompositeKey _d4 = new DictionaryOfInnerCompositeKey();
private readonly DictionaryOfEquatableCompositeKey _d5 = new DictionaryOfEquatableCompositeKey();
[Setup]
public void SetUp()
{
for (var i = 0; i < 100000; i++)
{
_d1[(i.ToString(), i)] = i + 4567;
_d2[new CompositeKey(i.ToString(), i)] = i + 4567;
_d3[i.ToString(), i] = i + 4567;
_d4[i.ToString(), i] = i + 4567;
_d5[i.ToString(), i] = i + 4567;
}
}
[Benchmark(Baseline = true)]
public void ValueTuple()
{
var values = new List<int>();
for (var i = 0; i < 200000; i++)
if (_d1.TryGetValue((i.ToString(), i), out var value))
values.Add(value);
if (values.Count != 100000) throw new Exception();
//IL_000a: ldarg.0 // this
//IL_000b: ldfld class [mscorlib] System.Collections.Generic.Dictionary`2<valuetype[System.ValueTuple] System.ValueTuple`2<string, int32>, int32> Bench.ValueTuple3Benchmark::_d1
//IL_0010: ldloca.s i
//IL_0012: call instance string[mscorlib] System.Int32::ToString()
//IL_0017: ldloc.1 // i
//IL_0018: newobj instance void valuetype [System.ValueTuple]System.ValueTuple`2<string, int32>::.ctor(!0/*string*/, !1/*int32*/)
//IL_001d: ldloca.s 'value'
//IL_001f: callvirt instance bool class [mscorlib] System.Collections.Generic.Dictionary`2<valuetype[System.ValueTuple] System.ValueTuple`2<string, int32>, int32>::TryGetValue(!0/*valuetype [System.ValueTuple]System.ValueTuple`2<string, int32>*/, !1/*int32*/&)
//IL_0024: brfalse.s IL_002d
}
[Benchmark]
public void DirectCompositeKey()
{
var values = new List<int>();
for (var i = 0; i < 200000; i++)
if (_d2.TryGetValue(new CompositeKey(i.ToString(), i), out var value))
values.Add(value);
if (values.Count != 100000) throw new Exception();
//IL_000a: ldarg.0 // this
//IL_000b: ldfld class [mscorlib] System.Collections.Generic.Dictionary`2<valuetype Bench.ValueTuple3Benchmark/CompositeKey, int32> Bench.ValueTuple3Benchmark::_d2
//IL_0010: ldloca.s i
//IL_0012: call instance string[mscorlib] System.Int32::ToString()
//IL_0017: ldloc.1 // i
//IL_0018: newobj instance void Bench.ValueTuple3Benchmark/CompositeKey::.ctor(string, int32)
//IL_001d: ldloca.s 'value'
//IL_001f: callvirt instance bool class [mscorlib] System.Collections.Generic.Dictionary`2<valuetype Bench.ValueTuple3Benchmark/CompositeKey, int32>::TryGetValue(!0/*valuetype Bench.ValueTuple3Benchmark/CompositeKey*/, !1/*int32*/&)
//IL_0024: brfalse.s IL_002d
}
[Benchmark]
public void OfCompositeKey()
{
var values = new List<int>();
for (var i = 0; i < 200000; i++)
if (_d3.TryGetValue(i.ToString(), i, out var value))
values.Add(value);
if (values.Count != 100000) throw new Exception();
//IL_000a: ldarg.0 // this
//IL_000b: ldfld class Bench.ValueTuple3Benchmark/DictionaryOfCompositeKey Bench.ValueTuple3Benchmark::_d3
//IL_0010: ldloca.s i
//IL_0012: call instance string[mscorlib] System.Int32::ToString()
//IL_0017: ldloc.1 // i
//IL_0018: ldloca.s 'value'
//IL_001a: callvirt instance bool Bench.ValueTuple3Benchmark/DictionaryOfCompositeKey::TryGetValue(string, int32, int32&)
//IL_001f: brfalse.s IL_0028
// --
//IL_0000: ldarg.0 // this
//IL_0001: ldarg.1 // key1
//IL_0002: ldarg.2 // key2
//IL_0003: newobj instance void Bench.ValueTuple3Benchmark / CompositeKey::.ctor(string, int32)
//IL_0008: ldarg.3 // 'value'
//IL_0009: call instance bool class [mscorlib] System.Collections.Generic.Dictionary`2<valuetype Bench.ValueTuple3Benchmark/CompositeKey, int32>::TryGetValue(!0/*valuetype Bench.ValueTuple3Benchmark/CompositeKey*/, !1/*int32*/&)
//IL_000e: ret
}
[Benchmark]
public void OfInnerCompositeKey()
{
var values = new List<int>();
for (var i = 0; i < 200000; i++)
if (_d4.TryGetValue(i.ToString(), i, out var value))
values.Add(value);
if (values.Count != 100000) throw new Exception();
//IL_000a: ldarg.0 // this
//IL_000b: ldfld class Bench.ValueTuple3Benchmark/DictionaryOfInnerCompositeKey Bench.ValueTuple3Benchmark::_d4
//IL_0010: ldloca.s i
//IL_0012: call instance string[mscorlib] System.Int32::ToString()
//IL_0017: ldloc.1 // i
//IL_0018: ldloca.s 'value'
//IL_001a: callvirt instance bool Bench.ValueTuple3Benchmark/DictionaryOfInnerCompositeKey::TryGetValue(string, int32, int32&)
//IL_001f: brfalse.s IL_0028
// --
//IL_0000: ldarg.0 // this
//IL_0001: ldarg.1 // key1
//IL_0002: ldarg.2 // key2
//IL_0003: newobj instance void Bench.ValueTuple3Benchmark / DictionaryOfInnerCompositeKey / CompositeKey::.ctor(string, int32)
//IL_0008: ldarg.3 // 'value'
//IL_0009: call instance bool class [mscorlib] System.Collections.Generic.Dictionary`2<valuetype Bench.ValueTuple3Benchmark/DictionaryOfInnerCompositeKey/CompositeKey, int32>::TryGetValue(!0/*valuetype Bench.ValueTuple3Benchmark/DictionaryOfInnerCompositeKey/CompositeKey*/, !1/*int32*/&)
//IL_000e: ret
}
[Benchmark]
public void OfEquatableCompositeKey()
{
var values = new List<int>();
for (var i = 0; i < 200000; i++)
if (_d5.TryGetValue(i.ToString(), i, out var value))
values.Add(value);
if (values.Count != 100000) throw new Exception();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment