diff --git a/Jint.Benchmark/HashBenchmark.cs b/Jint.Benchmark/HashBenchmark.cs new file mode 100644 index 0000000000..01b8b41386 --- /dev/null +++ b/Jint.Benchmark/HashBenchmark.cs @@ -0,0 +1,35 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; +using Jint.Extensions; + +namespace Jint.Benchmark; + +[RankColumn] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByParams)] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class HashBenchmark +{ + + [Params("i", "str", "Math", "encodeURIComponent")] + public string Input { get; set; } + + [Benchmark(Baseline = true)] + public int StringHashCode() => Input.GetHashCode(); + + [Benchmark] + public int StringOrdinalHashCode() => StringComparer.Ordinal.GetHashCode(Input); + + [Benchmark] + public int Fnv1() => Hash.GetFNVHashCode(Input); + + /* + [Benchmark] + public ulong Hash3() + { + Span s1 = stackalloc byte[Input.Length * 2]; + Encoding.UTF8.TryGetBytes(Input, s1, out var written); + return System.IO.Hashing.XxHash3.HashToUInt64(s1[..written]); + } + */ +} diff --git a/Jint/Extensions/Hash.cs b/Jint/Extensions/Hash.cs new file mode 100644 index 0000000000..dace6edf27 --- /dev/null +++ b/Jint/Extensions/Hash.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// taken from and removed unused methods +// https://github.com/dotnet/roslyn/blob/8a7ca9af3360ef388b6dad61c95e2d0629d7a032/src/Compilers/Core/Portable/InternalUtilities/Hash.cs + +using System.Runtime.CompilerServices; + +namespace Jint.Extensions; + +internal static class Hash +{ + /// + /// The offset bias value used in the FNV-1a algorithm + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + private const int FnvOffsetBias = unchecked((int)2166136261); + + /// + /// The generative factor used in the FNV-1a algorithm + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + private const int FnvPrime = 16777619; + + /// + /// Compute the hashcode of a sub-string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// Note: FNV-1a was developed and tuned for 8-bit sequences. We're using it here + /// for 16-bit Unicode chars on the understanding that the majority of chars will + /// fit into 8-bits and, therefore, the algorithm will retain its desirable traits + /// for generating hash codes. + /// + internal static int GetFNVHashCode(ReadOnlySpan data) => CombineFNVHash(FnvOffsetBias, data); + + /// + /// Compute the hashcode of a string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The input string + /// The FNV-1a hash code of + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GetFNVHashCode(string text) => CombineFNVHash(FnvOffsetBias, text); + + /// + /// Compute the hashcode of a string using FNV-1a + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The input string + /// The FNV-1a hash code of + internal static int GetFNVHashCode(System.Text.StringBuilder text) + { + int hashCode = FnvOffsetBias; + +#if NETCOREAPP3_1_OR_GREATER + foreach (var chunk in text.GetChunks()) + { + hashCode = CombineFNVHash(hashCode, chunk.Span); + } +#else + // StringBuilder.GetChunks is not available in this target framework. Since there is no other direct access + // to the underlying storage spans of StringBuilder, we fall back to using slower per-character operations. + int end = text.Length; + + for (int i = 0; i < end; i++) + { + hashCode = unchecked((hashCode ^ text[i]) * FnvPrime); + } +#endif + + return hashCode; + } + + /// + /// Combine a string with an existing FNV-1a hash code + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The accumulated hash code + /// The string to combine + /// The result of combining with using the FNV-1a algorithm + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)] + private static int CombineFNVHash(int hashCode, string text) + { + foreach (char ch in text) + { + hashCode = unchecked((hashCode ^ ch) * FnvPrime); + } + + return hashCode; + } + + /// + /// Combine a string with an existing FNV-1a hash code + /// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + /// + /// The accumulated hash code + /// The string to combine + /// The result of combining with using the FNV-1a algorithm + [MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)] + private static int CombineFNVHash(int hashCode, ReadOnlySpan data) + { + for (int i = 0; i < data.Length; i++) + { + hashCode = unchecked((hashCode ^ data[i]) * FnvPrime); + } + + return hashCode; + } +} diff --git a/Jint/Key.cs b/Jint/Key.cs index dd3a55bbf6..158ecee926 100644 --- a/Jint/Key.cs +++ b/Jint/Key.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +using Jint.Extensions; namespace Jint { @@ -14,7 +15,7 @@ namespace Jint private Key(string name) { Name = name; - HashCode = StringComparer.Ordinal.GetHashCode(name); + HashCode = Hash.GetFNVHashCode(name); } internal readonly string Name;