From 3a571878470b1e8b263d3ddb5373d3db3d29fd3d Mon Sep 17 00:00:00 2001 From: James Lao Date: Sun, 10 Mar 2019 18:41:32 -0700 Subject: [PATCH 1/3] Use fast XorShift RNG. --- Benchmark/RngPerf.cs | 26 +++++++++++++++++ CacheTable/CacheTable.cs | 2 +- CacheTable/CacheTableInternal.cs | 2 +- CacheTable/ConcurrentCacheTable.cs | 8 +++--- CacheTable/XorShiftRandom.cs | 45 ++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 Benchmark/RngPerf.cs create mode 100644 CacheTable/XorShiftRandom.cs diff --git a/Benchmark/RngPerf.cs b/Benchmark/RngPerf.cs new file mode 100644 index 0000000..208ed95 --- /dev/null +++ b/Benchmark/RngPerf.cs @@ -0,0 +1,26 @@ +using BenchmarkDotNet.Attributes; +using CacheTable; +using System; + +namespace Benchmark +{ + [CoreJob] + [RankColumn] + public class RngPerf + { + private readonly Random random = new Random(); + private readonly XorShiftRandom xorshift = new XorShiftRandom(); + + [Benchmark(Baseline = true)] + public int Random() + { + return this.random.Next(12); + } + + [Benchmark] + public int XortShiftRandom() + { + return this.xorshift.Next(12); + } + } +} diff --git a/CacheTable/CacheTable.cs b/CacheTable/CacheTable.cs index da72aac..3ab9775 100644 --- a/CacheTable/CacheTable.cs +++ b/CacheTable/CacheTable.cs @@ -14,7 +14,7 @@ public class CacheTable : ICacheTable { private CacheTableInternal table; private int count; - private readonly Random rng = new Random(); + private readonly XorShiftRandom rng = new XorShiftRandom(); /// /// Creates a set associative cache with specified number of rows and columns. diff --git a/CacheTable/CacheTableInternal.cs b/CacheTable/CacheTableInternal.cs index 0b1f37f..bc0c221 100644 --- a/CacheTable/CacheTableInternal.cs +++ b/CacheTable/CacheTableInternal.cs @@ -106,7 +106,7 @@ public IEnumerator> GetEnumerator() } // Returns true if item was inserted into empty slot. False otherwise. - public bool Set(TKey key, TValue value, int row, Random rng) + public bool Set(TKey key, TValue value, int row, XorShiftRandom rng) { (int rowStart, int rowEnd) = this.GetRowRange(row); int empty = -1; diff --git a/CacheTable/ConcurrentCacheTable.cs b/CacheTable/ConcurrentCacheTable.cs index 91a8a37..5124beb 100644 --- a/CacheTable/ConcurrentCacheTable.cs +++ b/CacheTable/ConcurrentCacheTable.cs @@ -15,7 +15,7 @@ public class ConcurrentCacheTable : ICacheTable { private readonly CacheTableInternal table; private readonly object[] lockObjects; - private readonly Random[] rngs; + private readonly XorShiftRandom[] rngs; private readonly int[] counts; /// @@ -35,10 +35,10 @@ public ConcurrentCacheTable(int rows, int columns, int concurrency) this.lockObjects[i] = new object(); } - this.rngs = new Random[concurrency]; + this.rngs = new XorShiftRandom[concurrency]; for (int i = 0; i < concurrency; i++) { - this.rngs[i] = new Random(); + this.rngs[i] = new XorShiftRandom(); } } @@ -248,7 +248,7 @@ private object GetLockObjectForRow(int row) return this.lockObjects[row % this.lockObjects.Length]; } - private Random GetRng(int row) + private XorShiftRandom GetRng(int row) { return this.rngs[row % this.rngs.Length]; } diff --git a/CacheTable/XorShiftRandom.cs b/CacheTable/XorShiftRandom.cs new file mode 100644 index 0000000..2e20c74 --- /dev/null +++ b/CacheTable/XorShiftRandom.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.CompilerServices; + +namespace CacheTable +{ + public class XorShiftRandom + { + [ThreadStatic] + private static readonly Random seedRng = new Random(); + + private uint state; + + public XorShiftRandom() : this(GetNonZeroSeed()) + { + } + + public XorShiftRandom(uint seed) + { + this.state = seed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Next(int max) + { + uint x = this.state; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + this.state = x; + return (int)(x % max); + } + + private static uint GetNonZeroSeed() + { + while (true) + { + uint seed = (uint)seedRng.Next(); + if (seed != 0) + { + return seed; + } + } + } + } +} From 034841edf868e33438e48d4b8729d8adabe07c31 Mon Sep 17 00:00:00 2001 From: James Lao Date: Sun, 10 Mar 2019 21:52:10 -0700 Subject: [PATCH 2/3] More benchmarks. --- Benchmark/Eviction.cs | 35 ++++++++++++ Benchmark/GuidKeysReadFullTable.cs | 86 ++++++++++++++++++++++++++++++ Benchmark/HashCollisions.cs | 30 ++++------- Benchmark/WrappedInt.cs | 17 ++++++ 4 files changed, 148 insertions(+), 20 deletions(-) create mode 100644 Benchmark/Eviction.cs create mode 100644 Benchmark/GuidKeysReadFullTable.cs create mode 100644 Benchmark/WrappedInt.cs diff --git a/Benchmark/Eviction.cs b/Benchmark/Eviction.cs new file mode 100644 index 0000000..011ed23 --- /dev/null +++ b/Benchmark/Eviction.cs @@ -0,0 +1,35 @@ +using BenchmarkDotNet.Attributes; +using CacheTable; + +namespace Benchmark +{ + [ClrJob] + [RankColumn] + public class Eviction + { + private CacheTable cacheTable; + private int i; + + [Params(4, 8)] + public int N; + + [GlobalSetup] + public void Setup() + { + this.cacheTable = new CacheTable(10, this.N); + + for (int i = 0; i < this.N; i++) + { + this.cacheTable[i] = i; + } + + this.i = this.N; + } + + [Benchmark] + public void CacheTable() + { + this.cacheTable[this.i] = this.i++; + } + } +} diff --git a/Benchmark/GuidKeysReadFullTable.cs b/Benchmark/GuidKeysReadFullTable.cs new file mode 100644 index 0000000..98dde4d --- /dev/null +++ b/Benchmark/GuidKeysReadFullTable.cs @@ -0,0 +1,86 @@ +using CacheTable; +using System; +using BenchmarkDotNet.Attributes; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace Benchmark +{ + [CoreJob] + [RPlotExporter, RankColumn] + public class GuidKeysReadFullTable + { + private readonly CacheTable cacheTable = new CacheTable(10, 4); + private readonly Dictionary dictionary = new Dictionary(); + private WrappedString[] keys; + + struct WrappedString : IEquatable + { + public string Value; + + public bool Equals(WrappedString other) => this.Value.Equals(other.Value); + + public override int GetHashCode() => this.Value.GetHashCode(); + + public override bool Equals(object obj) => this.Equals((WrappedString)obj); + + public static implicit operator WrappedString(string str) => new WrappedString { Value = str }; + } + + [GlobalSetup] + public void Setup() + { + while (cacheTable.Count != 40) + { + this.cacheTable[Guid.NewGuid().ToString()] = 42; + } + + this.keys = new WrappedString[40]; + int i = 0; + foreach (KeyValuePair kvp in cacheTable) + { + this.dictionary[kvp.Key] = kvp.Value; + this.keys[i++] = kvp.Key; + } + + Random rng = new Random(); + Shuffle(rng, this.keys); + } + + public static void Shuffle(Random rng, T[] array) + { + int n = array.Length; + while (n > 1) + { + int k = rng.Next(n--); + T temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } + + [Benchmark] + public int CacheTable() + { + int sum = 0; + foreach (var k in this.keys) + { + sum += this.cacheTable[k]; + } + + return sum; + } + + [Benchmark(Baseline = true)] + public int Dictionary() + { + int sum = 0; + foreach (var k in this.keys) + { + sum += this.dictionary[k]; + } + + return sum; + } + } +} diff --git a/Benchmark/HashCollisions.cs b/Benchmark/HashCollisions.cs index e4db489..72ceeba 100644 --- a/Benchmark/HashCollisions.cs +++ b/Benchmark/HashCollisions.cs @@ -15,41 +15,31 @@ public class HashCollisions private readonly Dictionary dictionary = new Dictionary(); private readonly ConcurrentDictionary concurrentDictionary = new ConcurrentDictionary(); - struct WrappedInt : IEquatable - { - public int Value; - - public bool Equals(WrappedInt other) => this.Value == other.Value; - - public override int GetHashCode() => 42; - - public override bool Equals(object obj) => this.Equals((WrappedInt)obj); - - public static implicit operator WrappedInt(int i) => new WrappedInt { Value = i }; - } + [Params(32, 64)] + public int N; [Benchmark] public void CacheTable() { - for (int i = 0; i < 4; i++) this.cacheTable[i] = i; + for (int i = 0; i < this.N; i++) this.cacheTable[i] = i; } - [Benchmark] - public void ConcurrentCacheTable() + [Benchmark(Baseline = true)] + public void Dictionary() { - for (int i = 0; i < 4; i++) this.concurrentCacheTable[i] = i; + for (int i = 0; i < this.N; i++) this.dictionary[i] = i; } - [Benchmark(Baseline = true)] - public void Dictionary() + [Benchmark] + public void ConcurrentCacheTable() { - for (int i = 0; i < 4; i++) this.dictionary[i] = i; + for (int i = 0; i < this.N; i++) this.concurrentCacheTable[i] = i; } [Benchmark] public void ConcurrentDictionary() { - for (int i = 0; i < 4; i++) this.concurrentDictionary[i] = i; + for (int i = 0; i < this.N; i++) this.concurrentDictionary[i] = i; } } } diff --git a/Benchmark/WrappedInt.cs b/Benchmark/WrappedInt.cs new file mode 100644 index 0000000..689456e --- /dev/null +++ b/Benchmark/WrappedInt.cs @@ -0,0 +1,17 @@ +using System; + +namespace Benchmark +{ + struct WrappedInt : IEquatable + { + public int Value; + + public bool Equals(WrappedInt other) => this.Value == other.Value; + + public override int GetHashCode() => 42; + + public override bool Equals(object obj) => this.Equals((WrappedInt)obj); + + public static implicit operator WrappedInt(int i) => new WrappedInt { Value = i }; + } +} From b76bf21228c32fee3a011a3bcace08b4634c0393 Mon Sep 17 00:00:00 2001 From: James Lao Date: Mon, 11 Mar 2019 22:19:02 -0700 Subject: [PATCH 3/3] Fix seedRng init. --- CacheTable/XorShiftRandom.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CacheTable/XorShiftRandom.cs b/CacheTable/XorShiftRandom.cs index 2e20c74..e2ecedf 100644 --- a/CacheTable/XorShiftRandom.cs +++ b/CacheTable/XorShiftRandom.cs @@ -6,7 +6,7 @@ namespace CacheTable public class XorShiftRandom { [ThreadStatic] - private static readonly Random seedRng = new Random(); + private static Random seedRng; private uint state; @@ -32,6 +32,11 @@ public int Next(int max) private static uint GetNonZeroSeed() { + if (seedRng == null) + { + seedRng = new Random(); + } + while (true) { uint seed = (uint)seedRng.Next();