-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathHasherKeySamples.cs
More file actions
176 lines (163 loc) · 5.77 KB
/
Copy pathHasherKeySamples.cs
File metadata and controls
176 lines (163 loc) · 5.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
using System.Text;
/// <summary>
/// The key shapes swept by the hasher benchmarks and the hash-quality report.
/// </summary>
public enum KeyShape
{
/// <summary>Short lowercase-alphanumeric identifiers (6–12 chars), the common map-key case.</summary>
ShortAscii,
/// <summary>Long ASCII path / URL-like keys (48–80 chars).</summary>
LongAscii,
/// <summary>Shorter mixed Latin + CJK text (10–20 chars) that exercises the full-width fold.</summary>
NonAscii,
}
/// <summary>
/// Deterministic, <b>distinct</b> key samples shared by <c>StringHasherBenchmark</c> /
/// <c>IntegerHasherBenchmark</c> (throughput) and the <c>--hash-quality</c> distribution report.
/// </summary>
/// <remarks>
/// Both views drive off the <em>same</em> sample (same seed, same shapes), so a hasher's measured
/// throughput and its measured distribution quality are directly comparable — the whole point of
/// pairing the two: a fast hasher that clusters is not a win. Keys are de-duplicated during
/// generation because <see cref="Celerity.Hashing.HashQualityEvaluator"/> counts duplicate keys as
/// collisions, which would otherwise skew the distribution metrics.
/// </remarks>
public static class HasherKeySamples
{
/// <summary>The default sample size used by the benchmarks and the report.</summary>
public const int DefaultCount = 2_000;
private const string Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789_/";
/// <summary>Builds <paramref name="count"/> distinct <see cref="string"/> keys of the given shape.</summary>
public static string[] Strings(KeyShape shape, int count = DefaultCount)
{
// Deterministic seed so the sample is identical across runs, hashers, and the two views.
Random rand = new(42);
var set = new HashSet<string>(count);
var keys = new string[count];
int n = 0;
while (n < count)
{
string key = shape switch
{
KeyShape.ShortAscii => MakeAscii(rand, rand.Next(6, 13)),
KeyShape.LongAscii => MakeAscii(rand, rand.Next(48, 81)),
KeyShape.NonAscii => MakeNonAscii(rand, rand.Next(10, 21)),
_ => throw new ArgumentOutOfRangeException(nameof(shape)),
};
if (set.Add(key))
{
keys[n++] = key;
}
}
return keys;
}
/// <summary>Builds <paramref name="count"/> distinct non-zero <see cref="int"/> keys.</summary>
public static int[] Int32(int count = DefaultCount)
{
Random rand = new(42);
var set = new HashSet<int>(count);
var keys = new int[count];
int n = 0;
while (n < count)
{
int key = rand.Next(1, int.MaxValue);
if (set.Add(key))
{
keys[n++] = key;
}
}
return keys;
}
/// <summary>Builds <paramref name="count"/> distinct <see cref="long"/> keys.</summary>
public static long[] Int64(int count = DefaultCount)
{
Random rand = new(42);
var set = new HashSet<long>(count);
var keys = new long[count];
int n = 0;
while (n < count)
{
long key = ((long)rand.Next() << 32) | (uint)rand.Next();
if (key != 0 && set.Add(key))
{
keys[n++] = key;
}
}
return keys;
}
/// <summary>Builds <paramref name="count"/> distinct non-zero <see cref="uint"/> keys.</summary>
public static uint[] UInt32(int count = DefaultCount)
{
Random rand = new(42);
var set = new HashSet<uint>(count);
var keys = new uint[count];
int n = 0;
while (n < count)
{
uint key = (uint)rand.Next(1, int.MaxValue);
if (set.Add(key))
{
keys[n++] = key;
}
}
return keys;
}
/// <summary>Builds <paramref name="count"/> distinct non-zero <see cref="ulong"/> keys.</summary>
public static ulong[] UInt64(int count = DefaultCount)
{
Random rand = new(42);
var set = new HashSet<ulong>(count);
var keys = new ulong[count];
int n = 0;
while (n < count)
{
ulong key = ((ulong)(uint)rand.Next() << 32) | (uint)rand.Next();
if (key != 0 && set.Add(key))
{
keys[n++] = key;
}
}
return keys;
}
/// <summary>Builds <paramref name="count"/> distinct <see cref="Guid"/> keys.</summary>
public static Guid[] Guids(int count = DefaultCount)
{
Random rand = new(42);
var set = new HashSet<Guid>(count);
var keys = new Guid[count];
byte[] bytes = new byte[16];
int n = 0;
while (n < count)
{
rand.NextBytes(bytes);
var key = new Guid(bytes);
if (set.Add(key))
{
keys[n++] = key;
}
}
return keys;
}
private static string MakeAscii(Random rand, int length)
{
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
sb.Append(Alphabet[rand.Next(Alphabet.Length)]);
}
return sb.ToString();
}
private static string MakeNonAscii(Random rand, int length)
{
// Mix Latin letters with CJK code points (U+4E00–U+9FFF) so both bytes of the
// UTF-16 code unit vary — the case the full-width string hashers are built for.
var sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
sb.Append(rand.Next(2) == 0
? (char)('a' + rand.Next(26))
: (char)(0x4E00 + rand.Next(0x9FFF - 0x4E00 + 1)));
}
return sb.ToString();
}
}