Skip to content

Commit

Permalink
Add FastHash to Faster
Browse files Browse the repository at this point in the history
  • Loading branch information
Wsm2110 committed Nov 22, 2024
1 parent 6741719 commit a3bc563
Show file tree
Hide file tree
Showing 13 changed files with 591 additions and 111 deletions.
51 changes: 51 additions & 0 deletions Faster.Map.Hash.Benchmark/Benchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using BenchmarkDotNet.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Faster.Map.Hash.Benchmark
{
public class Benchmark
{
private FastHash _hash = new FastHash();
private string source;

Check warning on line 14 in Faster.Map.Hash.Benchmark/Benchmark.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable field 'source' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

[Params(10, 16, 32, 48, 100)]
public uint StringLength { get; set; }

[Params(10, 100, 1000, 10000)]
public uint Iterations { get; set; }

[GlobalSetup]
public void Setup()
{
FastHash.CreateSeed();

var data = Enumerable.Range(0, (int)StringLength).Select(i => (char)i).ToArray();

source = new string(data);
}

[Benchmark]
public void RunFastHash()
{
for (int i = 0; i < Iterations; ++i)
{
FastHash.HashU64(MemoryMarshal.AsBytes(source.AsSpan()));
}
}

[Benchmark]
public void RunMarvinHash()
{
for (int i = 0; i < Iterations; ++i)
{
source.GetHashCode();
}
}

}
}
20 changes: 20 additions & 0 deletions Faster.Map.Hash.Benchmark/Faster.Map.Hash.Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" />
<PackageReference Include="ObjectLayoutInspector" Version="0.1.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\Faster.Map.csproj" />
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions Faster.Map.Hash.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
using Faster.Map.Hash.Benchmark;

BenchmarkRunner.Run<Benchmark>(new DebugInProcessConfig());
179 changes: 179 additions & 0 deletions Faster.Map.Hash/FastHashTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Linq;
using System.Text;
using Xunit;

namespace Faster.Map.Hash;
public class FastHashLargeStringTests
{
private const int LargeSize = 10_000_000; // 10 million characters


[Fact]
public void HashU64_LargeString_AllSameCharacters()
{
// Arrange
string input = new string('A', LargeSize);
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(input);

// Act
ulong hash = FastHash.HashU64(bytes);

// Assert
Assert.NotEqual(0UL, hash);
}

[Fact]
public void HashU64_LargeString_RepeatingPattern()
{
// Arrange
string pattern = "1234567890";
int repeatCount = LargeSize / pattern.Length;
string input = string.Concat(Enumerable.Repeat(pattern, repeatCount));
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(input);

// Act
ulong hash = FastHash.HashU64(bytes);

// Assert
Assert.NotEqual(0UL, hash);
}

[Fact]
public void HashU64_LargeString_RandomCharacters()
{
// Arrange
string input = GenerateRandomString(LargeSize);
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(input);

// Act
ulong hash = FastHash.HashU64(bytes);

// Assert
Assert.NotEqual(0UL, hash);
}

[Fact]
public void HashU64Secure_LargeString_AllSameCharacters()
{
// Arrange
string input = new string('Z', LargeSize);
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(input);

// Act
ulong hash = FastHash.HashU64Secure(bytes);

// Assert
Assert.NotEqual(0UL, hash);
}

[Fact]
public void HashU64Secure_LargeString_RepeatingPattern()
{
// Arrange
string pattern = "abcdefghij";
int repeatCount = LargeSize / pattern.Length;
string input = string.Concat(Enumerable.Repeat(pattern, repeatCount));
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(input);

// Act
ulong hash = FastHash.HashU64Secure(bytes);

// Assert
Assert.NotEqual(0UL, hash);
}

[Fact]
public void HashU64Secure_LargeString_RandomCharacters()
{
// Arrange
string input = GenerateRandomString(LargeSize);
ReadOnlySpan<byte> bytes = Encoding.UTF8.GetBytes(input);

// Act
ulong hash = FastHash.HashU64Secure(bytes);

// Assert
Assert.NotEqual(0UL, hash);
}

[Fact]
public void HashU64_LargeString_DifferentStrings_GenerateDifferentHashes()
{
// Arrange
string input1 = new string('X', LargeSize);
string input2 = new string('Y', LargeSize);
ReadOnlySpan<byte> bytes1 = Encoding.UTF8.GetBytes(input1);
ReadOnlySpan<byte> bytes2 = Encoding.UTF8.GetBytes(input2);

// Act
ulong hash1 = FastHash.HashU64(bytes1);
ulong hash2 = FastHash.HashU64(bytes2);

// Assert
Assert.NotEqual(hash1, hash2);
}

[Fact]
public void HashU64Secure_LargeString_DifferentStrings_GenerateDifferentHashes()
{
// Arrange
string input1 = GenerateRandomString(LargeSize);
string input2 = GenerateRandomString(LargeSize);
ReadOnlySpan<byte> bytes1 = Encoding.UTF8.GetBytes(input1);
ReadOnlySpan<byte> bytes2 = Encoding.UTF8.GetBytes(input2);

// Act
ulong hash1 = FastHash.HashU64Secure(bytes1);
ulong hash2 = FastHash.HashU64Secure(bytes2);

// Assert
Assert.NotEqual(hash1, hash2);
}

[Fact]
public void HashU64_LargeString_SubstringHashesDiffer()
{
// Arrange
string input = GenerateRandomString(LargeSize);
ReadOnlySpan<byte> fullBytes = Encoding.UTF8.GetBytes(input);
ReadOnlySpan<byte> substringBytes = Encoding.UTF8.GetBytes(input.Substring(0, LargeSize / 2));

// Act
ulong fullHash = FastHash.HashU64(fullBytes);
ulong substringHash = FastHash.HashU64(substringBytes);

// Assert
Assert.NotEqual(fullHash, substringHash);
}

[Fact]
public void HashU64Secure_LargeString_SubstringHashesDiffer()
{
// Arrange
string input = new string('A', LargeSize);
ReadOnlySpan<byte> fullBytes = Encoding.UTF8.GetBytes(input);
ReadOnlySpan<byte> substringBytes = Encoding.UTF8.GetBytes(input.Substring(0, LargeSize / 2));

// Act
ulong fullHash = FastHash.HashU64Secure(fullBytes);
ulong substringHash = FastHash.HashU64Secure(substringBytes);

// Assert
Assert.NotEqual(fullHash, substringHash);
}

private string GenerateRandomString(int length)
{
Random random = new Random();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
char[] result = new char[length];

for (int i = 0; i < length; i++)
{
result[i] = chars[random.Next(chars.Length)];
}

return new string(result);
}
}
25 changes: 25 additions & 0 deletions Faster.Map.Hash/Faster.Map.Hash.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\src\Faster.Map.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions Faster.Map.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Faster.Map.CMap.Tests", "un
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Faster.Map", "src\Faster.Map.csproj", "{205E2F61-AB6D-4BE2-B9C5-DAE5DED816B6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Faster.Map.Hash", "Faster.Map.Hash\Faster.Map.Hash.csproj", "{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Faster.Map.Hash.Benchmark", "Faster.Map.Hash.Benchmark\Faster.Map.Hash.Benchmark.csproj", "{49D932DA-0606-44CB-8E38-34EC2ADD24CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -77,6 +81,22 @@ Global
{205E2F61-AB6D-4BE2-B9C5-DAE5DED816B6}.Release|Any CPU.Build.0 = Release|Any CPU
{205E2F61-AB6D-4BE2-B9C5-DAE5DED816B6}.Release|x86.ActiveCfg = Release|Any CPU
{205E2F61-AB6D-4BE2-B9C5-DAE5DED816B6}.Release|x86.Build.0 = Release|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Debug|x86.ActiveCfg = Debug|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Debug|x86.Build.0 = Debug|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Release|Any CPU.Build.0 = Release|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Release|x86.ActiveCfg = Release|Any CPU
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F}.Release|x86.Build.0 = Release|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Debug|x86.Build.0 = Debug|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Release|Any CPU.Build.0 = Release|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Release|x86.ActiveCfg = Release|Any CPU
{49D932DA-0606-44CB-8E38-34EC2ADD24CD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -88,6 +108,8 @@ Global
{C5F9A7E0-085D-4BA9-BD18-BDEDF987C35E} = {D14F1A94-1E12-40B7-95AD-458306CAC233}
{C917D320-324E-4696-BE76-18C967ABDAAD} = {D14F1A94-1E12-40B7-95AD-458306CAC233}
{205E2F61-AB6D-4BE2-B9C5-DAE5DED816B6} = {21A59AB1-1ABE-404A-972E-21ECAE7DC24A}
{D9DE8A4A-AC16-4AB8-B939-9280F12C977F} = {D14F1A94-1E12-40B7-95AD-458306CAC233}
{49D932DA-0606-44CB-8E38-34EC2ADD24CD} = {717F512C-A5FC-4B01-9532-17D2663884A0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B844430C-F4DE-4B2B-BD01-0E455BD01E41}
Expand Down
Loading

0 comments on commit a3bc563

Please sign in to comment.