Last active
May 31, 2020 07:57
-
-
Save zpqrtbnk/277b9ad0e4fa960ef9be174ff9760eb3 to your computer and use it in GitHub Desktop.
ValueTuple Benchmark
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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