Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use Xxhash3 for StorageKey.GetHashCode #3559

Merged
merged 12 commits into from
Nov 6, 2024
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,6 @@ paket-files/
PublishProfiles
/.vscode
launchSettings.json

# Benchmarks
**/BenchmarkDotNet.Artifacts/
56 changes: 56 additions & 0 deletions benchmarks/Neo.Benchmarks/Benchmarks.Hash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// Benchmarks.Hash.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using BenchmarkDotNet.Attributes;
using Neo.Cryptography;
using Neo.Extensions;
using System.Diagnostics;
using System.IO.Hashing;
using System.Text;

namespace Neo.Benchmark;

public class Benchmarks_Hash
{
// 256 KiB
static readonly byte[] data = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("Hello, World!^_^", 16 * 1024)));

static readonly byte[] hash = "9182abedfbb9b18d81a05d8bcb45489e7daa2858".HexToBytes();

[Benchmark]
public void RIPEMD160_ComputeHash()
{
using var ripemd160 = new RIPEMD160Managed();
var result = ripemd160.ComputeHash(data);
Debug.Assert(result.SequenceEqual(hash));
}

[Benchmark]
public void XxHash32_HashToUInt32()
{
var result = XxHash32.HashToUInt32(data);
Debug.Assert(result == 682967318u);
}

[Benchmark]
public void XxHash3_HashToUInt64()
{
var result = (uint)XxHash3.HashToUInt64(data);
Debug.Assert(result == 1389469485u);
}

[Benchmark]
public void Murmur32_HashToUInt32()
{
var result = data.Murmur32(0);
Debug.Assert(result == 3731881930u);
}
}
1 change: 1 addition & 0 deletions benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
<ProjectReference Include="..\..\src\Neo\Neo.csproj" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions benchmarks/Neo.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@

// BenchmarkRunner.Run<Benchmarks_PoCs>();
BenchmarkRunner.Run<Benchmarks_UInt160>();
BenchmarkRunner.Run<Benchmarks_Hash>();
25 changes: 25 additions & 0 deletions src/Neo/Cryptography/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Buffers.Binary;
using System.IO.Hashing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -117,6 +118,30 @@ public static byte[] Sha256(this byte[] value)
return sha256.ComputeHash(value);
}

/// <summary>
/// Computes the 32-bit hash value for the specified byte array using the xxhash3 algorithm.
/// </summary>
/// <param name="value">The input to compute the hash code for.</param>
/// <param name="seed">The seed used by the xxhash3 algorithm.</param>
/// <returns>The computed hash code.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint XxHash3_32(this ReadOnlySpan<byte> value, uint seed = 0)
{
return (uint)XxHash3.HashToUInt64(value, seed);
shargon marked this conversation as resolved.
Show resolved Hide resolved
}

/// <summary>
/// Computes the 32-bit hash value for the specified byte array using the xxhash3 algorithm.
/// </summary>
/// <param name="value">The input to compute the hash code for.</param>
/// <param name="seed">The seed used by the xxhash3 algorithm.</param>
/// <returns>The computed hash code.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint XxHash3_32(this byte[] value, uint seed = 0)
{
return XxHash3_32(value.AsSpan(), seed);
}

/// <summary>
/// Computes the hash value for the specified region of the specified byte array using the sha256 algorithm.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Neo/Neo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 10 additions & 1 deletion src/Neo/SmartContract/StorageKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ public sealed record StorageKey

private byte[] cache = null;

// NOTE: StorageKey is readonly, so we can cache the hash code.
private int _hashCode = 0;

public StorageKey() { }

/// <summary>
/// Initializes a new instance of the <see cref="StorageKey"/> class.
/// </summary>
/// <param name="cache">The cached byte array. NOTE: It must be read-only and can be modified by the caller.</param>
internal StorageKey(byte[] cache)
{
this.cache = cache;
Expand Down Expand Up @@ -67,7 +74,9 @@ public bool Equals(StorageKey other)

public override int GetHashCode()
{
return Id + (int)Key.Span.Murmur32(0);
if (_hashCode == 0)
shargon marked this conversation as resolved.
Show resolved Hide resolved
_hashCode = Id + (int)Key.Span.XxHash3_32();
return _hashCode;
}

public byte[] ToArray()
Expand Down
17 changes: 13 additions & 4 deletions tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
using Neo.Wallets;
using Neo.Wallets.NEP6;
using System;
using System.Collections.Generic;
using System.IO.Hashing;
using System.Linq;
using System.Text;

Expand Down Expand Up @@ -55,13 +57,21 @@ public void TestMurmurReadOnlySpan()
input.Murmur128(0).Should().Equal(input2.Murmur128(0));
}

[TestMethod]
public void TestXxHash3()
{
byte[] data = Encoding.ASCII.GetBytes(string.Concat(Enumerable.Repeat("Hello, World!^_^", 16 * 1024)));
data.XxHash3_32().Should().Be(1389469485u);
XxHash32.HashToUInt32(data).Should().Be(682967318u);
shargon marked this conversation as resolved.
Show resolved Hide resolved
data.Murmur32(0).Should().Be(3731881930u);
}

[TestMethod]
public void TestSha256()
{
byte[] value = Encoding.ASCII.GetBytes("hello world");
byte[] result = value.Sha256(0, value.Length);
string resultStr = result.ToHexString();
resultStr.Should().Be("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9");
result.ToHexString().Should().Be("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9");
value.Sha256().Should().Equal(result);
((Span<byte>)value).Sha256().Should().Equal(result);
((ReadOnlySpan<byte>)value).Sha256().Should().Equal(result);
Expand All @@ -82,8 +92,7 @@ public void TestRIPEMD160()
{
ReadOnlySpan<byte> value = Encoding.ASCII.GetBytes("hello world");
byte[] result = value.RIPEMD160();
string resultStr = result.ToHexString();
resultStr.Should().Be("98c615784ccb5fe5936fbc0cbe9dfdb408d92f0f");
result.ToHexString().Should().Be("98c615784ccb5fe5936fbc0cbe9dfdb408d92f0f");
}

[TestMethod]
Expand Down
2 changes: 1 addition & 1 deletion tests/Neo.UnitTests/Ledger/UT_StorageKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void Equals_SameHash_DiffKey()
public void GetHashCode_Get()
{
StorageKey uut = new() { Id = 0x42000000, Key = TestUtils.GetByteArray(10, 0x42) };
uut.GetHashCode().Should().Be(1374529787);
uut.GetHashCode().Should().Be(-729135550);
}

[TestMethod]
Expand Down
Loading