Merge pull request #597 from aspnet/rynowak/jumptable2
Add experimental JumpTables to benchmarks
This commit is contained in:
commit
5e1f99faaf
|
|
@ -18,5 +18,6 @@
|
||||||
<PackageSigningCertName>MicrosoftNuGet</PackageSigningCertName>
|
<PackageSigningCertName>MicrosoftNuGet</PackageSigningCertName>
|
||||||
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
<PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<LangVersion>7.2</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
// An optimized jump table that trades a small amount of additional memory for
|
||||||
|
// hash-table like performance.
|
||||||
|
//
|
||||||
|
// The optimization here is to use the first character of the known entries
|
||||||
|
// as a 'key' in the hash table in the space of A-Z. This gives us a maximum
|
||||||
|
// of 26 buckets (hence the reduced memory)
|
||||||
|
internal class AsciiKeyedJumpTable : JumpTable
|
||||||
|
{
|
||||||
|
public static bool TryCreate(
|
||||||
|
int defaultDestination,
|
||||||
|
int exitDestination,
|
||||||
|
List<(string text, int destination)> entries,
|
||||||
|
out JumpTable result)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
// First we group string by their uppercase letter. If we see a string
|
||||||
|
// that starts with a non-ASCII letter
|
||||||
|
var map = new Dictionary<char, List<(string text, int destination)>>();
|
||||||
|
|
||||||
|
for (var i = 0; i < entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (entries[i].text.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsAscii(entries[i].text))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var first = ToUpperAscii(entries[i].text[0]);
|
||||||
|
if (first < 'A' || first > 'Z')
|
||||||
|
{
|
||||||
|
// Not a letter
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!map.TryGetValue(first, out var matches))
|
||||||
|
{
|
||||||
|
matches = new List<(string text, int destination)>();
|
||||||
|
map.Add(first, matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.Add(entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var next = 0;
|
||||||
|
var ordered = new(string text, int destination)[entries.Count];
|
||||||
|
var indexes = new int[26 * 2];
|
||||||
|
for (var i = 0; i < 26; i++)
|
||||||
|
{
|
||||||
|
indexes[i * 2] = next;
|
||||||
|
|
||||||
|
var length = 0;
|
||||||
|
if (map.TryGetValue((char)('A' + i), out var matches))
|
||||||
|
{
|
||||||
|
length += matches.Count;
|
||||||
|
for (var j = 0; j < matches.Count; j++)
|
||||||
|
{
|
||||||
|
ordered[next++] = matches[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes[i * 2 + 1] = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new AsciiKeyedJumpTable(defaultDestination, exitDestination, ordered, indexes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly int _defaultDestination;
|
||||||
|
private readonly int _exitDestination;
|
||||||
|
private readonly (string text, int destination)[] _entries;
|
||||||
|
private readonly int[] _indexes;
|
||||||
|
|
||||||
|
private AsciiKeyedJumpTable(
|
||||||
|
int defaultDestination,
|
||||||
|
int exitDestination,
|
||||||
|
(string text, int destination)[] entries,
|
||||||
|
int[] indexes)
|
||||||
|
{
|
||||||
|
_defaultDestination = defaultDestination;
|
||||||
|
_exitDestination = exitDestination;
|
||||||
|
_entries = entries;
|
||||||
|
_indexes = indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override unsafe int GetDestination(string path, PathSegment segment)
|
||||||
|
{
|
||||||
|
if (segment.Length == 0)
|
||||||
|
{
|
||||||
|
return _exitDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
var c = path[segment.Start];
|
||||||
|
if (!IsAscii(c))
|
||||||
|
{
|
||||||
|
return _defaultDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
c = ToUpperAscii(c);
|
||||||
|
if (c < 'A' || c > 'Z')
|
||||||
|
{
|
||||||
|
// Character is non-ASCII or not a letter. Since we know that all of the entries are ASCII
|
||||||
|
// and begin with a letter this is not a match.
|
||||||
|
return _defaultDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = (c - 'A') * 2;
|
||||||
|
var start = _indexes[offset];
|
||||||
|
var length = _indexes[offset + 1];
|
||||||
|
|
||||||
|
var entries = _entries;
|
||||||
|
for (var i = start; i < start + length; i++)
|
||||||
|
{
|
||||||
|
var text = entries[i].text;
|
||||||
|
if (segment.Length == text.Length &&
|
||||||
|
string.Compare(
|
||||||
|
path,
|
||||||
|
segment.Start,
|
||||||
|
text,
|
||||||
|
0,
|
||||||
|
segment.Length,
|
||||||
|
StringComparison.OrdinalIgnoreCase) == 0)
|
||||||
|
{
|
||||||
|
return entries[i].destination;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _defaultDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsAscii(char c)
|
||||||
|
{
|
||||||
|
// ~0x7F is a bit mask that checks for bits that won't be set in an ASCII character.
|
||||||
|
// ASCII only uses the lowest 7 bits.
|
||||||
|
return (c & ~0x7F) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsAscii(string text)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < text.Length; i++)
|
||||||
|
{
|
||||||
|
var c = text[i];
|
||||||
|
if (!IsAscii(c))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static char ToUpperAscii(char c)
|
||||||
|
{
|
||||||
|
// 0x5F can be used to convert a character to uppercase ascii (assuming it's a letter).
|
||||||
|
// This works because lowercase ASCII chars are exactly 32 less than their uppercase
|
||||||
|
// counterparts.
|
||||||
|
return (char)(c & 0x5F);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
// 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.Matchers
|
||||||
|
{
|
||||||
|
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 unsafe 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
// 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.Linq;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
{
|
||||||
|
internal class DictionaryLookupJumpTable : JumpTable
|
||||||
|
{
|
||||||
|
private readonly int _defaultDestination;
|
||||||
|
private readonly int _exitDestination;
|
||||||
|
private readonly Dictionary<int, (string text, int destination)[]> _store;
|
||||||
|
|
||||||
|
public DictionaryLookupJumpTable(
|
||||||
|
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.AsSpan());
|
||||||
|
if (!map.TryGetValue(key, out var matches))
|
||||||
|
{
|
||||||
|
matches = new List<(string text, int destination)>();
|
||||||
|
map.Add(key, matches);
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.Add(entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_store = map.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetDestination(string path, PathSegment segment)
|
||||||
|
{
|
||||||
|
if (segment.Length == 0)
|
||||||
|
{
|
||||||
|
return _exitDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = GetKey(path.AsSpan(segment.Start, segment.Length));
|
||||||
|
if (_store.TryGetValue(key, out var entries))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetKey(string path, PathSegment segment)
|
||||||
|
{
|
||||||
|
return GetKey(path.AsSpan(segment.Start, segment.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds a key from the last byte of length + first 3 characters of text (converted to ascii)
|
||||||
|
private static int GetKey(ReadOnlySpan<char> span)
|
||||||
|
{
|
||||||
|
var length = (byte)(span.Length & 0xFF);
|
||||||
|
|
||||||
|
byte c0, c1, c2;
|
||||||
|
switch (length)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
c0 = (byte)(span[0] & 0x5F);
|
||||||
|
return (length << 24) | (c0 << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
c0 = (byte)(span[0] & 0x5F);
|
||||||
|
c1 = (byte)(span[1] & 0x5F);
|
||||||
|
return (length << 24) | (c0 << 16) | (c1 << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
c0 = (byte)(span[0] & 0x5F);
|
||||||
|
c1 = (byte)(span[1] & 0x5F);
|
||||||
|
c2 = (byte)(span[2] & 0x5F);
|
||||||
|
return (length << 24) | (c0 << 16) | (c1 << 8) | c2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using BenchmarkDotNet.Attributes;
|
using BenchmarkDotNet.Attributes;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matchers
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
|
@ -14,9 +15,12 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
|
||||||
private JumpTable _linearSearch;
|
private JumpTable _linearSearch;
|
||||||
private JumpTable _dictionary;
|
private JumpTable _dictionary;
|
||||||
|
private JumpTable _ascii;
|
||||||
|
private JumpTable _dictionaryLookup;
|
||||||
|
private JumpTable _customHashTable;
|
||||||
|
|
||||||
// All factors of 100 to support sampling
|
// All factors of 100 to support sampling
|
||||||
[Params(2, 5, 10, 25, 50, 100)]
|
[Params(2, 4, 5, 10, 25)]
|
||||||
public int Count;
|
public int Count;
|
||||||
|
|
||||||
[GlobalSetup]
|
[GlobalSetup]
|
||||||
|
|
@ -44,6 +48,9 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
|
|
||||||
_linearSearch = new LinearSearchJumpTable(0, -1, entries.ToArray());
|
_linearSearch = new LinearSearchJumpTable(0, -1, entries.ToArray());
|
||||||
_dictionary = new DictionaryJumpTable(0, -1, entries.ToArray());
|
_dictionary = new DictionaryJumpTable(0, -1, entries.ToArray());
|
||||||
|
Debug.Assert(AsciiKeyedJumpTable.TryCreate(0, -1, entries, out _ascii));
|
||||||
|
_dictionaryLookup = new DictionaryLookupJumpTable(0, -1, entries.ToArray());
|
||||||
|
_customHashTable = new CustomHashTableJumpTable(0, -1, entries.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This baseline is similar to SingleEntryJumpTable. We just want
|
// This baseline is similar to SingleEntryJumpTable. We just want
|
||||||
|
|
@ -104,6 +111,51 @@ namespace Microsoft.AspNetCore.Routing.Matchers
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = 100)]
|
||||||
|
public int Ascii()
|
||||||
|
{
|
||||||
|
var strings = _strings;
|
||||||
|
var segments = _segments;
|
||||||
|
|
||||||
|
var destination = 0;
|
||||||
|
for (var i = 0; i < strings.Length; i++)
|
||||||
|
{
|
||||||
|
destination = _ascii.GetDestination(strings[i], segments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = 100)]
|
||||||
|
public int DictionaryLookup()
|
||||||
|
{
|
||||||
|
var strings = _strings;
|
||||||
|
var segments = _segments;
|
||||||
|
|
||||||
|
var destination = 0;
|
||||||
|
for (var i = 0; i < strings.Length; i++)
|
||||||
|
{
|
||||||
|
destination = _dictionaryLookup.GetDestination(strings[i], segments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark(OperationsPerInvoke = 100)]
|
||||||
|
public int CustomHashTable()
|
||||||
|
{
|
||||||
|
var strings = _strings;
|
||||||
|
var segments = _segments;
|
||||||
|
|
||||||
|
var destination = 0;
|
||||||
|
for (var i = 0; i < strings.Length; i++)
|
||||||
|
{
|
||||||
|
destination = _customHashTable.GetDestination(strings[i], segments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
private static string[] GetStrings(int count)
|
private static string[] GetStrings(int count)
|
||||||
{
|
{
|
||||||
var strings = new string[count];
|
var strings = new string[count];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue