Skip to content

Commit b14934d

Browse files
committed
Move the literals to be better scoped.
1 parent 6320185 commit b14934d

File tree

5 files changed

+89
-72
lines changed

5 files changed

+89
-72
lines changed

eng/StackExchange.Redis.Build/FastHashGenerator.cs

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ private bool Predicate(SyntaxNode node, CancellationToken cancellationToken)
4040
return false;
4141
}
4242

43+
private static string GetName(INamedTypeSymbol type)
44+
{
45+
if (type.ContainingType is null) return type.Name;
46+
var stack = new Stack<string>();
47+
while (true)
48+
{
49+
stack.Push(type.Name);
50+
if (type.ContainingType is null) break;
51+
type = type.ContainingType;
52+
}
53+
var sb = new StringBuilder(stack.Pop());
54+
while (stack.Count != 0)
55+
{
56+
sb.Append('.').Append(stack.Pop());
57+
}
58+
return sb.ToString();
59+
}
60+
4361
private (string Namespace, string ParentType, string Name, string Value) Transform(
4462
GeneratorSyntaxContext ctx,
4563
CancellationToken cancellationToken)
@@ -49,7 +67,7 @@ private bool Predicate(SyntaxNode node, CancellationToken cancellationToken)
4967
string ns = "", parentType = "";
5068
if (named.ContainingType is { } containingType)
5169
{
52-
parentType = containingType.Name; // don't worry about multi-level nesting for now; add later if needed
70+
parentType = GetName(containingType);
5371
ns = containingType.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat);
5472
}
5573
else if (named.ContainingNamespace is { } containingNamespace)
@@ -114,17 +132,33 @@ private void Generate(
114132
foreach (var grp in literals.GroupBy(l => (l.Namespace, l.ParentType)))
115133
{
116134
NewLine();
135+
int braces = 0;
117136
if (!string.IsNullOrWhiteSpace(grp.Key.Namespace))
118137
{
119138
NewLine().Append("namespace ").Append(grp.Key.Namespace);
120139
NewLine().Append("{");
121140
indent++;
141+
braces++;
122142
}
123143
if (!string.IsNullOrWhiteSpace(grp.Key.ParentType))
124144
{
125-
NewLine().Append("partial class ").Append(grp.Key.ParentType);
126-
NewLine().Append("{");
127-
indent++;
145+
if (grp.Key.ParentType.Contains('.')) // nested types
146+
{
147+
foreach (var part in grp.Key.ParentType.Split('.'))
148+
{
149+
NewLine().Append("partial class ").Append(part);
150+
NewLine().Append("{");
151+
indent++;
152+
braces++;
153+
}
154+
}
155+
else
156+
{
157+
NewLine().Append("partial class ").Append(grp.Key.ParentType);
158+
NewLine().Append("{");
159+
indent++;
160+
braces++;
161+
}
128162
}
129163

130164
foreach (var literal in grp)
@@ -156,12 +190,8 @@ private void Generate(
156190
NewLine().Append("}");
157191
}
158192

159-
if (!string.IsNullOrWhiteSpace(grp.Key.ParentType))
160-
{
161-
indent--;
162-
NewLine().Append("}");
163-
}
164-
if (!string.IsNullOrWhiteSpace(grp.Key.Namespace))
193+
// handle any closing braces
194+
while (braces-- > 0)
165195
{
166196
indent--;
167197
NewLine().Append("}");

eng/StackExchange.Redis.Build/FastHashGenerator.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ The purpose of this generator is to interpret inputs like:
1111

1212
Usually the token is inferred from the name; `[FastHash("real value")]` can be used if the token is not a valid identifier.
1313
Underscore is replaced with hyphen, so a field called `my_token` has the default value `"my-token"`.
14-
The generator demands *all* of `[FastHash] public static partial class`.
14+
The generator demands *all* of `[FastHash] public static partial class`, and note that any *containing* types must
15+
*also* be declared `partial`.
1516

1617
The output is of the form:
1718

@@ -34,7 +35,9 @@ static partial class f32
3435
}
3536
```
3637

37-
This allows for fast, efficient, and safe matching of well-known tokens, for example:
38+
(this API is strictly an internal implementation detail, and can change at any time)
39+
40+
This generated code allows for fast, efficient, and safe matching of well-known tokens, for example:
3841

3942
``` c#
4043
var key = ...
@@ -52,7 +55,11 @@ switch (key.Length)
5255

5356
The switch on the `Length` is optional, but recommended - these low values can often be implemented (by the compiler)
5457
as a simple jump-table, which is very fast. However, switching on the hash itself is also valid. All hash matches
55-
must also perform a sequence equality check - the `Is` convenient method validates both hash and equality.
58+
must also perform a sequence equality check - the `Is` convenient method validates both hash and equality.
59+
60+
Note that `switch` requires `const` values, hence why we use generated *types* rather than partial-properties
61+
that emit an instance with the known values. Also, the `"..."u8` syntax emits a span which is awkward to store, but
62+
easy to return via a property.~~~~
5663

5764

5865

src/StackExchange.Redis/FastHash.Literals.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/StackExchange.Redis/FastHash.cs

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using System;
2-
using System.Buffers;
1+
using System.Buffers;
32
using System.Buffers.Binary;
3+
using System.Diagnostics;
44
using System.Runtime.CompilerServices;
55
using System.Runtime.InteropServices;
66

@@ -11,29 +11,16 @@ namespace StackExchange.Redis;
1111
/// RESP literals that are usually identifiable by their length and initial bytes; it is not intended
1212
/// for general purpose hashing. All matches must also perform a sequence equality check.
1313
/// </summary>
14-
/// <remarks>While introduced alongside the VSET work, this is not specific to VSET - and indeed VSET
15-
/// is not a good example of the feature; rather, this is intended for more widespread use.</remarks>
16-
/*
17-
Example data from the benchmarks; note that string is included only for baseline purposes - we don't actually want
18-
to construct strings when parsing tokens.
19-
20-
| Method | Size | Mean | Error | StdDev | Median | Op/s | Ratio | RatioSD | Allocated | Alloc Ratio |
21-
|--------------------- |----- |----------:|----------:|----------:|----------:|--------------:|------:|--------:|----------:|------------:|
22-
| String | 16 | 21.376 ns | 0.4164 ns | 0.6483 ns | 21.268 ns | 46,781,518.5 | 1.00 | 0.04 | - | NA |
23-
| Hash64 | 16 | 3.161 ns | 0.0605 ns | 0.0647 ns | 3.148 ns | 316,400,326.5 | 0.15 | 0.01 | - | NA |
24-
| Hash64Unsafe | 16 | 3.820 ns | 0.0747 ns | 0.1072 ns | 3.811 ns | 261,789,013.8 | 0.18 | 0.01 | - | NA |
25-
| Hash64Fallback | 16 | 19.461 ns | 0.2954 ns | 0.2763 ns | 19.496 ns | 51,383,837.0 | 0.91 | 0.03 | - | NA |
26-
| Hash64_SingleSegment | 16 | 9.477 ns | 0.1877 ns | 0.3705 ns | 9.464 ns | 105,519,833.1 | 0.44 | 0.02 | - | NA |
27-
| Hash64_MultiSegment | 16 | 82.778 ns | 1.6255 ns | 2.3313 ns | 82.475 ns | 12,080,568.6 | 3.88 | 0.16 | - | NA |
28-
*/
29-
internal static partial class FastHash
14+
/// <remarks>See HastHashGenerator.md for more information and intended usage.</remarks>
15+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
16+
[Conditional("DEBUG")] // evaporate in release
17+
internal sealed class FastHashAttribute(string token = "") : Attribute
3018
{
31-
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
32-
public sealed class FastHashAttribute(string token = "") : Attribute
33-
{
34-
public string Token => token;
35-
}
19+
public string Token => token;
20+
}
3621

22+
internal static class FastHash
23+
{
3724
// Perform case-insensitive hash by masking (X and x differ by only 1 bit); this halves
3825
// our entropy, but is still useful when case doesn't matter.
3926
private const long CaseMask = ~0x2020202020202020;

src/StackExchange.Redis/ResultProcessor.VectorSets.cs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Pipelines.Sockets.Unofficial.Arenas;
2-
using FH = global::StackExchange.Redis.FastHash;
32

43
// ReSharper disable once CheckNamespace
54
namespace StackExchange.Redis;
@@ -44,7 +43,7 @@ protected override bool TryReadOne(in RawResult result, out RedisValue value)
4443
}
4544
}
4645

47-
private sealed class VectorSetInfoProcessor : ResultProcessor<VectorSetInfo?>
46+
private sealed partial class VectorSetInfoProcessor : ResultProcessor<VectorSetInfo?>
4847
{
4948
protected override bool SetResultCore(PhysicalConnection connection, Message message, in RawResult result)
5049
{
@@ -59,7 +58,7 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
5958
var quantType = VectorSetQuantization.Unknown;
6059
string? quantTypeRaw = null;
6160
int vectorDim = 0, maxLevel = 0;
62-
long size = 0, vsetUid = 0, hnswMaxNodeUid = 0;
61+
long resultSize = 0, vsetUid = 0, hnswMaxNodeUid = 0;
6362
var iter = result.GetItems().GetEnumerator();
6463
while (iter.MoveNext())
6564
{
@@ -71,30 +70,30 @@ protected override bool SetResultCore(PhysicalConnection connection, Message mes
7170
var keyHash = key.Payload.Hash64();
7271
switch (key.Payload.Length)
7372
{
74-
case FH.size.Length when FH.size.Is(keyHash, key) && value.TryGetInt64(out var i64):
75-
size = i64;
73+
case size.Length when size.Is(keyHash, key) && value.TryGetInt64(out var i64):
74+
resultSize = i64;
7675
break;
77-
case FH.vset_uid.Length when FH.vset_uid.Is(keyHash, key) && value.TryGetInt64(out var i64):
76+
case vset_uid.Length when vset_uid.Is(keyHash, key) && value.TryGetInt64(out var i64):
7877
vsetUid = i64;
7978
break;
80-
case FH.max_level.Length when FH.max_level.Is(keyHash, key) && value.TryGetInt64(out var i64):
79+
case max_level.Length when max_level.Is(keyHash, key) && value.TryGetInt64(out var i64):
8180
maxLevel = checked((int)i64);
8281
break;
83-
case FH.vector_dim.Length
84-
when FH.vector_dim.Is(keyHash, key) && value.TryGetInt64(out var i64):
82+
case vector_dim.Length
83+
when vector_dim.Is(keyHash, key) && value.TryGetInt64(out var i64):
8584
vectorDim = checked((int)i64);
8685
break;
87-
case FH.quant_type.Length when FH.quant_type.Is(keyHash, key):
86+
case quant_type.Length when quant_type.Is(keyHash, key):
8887
var qHash = value.Payload.Hash64();
8988
switch (value.Payload.Length)
9089
{
91-
case FH.bin.Length when FH.bin.Is(qHash, value):
90+
case bin.Length when bin.Is(qHash, value):
9291
quantType = VectorSetQuantization.Binary;
9392
break;
94-
case FH.f32.Length when FH.f32.Is(qHash, value):
93+
case f32.Length when f32.Is(qHash, value):
9594
quantType = VectorSetQuantization.None;
9695
break;
97-
case FH.int8.Length when FH.int8.Is(qHash, value):
96+
case int8.Length when int8.Is(qHash, value):
9897
quantType = VectorSetQuantization.Int8;
9998
break;
10099
default:
@@ -104,20 +103,34 @@ when FH.vector_dim.Is(keyHash, key) && value.TryGetInt64(out var i64):
104103
}
105104

106105
break;
107-
case FH.hnsw_max_node_uid.Length
108-
when FH.hnsw_max_node_uid.Is(keyHash, key) && value.TryGetInt64(out var i64):
106+
case hnsw_max_node_uid.Length
107+
when hnsw_max_node_uid.Is(keyHash, key) && value.TryGetInt64(out var i64):
109108
hnswMaxNodeUid = i64;
110109
break;
111110
}
112111
}
113112

114113
SetResult(
115114
message,
116-
new VectorSetInfo(quantType, quantTypeRaw, vectorDim, size, maxLevel, vsetUid, hnswMaxNodeUid));
115+
new VectorSetInfo(quantType, quantTypeRaw, vectorDim, resultSize, maxLevel, vsetUid, hnswMaxNodeUid));
117116
return true;
118117
}
119118

120119
return false;
121120
}
121+
122+
#pragma warning disable CS8981, SA1134, SA1300, SA1303, SA1502
123+
// ReSharper disable InconsistentNaming - to better represent expected literals
124+
[FastHash] public static partial class bin { }
125+
[FastHash] public static partial class f32 { }
126+
[FastHash] public static partial class int8 { }
127+
[FastHash] public static partial class size { }
128+
[FastHash] public static partial class vset_uid { }
129+
[FastHash] public static partial class max_level { }
130+
[FastHash] public static partial class quant_type { }
131+
[FastHash] public static partial class vector_dim { }
132+
[FastHash] public static partial class hnsw_max_node_uid { }
133+
// ReSharper restore InconsistentNaming
134+
#pragma warning restore CS8981, SA1134, SA1300, SA1303, SA1502
122135
}
123136
}

0 commit comments

Comments
 (0)