Skip to content

Commit

Permalink
add benchmarks, reduce allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveRuble committed Sep 26, 2021
1 parent a94da2f commit aad7a7b
Show file tree
Hide file tree
Showing 31 changed files with 983 additions and 312 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ coverage.json
.vscode/

# Snapshotter mismatches
**/__mismatch__/
**/__mismatch__/

# Benchmark results
tests/Bloomn.Benchmarks/BenchmarkDotNet.Artifacts
6 changes: 6 additions & 0 deletions Bloomn.sln
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bloomn", "src\Bloomn\Bloomn
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bloomn.Tests", "tests\Bloomn.Tests\Bloomn.Tests.csproj", "{171BBD20-CB99-41CD-9F23-EA7BF99241E4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bloomn.Benchmarks", "tests\Bloomn.Benchmarks\Bloomn.Benchmarks.csproj", "{D00EC957-A24D-4061-907C-F7E9E24D489C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -18,5 +20,9 @@ Global
{171BBD20-CB99-41CD-9F23-EA7BF99241E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{171BBD20-CB99-41CD-9F23-EA7BF99241E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{171BBD20-CB99-41CD-9F23-EA7BF99241E4}.Release|Any CPU.Build.0 = Release|Any CPU
{D00EC957-A24D-4061-907C-F7E9E24D489C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D00EC957-A24D-4061-907C-F7E9E24D489C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D00EC957-A24D-4061-907C-F7E9E24D489C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D00EC957-A24D-4061-907C-F7E9E24D489C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
9 changes: 7 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

Bloomn provides a modern, high performance bloom filter implementation.

### Features
## Features

- Provides a very low allocation API for demanding scenarios
- Provides a simpler API for simpler scenarios
- Bloom filter state can be exported, serialized, and imported
- Integrates with standard .NET dependency injection framework.
- Integrates with standard .NET dependency injection framework


## Examples


7 changes: 7 additions & 0 deletions src/Bloomn/BloomFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Bloomn
{
public static class BloomFilter
{
public static IBloomFilterBuilder<TKey> Builder<TKey>() => new BloomFilterBuilder<TKey>(new BloomFilterOptions<TKey>());
}
}
63 changes: 38 additions & 25 deletions src/Bloomn/BloomFilterBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,14 @@

namespace Bloomn
{
public interface IBloomFilterOptionsBuilder<TKey>
{
IBloomFilterBuilder<TKey> WithCapacityAndErrorRate(int capacity, double errorRate);
IBloomFilterBuilder<TKey> WithDimensions(BloomFilterDimensions dimensions);
IBloomFilterBuilder<TKey> WithScaling(double capacityScaling = 2, double errorRateScaling = 0.8);
IBloomFilterBuilder<TKey> WithHasher(IKeyHasherFactory<TKey> hasherFactory);
}

public interface IBloomFilterBuilder<TKey> : IBloomFilterOptionsBuilder<TKey>
{
IBloomFilterBuilder<TKey> WithOptions(BloomFilterOptions<TKey> options);
IBloomFilterBuilder<TKey> WithProfile(string profile);
IBloomFilterBuilder<TKey> WithState(BloomFilterState state);
IBloomFilter<TKey> Build();
}

internal class BloomFilterBuilder<TKey> : IBloomFilterBuilder<TKey>
internal class BloomFilterBuilder<TKey> : IBloomFilterBuilder<TKey>, IBloomFilterOptionsBuilder<TKey>
{
private bool _validateStateAgainstOptions;
private readonly IOptionsSnapshot<BloomFilterOptions<TKey>>? _optionsSnapshot;

public BloomFilterBuilder(IOptionsSnapshot<BloomFilterOptions<TKey>> options)
{
_validateStateAgainstOptions = true;
_optionsSnapshot = options;
Options = options.Value;
}
Expand All @@ -40,13 +26,15 @@ public BloomFilterBuilder(BloomFilterOptions<TKey> options)

internal BloomFilterState? State { get; set; }

public IBloomFilterBuilder<TKey> WithCapacityAndErrorRate(int capacity, double errorRate)
IBloomFilterOptionsBuilder<TKey> IBloomFilterOptionsBuilder<TKey>.WithCapacityAndFalsePositiveProbability(int capacity, double falsePositiveProbability)
{
return WithDimensions(BloomFilterDimensions.ForCapacityAndErrorRate(capacity, errorRate));
_validateStateAgainstOptions = true;
return WithDimensions(BloomFilterDimensions.ForCapacityAndErrorRate(capacity, falsePositiveProbability));
}

public IBloomFilterBuilder<TKey> WithDimensions(BloomFilterDimensions dimensions)
public IBloomFilterOptionsBuilder<TKey> WithDimensions(BloomFilterDimensions dimensions)
{
_validateStateAgainstOptions = true;
Options.Dimensions = new BloomFilterDimensionsBuilder
{
FalsePositiveProbability = dimensions.FalsePositiveProbability,
Expand All @@ -58,8 +46,9 @@ public IBloomFilterBuilder<TKey> WithDimensions(BloomFilterDimensions dimensions
return this;
}

public IBloomFilterBuilder<TKey> WithScaling(double capacityScaling = 2, double errorRateScaling = 0.8)
public IBloomFilterOptionsBuilder<TKey> WithScaling(double capacityScaling = 2, double errorRateScaling = 0.8)
{
_validateStateAgainstOptions = true;
Options.Scaling = new BloomFilterScaling
{
MaxCapacityBehavior = MaxCapacityBehavior.Scale,
Expand All @@ -69,23 +58,47 @@ public IBloomFilterBuilder<TKey> WithScaling(double capacityScaling = 2, double
return this;
}

public IBloomFilterBuilder<TKey> WithHasher(IKeyHasherFactory<TKey> hasherFactory)
public IBloomFilterOptionsBuilder<TKey> WithHasher(IKeyHasherFactory<TKey> hasherFactory)
{
_validateStateAgainstOptions = true;
Options.SetHasher(hasherFactory);
return this;
}

public IBloomFilterBuilder<TKey> WithOptions(BloomFilterOptions<TKey> options)
{
_validateStateAgainstOptions = true;
Options = options;
return this;
}

public IBloomFilterBuilder<TKey> WithOptions(Action<IBloomFilterOptionsBuilder<TKey>> configure)
{
_validateStateAgainstOptions = true;
configure(this);
return this;
}

public IBloomFilterOptionsBuilder<TKey> WithCallbacks(BloomFilterEvents events)
{
_validateStateAgainstOptions = true;
Options.Events = events;
return this;
}

public IBloomFilterOptionsBuilder<TKey> IgnoreCapacityLimits()
{
_validateStateAgainstOptions = true;
Options.Scaling = Options.Scaling with {MaxCapacityBehavior = MaxCapacityBehavior.Ignore};
return this;
}

public IBloomFilterBuilder<TKey> WithProfile(string profile)
{
_validateStateAgainstOptions = true;
if (_optionsSnapshot == null)
{
throw new InvalidOperationException("This builder was not ");
throw new BloomFilterException(BloomFilterExceptionCode.InvalidOptions, "This builder was not acquired from a service provider that could inject options.");
}

Options = _optionsSnapshot.Get(profile);
Expand All @@ -106,11 +119,11 @@ public IBloomFilter<TKey> Build()
{
Dimensions = Options.GetDimensions(),
Scaling = Options.Scaling,
HashAlgorithm = Options.GetHasher().Algorithm
HashAlgorithm = Options.GetHasherFactory().Algorithm
};

var state = State;
if (state != null)
if (state != null && _validateStateAgainstOptions)
{
var parametersFromState = state?.Parameters;

Expand Down
81 changes: 0 additions & 81 deletions src/Bloomn/BloomFilterDimensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Bloomn
{
Expand Down Expand Up @@ -135,84 +134,4 @@ public static int k(double p)
// ReSharper restore InconsistentNaming
}
}

public class BloomFilterDimensionsBuilder
{
public double? FalsePositiveProbability { get; set; }
public int? Capacity { get; set; }
public int? BitCount { get; set; }
public int? HashCount { get; set; }

[MemberNotNullWhen(true, nameof(FalsePositiveProbability))]
[MemberNotNullWhen(true, nameof(Capacity))]
[MemberNotNullWhen(true, nameof(BitCount))]
[MemberNotNullWhen(true, nameof(HashCount))]
public bool FullySpecified => FalsePositiveProbability != null && Capacity != null && BitCount != null && HashCount != null;

public bool Buildable =>
Capacity.HasValue && FalsePositiveProbability.HasValue
|| FalsePositiveProbability.HasValue && BitCount.HasValue
|| Capacity.HasValue && FalsePositiveProbability.HasValue
|| Capacity.HasValue && BitCount.HasValue
|| FalsePositiveProbability.HasValue && BitCount.HasValue;

public BloomFilterDimensions Build()
{
if (!Buildable)
{
throw new InvalidOperationException("Not enough parameters are set.");
}

var makingProgress = true;
while (!FullySpecified && makingProgress)
{
makingProgress = false;
if (!HashCount.HasValue && Capacity.HasValue && BitCount.HasValue)
{
HashCount = BloomFilterDimensions.Equations.k(BitCount.Value, Capacity.Value);
makingProgress = true;
continue;
}

if (!HashCount.HasValue && FalsePositiveProbability.HasValue)
{
HashCount = BloomFilterDimensions.Equations.k(FalsePositiveProbability.Value);
makingProgress = true;
continue;
}

if (!BitCount.HasValue && Capacity.HasValue && FalsePositiveProbability.HasValue)
{
BitCount = BloomFilterDimensions.Equations.m(Capacity.Value, FalsePositiveProbability.Value);
makingProgress = true;
continue;
}

if (!Capacity.HasValue && BitCount.HasValue && HashCount.HasValue && FalsePositiveProbability.HasValue)
{
Capacity = BloomFilterDimensions.Equations.n(BitCount.Value, HashCount.Value, FalsePositiveProbability.Value);
makingProgress = true;
continue;
}

if (!FalsePositiveProbability.HasValue && BitCount.HasValue && Capacity.HasValue && HashCount.HasValue)
{
FalsePositiveProbability = BloomFilterDimensions.Equations.p(BitCount.Value, Capacity.Value, HashCount.Value);
makingProgress = true;
}
}

if (!FullySpecified)
{
throw new InvalidOperationException($"Could not compute dimensions using provided values: {this}");
}

return new BloomFilterDimensions(FalsePositiveProbability.Value, Capacity.Value, BitCount.Value, HashCount.Value);
}

public override string ToString()
{
return $"{nameof(FalsePositiveProbability)}: {FalsePositiveProbability}, {nameof(Capacity)}: {Capacity}, {nameof(BitCount)}: {BitCount}, {nameof(HashCount)}: {HashCount}";
}
}
}
97 changes: 97 additions & 0 deletions src/Bloomn/BloomFilterDimensionsBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Diagnostics.CodeAnalysis;

namespace Bloomn
{
public class BloomFilterDimensionsBuilder
{
public double? FalsePositiveProbability { get; set; }
public int? Capacity { get; set; }
public int? BitCount { get; set; }
public int? HashCount { get; set; }

[MemberNotNullWhen(true, nameof(FalsePositiveProbability))]
[MemberNotNullWhen(true, nameof(Capacity))]
[MemberNotNullWhen(true, nameof(BitCount))]
[MemberNotNullWhen(true, nameof(HashCount))]
public bool FullySpecified => FalsePositiveProbability != null && Capacity != null && BitCount != null && HashCount != null;

public bool Buildable =>
Capacity.HasValue && FalsePositiveProbability.HasValue
|| FalsePositiveProbability.HasValue && BitCount.HasValue
|| Capacity.HasValue && FalsePositiveProbability.HasValue
|| Capacity.HasValue && BitCount.HasValue
|| FalsePositiveProbability.HasValue && BitCount.HasValue;

public BloomFilterDimensions Build()
{
// Create a copy to mutate during building
return new BloomFilterDimensionsBuilder()
{
FalsePositiveProbability = this.FalsePositiveProbability,
Capacity = this.Capacity,
BitCount = this.BitCount,
HashCount = this.HashCount
}.ReallyBuild();
}

private BloomFilterDimensions ReallyBuild()
{
if (!Buildable)
{
throw new InvalidOperationException("Not enough parameters are set.");
}

var makingProgress = true;
while (!FullySpecified && makingProgress)
{
makingProgress = false;
if (!HashCount.HasValue && Capacity.HasValue && BitCount.HasValue)
{
HashCount = BloomFilterDimensions.Equations.k(BitCount.Value, Capacity.Value);
makingProgress = true;
continue;
}

if (!HashCount.HasValue && FalsePositiveProbability.HasValue)
{
HashCount = BloomFilterDimensions.Equations.k(FalsePositiveProbability.Value);
makingProgress = true;
continue;
}

if (!BitCount.HasValue && Capacity.HasValue && FalsePositiveProbability.HasValue)
{
BitCount = BloomFilterDimensions.Equations.m(Capacity.Value, FalsePositiveProbability.Value);
makingProgress = true;
continue;
}

if (!Capacity.HasValue && BitCount.HasValue && HashCount.HasValue && FalsePositiveProbability.HasValue)
{
Capacity = BloomFilterDimensions.Equations.n(BitCount.Value, HashCount.Value, FalsePositiveProbability.Value);
makingProgress = true;
continue;
}

if (!FalsePositiveProbability.HasValue && BitCount.HasValue && Capacity.HasValue && HashCount.HasValue)
{
FalsePositiveProbability = BloomFilterDimensions.Equations.p(BitCount.Value, Capacity.Value, HashCount.Value);
makingProgress = true;
}
}

if (!FullySpecified)
{
throw new InvalidOperationException($"Could not compute dimensions using provided values: {this}");
}

return new BloomFilterDimensions(FalsePositiveProbability.Value, Capacity.Value, BitCount.Value, HashCount.Value);
}

public override string ToString()
{
return $"{nameof(FalsePositiveProbability)}: {FalsePositiveProbability}, {nameof(Capacity)}: {Capacity}, {nameof(BitCount)}: {BitCount}, {nameof(HashCount)}: {HashCount}";
}
}
}
Loading

0 comments on commit aad7a7b

Please sign in to comment.