Skip to content

Commit

Permalink
Merge pull request #60 from LeeCampbell/LeadingZeroCountBenchmark
Browse files Browse the repository at this point in the history
Leading zero count benchmark
  • Loading branch information
LeeCampbell authored Jul 17, 2017
2 parents a8c2a7e + d34e0a7 commit 41b16b0
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 10 deletions.
4 changes: 2 additions & 2 deletions HdrHistogram.Benchmarking/HdrHistogram.Benchmarking.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.6" />
<PackageReference Include="BenchmarkDotNet" Version="0.10.8" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net47'">
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows">
<Version>0.10.6</Version>
<Version>0.10.8</Version>
</PackageReference>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;

namespace HdrHistogram.Benchmarking.LeadingZeroCount
{
/// <summary>
/// Contributed from @BBarry at https://github.com/HdrHistogram/HdrHistogram.NET/issues/42
/// This variation perfoms very well. Similar profile to the "Current Impl".
/// Faster on LegacyJIT/CLR, but much slower on RyuJIT (CLR & Core)
/// </summary>
public static class BBarry32BitIfShiftLookupWith64BitShiftBranch
{
private static readonly int[] Lookup;

static BBarry32BitIfShiftLookupWith64BitShiftBranch()
{
Lookup = new int[256];
for (int i = 1; i < 256; ++i)
{
Lookup[i] = (int)(Math.Log(i) / Math.Log(2));
}
}
public static int GetLeadingZeroCount(long value)
{
//TODO: Test this with just < instead of <=? i.e. const of int.Max+1;
if (value <= int.MaxValue)
return 63 - Log2((int)value);
if (value <= uint.MaxValue)
return 62 - Log2((int)(value >> 1));
return 31 - Log2((int)(value >> 32));
}

private static int Log2(int i)
{
if (i >= 0x1000000) { return Lookup[i >> 24] + 24; }
if (i >= 0x10000) { return Lookup[i >> 16] + 16; }
if (i >= 0x100) { return Lookup[i >> 8] + 8; }
return Lookup[i];
}
}

/// <summary>
/// Contributed from @BBarry at https://github.com/HdrHistogram/HdrHistogram.NET/issues/42
/// This variation perfoms very well. Similar profile to the "Current Impl".
/// Faster on LegacyJIT/CLR, but much slower on RyuJIT (CLR & Core)
/// </summary>
public static class BBarry32BitIfShiftLookupWith64BitShiftBranch_2
{
private static readonly int[] Lookup;
static BBarry32BitIfShiftLookupWith64BitShiftBranch_2()
{
Lookup = new int[256];
for (int i = 1; i < 256; ++i)
{
Lookup[i] = (int)(Math.Log(i) / Math.Log(2));
}
}
public static int GetLeadingZeroCount(long value)
{
if (value <= int.MaxValue)
return 63 - Log2((uint)value);
return 32 - Log2((uint)(value >> 31));
}

private static int Log2(uint i)
{
if (i >= 0x1000000) { return Lookup[i >> 24] + 24; }
if (i >= 0x10000) { return Lookup[i >> 16] + 16; }
if (i >= 0x100) { return Lookup[i >> 8] + 8; }
return Lookup[i];
}
}

/// <summary>
/// Contributed from @BBarry at https://github.com/HdrHistogram/HdrHistogram.NET/issues/42
/// This variation perfoms very well. Similar profile to the "Current Impl".
/// Faster on LegacyJIT/CLR, but much slower on RyuJIT (CLR & Core)
/// This just compares only lessThan operator instead of lessThanEqualTo. #completeness
/// </summary>
public static class BBarry32BitIfShiftLookupWith64BitShiftBranch_3
{
private static readonly int[] Lookup;
private const long IntOverflow = int.MaxValue + 1L;
static BBarry32BitIfShiftLookupWith64BitShiftBranch_3()
{
Lookup = new int[256];
for (int i = 1; i < 256; ++i)
{
Lookup[i] = (int)(Math.Log(i) / Math.Log(2));
}
}
public static int GetLeadingZeroCount(long value)
{
if (value < IntOverflow)
return 63 - Log2((uint)value);
return 32 - Log2((uint)(value >> 31));
}

private const int Bit24Range = 0x1000000 - 1;
private const int Bit16Range = 0x10000 - 1;
private const int Bit8Range = 0x100 - 1;
private static int Log2(uint i)
{
if (i > Bit24Range) { return Lookup[i >> 24] + 24; }
if (i > Bit16Range) { return Lookup[i >> 16] + 16; }
if (i > Bit8Range) { return Lookup[i >> 8] + 8; }
return Lookup[i];
}
}
}
36 changes: 36 additions & 0 deletions HdrHistogram.Benchmarking/LeadingZeroCount/BBarryIfShiftLookup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;

namespace HdrHistogram.Benchmarking.LeadingZeroCount
{
/// <summary>
/// Contributed from @BBarry at https://github.com/HdrHistogram/HdrHistogram.NET/issues/42
/// This variation inlines all the shifts.
/// It performs on par on LegacyJIT/CLR but significantly slower on RyuJIT.
/// I assume it is because 7 branches vs the 4 above.
/// </summary>
internal static class BBarryIfShiftLookup
{
private static readonly int[] Lookup;

static BBarryIfShiftLookup()
{
Lookup = new int[256];
for (int i = 1; i < 256; ++i)
{
Lookup[i] = (int)(Math.Log(i) / Math.Log(2));
}
}

public static int GetLeadingZeroCount(long value)
{
if (value >= 0x100000000000000) { return 7 - Lookup[value >> 56]; }
if (value >= 0x1000000000000) { return 15 - Lookup[value >> 48]; }
if (value >= 0x10000000000) { return 23 - Lookup[value >> 40]; }
if (value >= 0x100000000) { return 31 - Lookup[value >> 32]; }
if (value >= 0x1000000) { return 39 - Lookup[value >> 24]; }
if (value >= 0x10000) { return 47 - Lookup[value >> 16]; }
if (value >= 0x100) { return 55 - Lookup[value >> 8]; }
return 63 - Lookup[value];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,23 @@ namespace HdrHistogram.Benchmarking.LeadingZeroCount
/// </remarks>
public abstract class LeadingZeroCountBenchmarkBase
{
private readonly int _maxBit;
private readonly long[] _testValues;

protected LeadingZeroCountBenchmarkBase(int maxBit)
{
_maxBit = maxBit;


//Create array of +ve numbers in the 'maxBit' bit range (i.e. 32 bit or 64bit)
var expectedData = GenerateTestData(maxBit);
_testValues = expectedData.Select(d => d.Value).ToArray();
}

[BenchmarkDotNet.Attributes.GlobalSetup]
public void OneOffValidationOfImplementations()
{
var expectedData = GenerateTestData(_maxBit);
var functions = new Dictionary<string, Func<long, int>>
{
{"CurrentImpl", Bitwise.NumberOfLeadingZeros},
Expand All @@ -41,13 +54,12 @@ protected LeadingZeroCountBenchmarkBase(int maxBit)
{"DeBruijn64BitsBitScanner", LeadingZeroCount.DeBruijn64BitsBitScanner.GetLeadingZeroCount},
{"DeBruijnMultiplication", LeadingZeroCount.DeBruijnMultiplication.GetLeadingZeroCount},
{"DeBruijn128Bits", LeadingZeroCount.DeBruijn128Bits.GetLeadingZeroCount},
{"BBarry32BitIfShiftLookupWith64BitShiftBranch", LeadingZeroCount.BBarry32BitIfShiftLookupWith64BitShiftBranch.GetLeadingZeroCount},
{"BBarry32BitIfShiftLookupWith64BitShiftBranch_2", LeadingZeroCount.BBarry32BitIfShiftLookupWith64BitShiftBranch_2.GetLeadingZeroCount},
{"BBarry32BitIfShiftLookupWith64BitShiftBranch_3", LeadingZeroCount.BBarry32BitIfShiftLookupWith64BitShiftBranch_3.GetLeadingZeroCount},
{"BBarryIfShiftLookup", LeadingZeroCount.BBarryIfShiftLookup.GetLeadingZeroCount},
};

//Create array of +ve numbers in the 'maxBit' bit range (i.e. 32 bit or 64bit)
var expectedData = GenerateTestData(maxBit);
ValidateImplementations(expectedData, functions);

_testValues = expectedData.Select(d => d.Value).ToArray();
}

private static CalculationExpectation[] GenerateTestData(int maxBit)
Expand Down Expand Up @@ -157,7 +169,7 @@ public int Debruijn128Bit()
}
return sum;
}

[Benchmark]
public int StringManipulation()
{
Expand All @@ -168,7 +180,48 @@ public int StringManipulation()
}
return sum;
}

[Benchmark]
public int BBarry_imp1()
{
var sum = 0;
for (int i = 0; i < _testValues.Length; i++)
{
sum += LeadingZeroCount.BBarry32BitIfShiftLookupWith64BitShiftBranch.GetLeadingZeroCount(_testValues[i]);
}
return sum;
}
[Benchmark]
public int BBarry_imp2()
{
var sum = 0;
for (int i = 0; i < _testValues.Length; i++)
{
sum += LeadingZeroCount.BBarryIfShiftLookup.GetLeadingZeroCount(_testValues[i]);
}
return sum;
}
[Benchmark]
public int BBarry_imp3()
{
var sum = 0;
for (int i = 0; i < _testValues.Length; i++)
{
sum += LeadingZeroCount.BBarry32BitIfShiftLookupWith64BitShiftBranch_2.GetLeadingZeroCount(_testValues[i]);
}
return sum;
}
[Benchmark]
public int BBarry_imp4()
{
var sum = 0;
for (int i = 0; i < _testValues.Length; i++)
{
sum += LeadingZeroCount.BBarry32BitIfShiftLookupWith64BitShiftBranch_3.GetLeadingZeroCount(_testValues[i]);
}
return sum;
}


private class CalculationExpectation
{
public long Value { get; }
Expand Down
8 changes: 7 additions & 1 deletion build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ if [%1]==[] (
)

dotnet restore -v=q
IF %ERRORLEVEL% NEQ 0 GOTO EOF

dotnet build -v=q -c=Release /p:Version=%SemVer%
IF %ERRORLEVEL% NEQ 0 GOTO EOF

dotnet test .\HdrHistogram.UnitTests\HdrHistogram.UnitTests.csproj -v=q --no-build -c=Release
IF %ERRORLEVEL% NEQ 0 GOTO EOF

dotnet pack .\HdrHistogram\HdrHistogram.csproj --no-build --include-symbols -c=Release /p:Version=%SemVer%
dotnet pack .\HdrHistogram\HdrHistogram.csproj --no-build --include-symbols -c=Release /p:Version=%SemVer%
IF %ERRORLEVEL% NEQ 0 GOTO EOF

.\HdrHistogram.Benchmarking\bin\Release\net47\HdrHistogram.Benchmarking.exe *

0 comments on commit 41b16b0

Please sign in to comment.