Skip to content

Commit

Permalink
Use FNV hash for Key (#1736)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Jan 13, 2024
1 parent fbe0f6b commit 8047b02
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 1 deletion.
35 changes: 35 additions & 0 deletions Jint.Benchmark/HashBenchmark.cs
Original file line number Diff line number Diff line change
@@ -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<byte> s1 = stackalloc byte[Input.Length * 2];
Encoding.UTF8.TryGetBytes(Input, s1, out var written);
return System.IO.Hashing.XxHash3.HashToUInt64(s1[..written]);
}
*/
}
109 changes: 109 additions & 0 deletions Jint/Extensions/Hash.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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
/// </summary>
private const int FnvOffsetBias = unchecked((int)2166136261);

/// <summary>
/// The generative factor used in the FNV-1a algorithm
/// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/// </summary>
private const int FnvPrime = 16777619;

/// <summary>
/// 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.
/// </summary>
internal static int GetFNVHashCode(ReadOnlySpan<char> data) => CombineFNVHash(FnvOffsetBias, data);

/// <summary>
/// Compute the hashcode of a string using FNV-1a
/// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/// </summary>
/// <param name="text">The input string</param>
/// <returns>The FNV-1a hash code of <paramref name="text"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetFNVHashCode(string text) => CombineFNVHash(FnvOffsetBias, text);

/// <summary>
/// Compute the hashcode of a string using FNV-1a
/// See http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
/// </summary>
/// <param name="text">The input string</param>
/// <returns>The FNV-1a hash code of <paramref name="text"/></returns>
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;
}

/// <summary>
/// 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
/// </summary>
/// <param name="hashCode">The accumulated hash code</param>
/// <param name="text">The string to combine</param>
/// <returns>The result of combining <paramref name="hashCode"/> with <paramref name="text"/> using the FNV-1a algorithm</returns>
[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;
}

/// <summary>
/// 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
/// </summary>
/// <param name="hashCode">The accumulated hash code</param>
/// <param name="data">The string to combine</param>
/// <returns>The result of combining <paramref name="hashCode"/> with <paramref name="data"/> using the FNV-1a algorithm</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining | (MethodImplOptions) 512)]
private static int CombineFNVHash(int hashCode, ReadOnlySpan<char> data)
{
for (int i = 0; i < data.Length; i++)
{
hashCode = unchecked((hashCode ^ data[i]) * FnvPrime);
}

return hashCode;
}
}
3 changes: 2 additions & 1 deletion Jint/Key.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Jint.Extensions;

namespace Jint
{
Expand All @@ -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;
Expand Down

0 comments on commit 8047b02

Please sign in to comment.