aspnetcore/benchmarks/Microsoft.AspNetCore.Routin.../Matching/CustomHashTableJumpTable.cs

198 lines
6.2 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Microsoft.AspNetCore.Routing.Matching
{
internal class CustomHashTableJumpTable : JumpTable
{
// Similar to HashHelpers list of primes, but truncated. We don't expect
// incredibly large numbers to be useful here.
private static readonly int[] Primes = new int[]
{
3, 7, 11, 17, 23, 29, 37, 47, 59,
71, 89, 107, 131, 163, 197, 239, 293,
353, 431, 521, 631, 761, 919, 1103, 1327,
1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839,
7013, 8419, 10103,
};
private readonly int _defaultDestination;
private readonly int _exitDestination;
private readonly int _prime;
private readonly int[] _buckets;
private readonly Entry[] _entries;
public CustomHashTableJumpTable(
int defaultDestination,
int exitDestination,
(string text, int destination)[] entries)
{
_defaultDestination = defaultDestination;
_exitDestination = exitDestination;
var map = new Dictionary<int, List<(string text, int destination)>>();
for (var i = 0; i < entries.Length; i++)
{
var key = GetKey(entries[i].text, new PathSegment(0, entries[i].text.Length));
if (!map.TryGetValue(key, out var matches))
{
matches = new List<(string text, int destination)>();
map.Add(key, matches);
}
matches.Add(entries[i]);
}
_prime = GetPrime(map.Count);
_buckets = new int[_prime + 1];
_entries = new Entry[map.Sum(kvp => kvp.Value.Count)];
var next = 0;
foreach (var group in map.GroupBy(kvp => kvp.Key % _prime).OrderBy(g => g.Key))
{
_buckets[group.Key] = next;
foreach (var array in group)
{
for (var i = 0; i < array.Value.Count; i++)
{
_entries[next++] = new Entry(array.Value[i].text, array.Value[i].destination);
}
}
}
Debug.Assert(next == _entries.Length);
_buckets[_prime] = next;
var last = 0;
for (var i = 0; i < _buckets.Length; i++)
{
if (_buckets[i] == 0)
{
_buckets[i] = last;
}
else
{
last = _buckets[i];
}
}
}
public int Find(int key)
{
return key % _prime;
}
private static int GetPrime(int capacity)
{
for (int i = 0; i < Primes.Length; i++)
{
int prime = Primes[i];
if (prime >= capacity)
{
return prime;
}
}
return Primes[Primes.Length - 1];
}
public override int GetDestination(string path, PathSegment segment)
{
if (segment.Length == 0)
{
return _exitDestination;
}
var key = GetKey(path, segment);
var index = Find(key);
var start = _buckets[index];
var end = _buckets[index + 1];
var entries = _entries.AsSpan(start, end - start);
for (var i = 0; i < entries.Length; i++)
{
var text = entries[i].Text;
if (text.Length == segment.Length &&
string.Compare(
path,
segment.Start,
text,
0,
segment.Length,
StringComparison.OrdinalIgnoreCase) == 0)
{
return entries[i].Destination;
}
}
return _defaultDestination;
}
// Builds a hashcode from segment (first four characters converted to 8bit ASCII)
private static unsafe int GetKey(string path, PathSegment segment)
{
fixed (char* p = path)
{
switch (path.Length)
{
case 0:
{
return 0;
}
case 1:
{
return
((*(p + segment.Start + 0) & 0x5F) << (0 * 8));
}
case 2:
{
return
((*(p + segment.Start + 0) & 0x5F) << (0 * 8)) |
((*(p + segment.Start + 1) & 0x5F) << (1 * 8));
}
case 3:
{
return
((*(p + segment.Start + 0) & 0x5F) << (0 * 8)) |
((*(p + segment.Start + 1) & 0x5F) << (1 * 8)) |
((*(p + segment.Start + 2) & 0x5F) << (2 * 8));
}
default:
{
return
((*(p + segment.Start + 0) & 0x5F) << (0 * 8)) |
((*(p + segment.Start + 1) & 0x5F) << (1 * 8)) |
((*(p + segment.Start + 2) & 0x5F) << (2 * 8)) |
((*(p + segment.Start + 3) & 0x5F) << (3 * 8));
}
}
}
}
private readonly struct Entry
{
public readonly string Text;
public readonly int Destination;
public Entry(string text, int destination)
{
Text = text;
Destination = destination;
}
}
}
}