.NET 8 Suchoptimierung: SearchValues und FrozenCollections für Hot Paths
.NET 8 führt SearchValues<T> sowie Sammlungen wie FrozenSet<T>/FrozenDictionary<TKey, TValue> ein, die den Overhead in Hot Paths drastisch reduzieren. Diese Strukturen übernehmen die schwere Arbeit bei der Erstellung und ermöglichen Lookups in konstanter Zeit ohne wiederholte Analysen. Sie eignen sich ideal für Anwendungen mit hohem Durchsatz, die Millionen von Operationen verarbeiten.
Charaktersuche mit SearchValues beschleunigen
Die Standardmethode string.IndexOfAny analysiert das Zeichenspektrum bei jedem Aufruf neu und verursacht so Overhead in Schleifen. SearchValues<char> wird einmal erstellt und cached die optimale Suchstrategie.
Bei der Initialisierung führt .NET durch:
- Analyse des Eingabesets (einzelnes Zeichen, Bereich oder Mehrzeichen).
- Algorithmusauswahl mit SIMD-Unterstützung (AVX2, SSE2).
- Aufbau von Datenstrukturen (Bitmasken, Hash-Tabellen).
SearchValues<char> separators = SearchValues.Create(".,;!?");
ReadOnlySpan<char> input = "Hello, world!";
int index = input.IndexOfAny(separators);
Suchstrategien variieren je nach Daten:
- Einzelnes Zeichen — vektorisierte Prüfung.
- Bereich (0-9A-Z) — O(1) Bitmaske.
- Kleines Set (≤4 Zeichen) — SIMD.
- Großes Set — optimierte Hash-Tabelle.
Benchmarks zeigen enorme Gewinne:
| Szenario | IndexOfAny | SearchValues | Beschleunigung |
|----------|------------|--------------|----------------|
| Kurzer String | ~5 ns | ~5 ns | 1x |
| Langer String (2000 Zeichen) | ~52 ns | ~4 ns | ~13x |
Die größten Vorteile ergeben sich bei Strings über 100 Zeichen, da die wiederholte Analyse entfällt.
FrozenCollections: Unveränderliche Hash-Tabellen
HashSet<T> und Dictionary<TKey, TValue> haben Mutationsoverhead wie Rehashing und Grenzprüfungen. FrozenSet<T> und FrozenDictionary<TKey, TValue> sind für read-only Szenarien optimiert.
Bei der Erstellung:
- Schlüsselanalyse für eine perfekte Hash-Funktion ohne Kollisionen.
- Cache-freundliche Datenanordnung.
- Entfernte Laufzeit-Immutable-Checks.
Kleine Sammlungen nutzen Bitmasken statt Hashing.
var dict = Enumerable.Range(0, 100000)
.ToFrozenDictionary(x => x, x => $"Wert {{x}}");
if (dict.TryGetValue(42, out string? value))
{
Console.WriteLine(value);
}
Benchmarks (100.000 Elemente):
| Operation | Dictionary | HashSet | FrozenDictionary | FrozenSet |
|-----------|------------|---------|------------------|-----------|
| TryGetValue/Contains | 3,78 ns | 4,47 ns | 1,87 ns | 3,37 ns |
FrozenDictionary ist doppelt so schnell wie ein Standard-Dictionary.
Praxisbeispiele
SearchValues:
- Millionen Lookups gegen feste Sets.
- Log-Parsing, Tokenisierung, Validierung.
- Lange Strings (>100 Zeichen).
FrozenCollections:
- Statische Konfigs beim Startup.
- Lookup-Tabellen (Fehlercodes, Status).
- Read-lastige Workloads mit Tausenden Lookups.
Vermeiden Sie sie bei häufigen Mutationen oder seltenen Aufrufen – die Erstellungskosten überwiegen.
Praktische Umsetzung
Auf App-Ebene cachen:
public static class AppCache
{
public static readonly SearchValues<char> UrlSafeChars =
SearchValues.Create("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~");
public static readonly FrozenSet<string> ValidCurrencies =
new[] { "USD", "EUR", "GBP", "JPY", "CNY" }.ToFrozenSet();
}
Validierungsbeispiele:
- E-Mail:
SearchValues<char> invalidEmailChars = SearchValues.Create("()<>[]:;@\\,?\""); - TLD:
FrozenSet<string> validTlds = LoadTldsFromConfig().ToFrozenSet();
Immer mit BenchmarkDotNet profilieren – der Nutzen hängt vom Operationsvolumen ab.
Wichtige Erkenntnisse
SearchValuesbeschleunigt Suchen um das 13-Fache bei langen Strings durch einmalige Optimierung.FrozenDictionaryverdoppelt dieTryGetValue-Performance im Vergleich zuDictionary.- Beide glänzen mit unveränderlichen Sets in Hot Paths.
- Zuerst profilieren: Nutzlos bei seltenen Aufrufen.
- SIMD und perfektes Hashing für Erfolge bei Mid-/Senior-Entwicklern nutzen.
— Editorial Team
Noch keine Kommentare.