Skip to content

Commit c760c7b

Browse files
authored
Replace System.Random with Xorshift RNG. (#13)
Use fast XorShift RNG.
1 parent 56dd44b commit c760c7b

9 files changed

Lines changed: 230 additions & 26 deletions

Benchmark/Eviction.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using BenchmarkDotNet.Attributes;
2+
using CacheTable;
3+
4+
namespace Benchmark
5+
{
6+
[ClrJob]
7+
[RankColumn]
8+
public class Eviction
9+
{
10+
private CacheTable<WrappedInt, int> cacheTable;
11+
private int i;
12+
13+
[Params(4, 8)]
14+
public int N;
15+
16+
[GlobalSetup]
17+
public void Setup()
18+
{
19+
this.cacheTable = new CacheTable<WrappedInt, int>(10, this.N);
20+
21+
for (int i = 0; i < this.N; i++)
22+
{
23+
this.cacheTable[i] = i;
24+
}
25+
26+
this.i = this.N;
27+
}
28+
29+
[Benchmark]
30+
public void CacheTable()
31+
{
32+
this.cacheTable[this.i] = this.i++;
33+
}
34+
}
35+
}

Benchmark/GuidKeysReadFullTable.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using CacheTable;
2+
using System;
3+
using BenchmarkDotNet.Attributes;
4+
using System.Collections.Generic;
5+
using System.Collections.Concurrent;
6+
7+
namespace Benchmark
8+
{
9+
[CoreJob]
10+
[RPlotExporter, RankColumn]
11+
public class GuidKeysReadFullTable
12+
{
13+
private readonly CacheTable<WrappedString, int> cacheTable = new CacheTable<WrappedString, int>(10, 4);
14+
private readonly Dictionary<WrappedString, int> dictionary = new Dictionary<WrappedString, int>();
15+
private WrappedString[] keys;
16+
17+
struct WrappedString : IEquatable<WrappedString>
18+
{
19+
public string Value;
20+
21+
public bool Equals(WrappedString other) => this.Value.Equals(other.Value);
22+
23+
public override int GetHashCode() => this.Value.GetHashCode();
24+
25+
public override bool Equals(object obj) => this.Equals((WrappedString)obj);
26+
27+
public static implicit operator WrappedString(string str) => new WrappedString { Value = str };
28+
}
29+
30+
[GlobalSetup]
31+
public void Setup()
32+
{
33+
while (cacheTable.Count != 40)
34+
{
35+
this.cacheTable[Guid.NewGuid().ToString()] = 42;
36+
}
37+
38+
this.keys = new WrappedString[40];
39+
int i = 0;
40+
foreach (KeyValuePair<WrappedString, int> kvp in cacheTable)
41+
{
42+
this.dictionary[kvp.Key] = kvp.Value;
43+
this.keys[i++] = kvp.Key;
44+
}
45+
46+
Random rng = new Random();
47+
Shuffle(rng, this.keys);
48+
}
49+
50+
public static void Shuffle<T>(Random rng, T[] array)
51+
{
52+
int n = array.Length;
53+
while (n > 1)
54+
{
55+
int k = rng.Next(n--);
56+
T temp = array[n];
57+
array[n] = array[k];
58+
array[k] = temp;
59+
}
60+
}
61+
62+
[Benchmark]
63+
public int CacheTable()
64+
{
65+
int sum = 0;
66+
foreach (var k in this.keys)
67+
{
68+
sum += this.cacheTable[k];
69+
}
70+
71+
return sum;
72+
}
73+
74+
[Benchmark(Baseline = true)]
75+
public int Dictionary()
76+
{
77+
int sum = 0;
78+
foreach (var k in this.keys)
79+
{
80+
sum += this.dictionary[k];
81+
}
82+
83+
return sum;
84+
}
85+
}
86+
}

Benchmark/HashCollisions.cs

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,31 @@ public class HashCollisions
1515
private readonly Dictionary<WrappedInt, int> dictionary = new Dictionary<WrappedInt, int>();
1616
private readonly ConcurrentDictionary<WrappedInt, int> concurrentDictionary = new ConcurrentDictionary<WrappedInt, int>();
1717

18-
struct WrappedInt : IEquatable<WrappedInt>
19-
{
20-
public int Value;
21-
22-
public bool Equals(WrappedInt other) => this.Value == other.Value;
23-
24-
public override int GetHashCode() => 42;
25-
26-
public override bool Equals(object obj) => this.Equals((WrappedInt)obj);
27-
28-
public static implicit operator WrappedInt(int i) => new WrappedInt { Value = i };
29-
}
18+
[Params(32, 64)]
19+
public int N;
3020

3121
[Benchmark]
3222
public void CacheTable()
3323
{
34-
for (int i = 0; i < 4; i++) this.cacheTable[i] = i;
24+
for (int i = 0; i < this.N; i++) this.cacheTable[i] = i;
3525
}
3626

37-
[Benchmark]
38-
public void ConcurrentCacheTable()
27+
[Benchmark(Baseline = true)]
28+
public void Dictionary()
3929
{
40-
for (int i = 0; i < 4; i++) this.concurrentCacheTable[i] = i;
30+
for (int i = 0; i < this.N; i++) this.dictionary[i] = i;
4131
}
4232

43-
[Benchmark(Baseline = true)]
44-
public void Dictionary()
33+
[Benchmark]
34+
public void ConcurrentCacheTable()
4535
{
46-
for (int i = 0; i < 4; i++) this.dictionary[i] = i;
36+
for (int i = 0; i < this.N; i++) this.concurrentCacheTable[i] = i;
4737
}
4838

4939
[Benchmark]
5040
public void ConcurrentDictionary()
5141
{
52-
for (int i = 0; i < 4; i++) this.concurrentDictionary[i] = i;
42+
for (int i = 0; i < this.N; i++) this.concurrentDictionary[i] = i;
5343
}
5444
}
5545
}

Benchmark/RngPerf.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using BenchmarkDotNet.Attributes;
2+
using CacheTable;
3+
using System;
4+
5+
namespace Benchmark
6+
{
7+
[CoreJob]
8+
[RankColumn]
9+
public class RngPerf
10+
{
11+
private readonly Random random = new Random();
12+
private readonly XorShiftRandom xorshift = new XorShiftRandom();
13+
14+
[Benchmark(Baseline = true)]
15+
public int Random()
16+
{
17+
return this.random.Next(12);
18+
}
19+
20+
[Benchmark]
21+
public int XortShiftRandom()
22+
{
23+
return this.xorshift.Next(12);
24+
}
25+
}
26+
}

Benchmark/WrappedInt.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Benchmark
4+
{
5+
struct WrappedInt : IEquatable<WrappedInt>
6+
{
7+
public int Value;
8+
9+
public bool Equals(WrappedInt other) => this.Value == other.Value;
10+
11+
public override int GetHashCode() => 42;
12+
13+
public override bool Equals(object obj) => this.Equals((WrappedInt)obj);
14+
15+
public static implicit operator WrappedInt(int i) => new WrappedInt { Value = i };
16+
}
17+
}

CacheTable/CacheTable.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class CacheTable<TKey, TValue> : ICacheTable<TKey, TValue>
1414
{
1515
private CacheTableInternal<TKey, TValue> table;
1616
private int count;
17-
private readonly Random rng = new Random();
17+
private readonly XorShiftRandom rng = new XorShiftRandom();
1818

1919
/// <summary>
2020
/// Creates a set associative cache with specified number of rows and columns.

CacheTable/CacheTableInternal.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
106106
}
107107

108108
// Returns true if item was inserted into empty slot. False otherwise.
109-
public bool Set(TKey key, TValue value, int row, Random rng)
109+
public bool Set(TKey key, TValue value, int row, XorShiftRandom rng)
110110
{
111111
(int rowStart, int rowEnd) = this.GetRowRange(row);
112112
int empty = -1;

CacheTable/ConcurrentCacheTable.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class ConcurrentCacheTable<TKey, TValue> : ICacheTable<TKey, TValue>
1515
{
1616
private readonly CacheTableInternal<TKey, TValue> table;
1717
private readonly object[] lockObjects;
18-
private readonly Random[] rngs;
18+
private readonly XorShiftRandom[] rngs;
1919
private readonly int[] counts;
2020

2121
/// <summary>
@@ -35,10 +35,10 @@ public ConcurrentCacheTable(int rows, int columns, int concurrency)
3535
this.lockObjects[i] = new object();
3636
}
3737

38-
this.rngs = new Random[concurrency];
38+
this.rngs = new XorShiftRandom[concurrency];
3939
for (int i = 0; i < concurrency; i++)
4040
{
41-
this.rngs[i] = new Random();
41+
this.rngs[i] = new XorShiftRandom();
4242
}
4343
}
4444

@@ -248,7 +248,7 @@ private object GetLockObjectForRow(int row)
248248
return this.lockObjects[row % this.lockObjects.Length];
249249
}
250250

251-
private Random GetRng(int row)
251+
private XorShiftRandom GetRng(int row)
252252
{
253253
return this.rngs[row % this.rngs.Length];
254254
}

CacheTable/XorShiftRandom.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace CacheTable
5+
{
6+
public class XorShiftRandom
7+
{
8+
[ThreadStatic]
9+
private static Random seedRng;
10+
11+
private uint state;
12+
13+
public XorShiftRandom() : this(GetNonZeroSeed())
14+
{
15+
}
16+
17+
public XorShiftRandom(uint seed)
18+
{
19+
this.state = seed;
20+
}
21+
22+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
23+
public int Next(int max)
24+
{
25+
uint x = this.state;
26+
x ^= x << 13;
27+
x ^= x >> 17;
28+
x ^= x << 5;
29+
this.state = x;
30+
return (int)(x % max);
31+
}
32+
33+
private static uint GetNonZeroSeed()
34+
{
35+
if (seedRng == null)
36+
{
37+
seedRng = new Random();
38+
}
39+
40+
while (true)
41+
{
42+
uint seed = (uint)seedRng.Next();
43+
if (seed != 0)
44+
{
45+
return seed;
46+
}
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)