Skip to content

Commit

Permalink
Add readme to cmap, benchmark results
Browse files Browse the repository at this point in the history
  • Loading branch information
Wsm2110 committed Jul 7, 2024
1 parent e4f70bd commit 66059e2
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 49 deletions.
40 changes: 19 additions & 21 deletions benchmarks/Faster.Map.Concurrent.Benchmark/AddAndResizeBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,36 +47,34 @@ public void Clean()
_concurrentMap = new System.Collections.Concurrent.ConcurrentDictionary<uint, uint>();
}

//[Benchmark]
//public void NonBlocking()
//{
// Parallel.For(0, N, new ParallelOptions { MaxDegreeOfParallelism = NumberOfThreads }, i =>
// {
// var key = keys[i];
// _nonBlocking.TryAdd(key, key);
// });
//}
[Benchmark]
public void NonBlocking()
{
Parallel.For(0, N, new ParallelOptions { MaxDegreeOfParallelism = NumberOfThreads }, i =>
{
var key = keys[i];
_nonBlocking.TryAdd(key, key);
});
}

[Benchmark]
public void CMap()
{
Parallel.For(0, N, new ParallelOptions { MaxDegreeOfParallelism = NumberOfThreads }, i =>
{
var key = keys[i];
_map.Emplace(keys[i], 0);
_map.Emplace(key, key);
});
}

//[Benchmark]
//public void ConcurrentDictionary()
//{
// Parallel.For(0, N, new ParallelOptions { MaxDegreeOfParallelism = NumberOfThreads }, i =>
// {
// var key = keys[i];
// _concurrentMap.TryAdd(key, key);
// });
//}


[Benchmark]
public void ConcurrentDictionary()
{
Parallel.For(0, N, new ParallelOptions { MaxDegreeOfParallelism = NumberOfThreads }, i =>
{
var key = keys[i];
_concurrentMap.TryAdd(key, key);
});
}
}
}
54 changes: 26 additions & 28 deletions src/Concurrent/CMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ public bool Emplace(TKey key, TValue value)
return false;
}

if (entry.Meta is _resizeBucket or _groupResized)
if (entry.Meta == _resizeBucket || entry.Meta == _groupResized)
{
Resize(table);
jumpDistance = 0;
Expand Down Expand Up @@ -327,11 +327,11 @@ public bool Get(TKey key, out TValue value)
return false;
}

if (entry.Meta == _resizeBucket)
if (entry.Meta == _resizeBucket || entry.Meta == _groupResized)
{
Resize(table); // Perform the resize operation
jumpDistance = 0; // Reset the jump distance
goto start; // Restart the lookup process with the new table
Resize(table);
jumpDistance = 0;
goto start;
}

// Increment the jump distance and calculate the next index using triangular probing
Expand Down Expand Up @@ -388,11 +388,11 @@ public bool Update(TKey key, TValue newValue)
}

// If the entry indicates a resize operation, perform the resize
if (_resizeBucket == entry.Meta)
if (entry.Meta == _resizeBucket || entry.Meta == _groupResized)
{
Resize(table); // Resize the table
jumpDistance = 0; // Reset the jump distance
goto start; // Restart the update process with the new table
Resize(table);
jumpDistance = 0;
goto start;
}

// Increment the jump distance and calculate the next index using triangular probing
Expand All @@ -402,7 +402,6 @@ public bool Update(TKey key, TValue newValue)
} while (true); // Continue probing until a matching entry is found or the table is exhausted
}


/// <summary>
/// The Update method is designed to update the value associated with a given key in a concurrent hash table.
/// The method uses aggressive inlining for performance optimization.
Expand Down Expand Up @@ -464,11 +463,12 @@ public bool Update(TKey key, TValue newValue, TValue comparisonValue)
}

// If the entry indicates a resize operation, perform the resize
if (_resizeBucket == entry.Meta)

if (entry.Meta == _resizeBucket || entry.Meta == _groupResized)
{
Resize(table); // Resize the table
jumpDistance = 0; // Reset the jump distance
goto start; // Restart the update process with the new table
Resize(table);
jumpDistance = 0;
goto start;
}

// If the entry indicates an empty bucket, the key does not exist in the table
Expand Down Expand Up @@ -547,7 +547,8 @@ public bool Remove(TKey key)
}

// If the entry indicates a resize operation, perform the resize
if (_resizeBucket == entry.Meta)

if (entry.Meta == _resizeBucket || entry.Meta == _groupResized)
{
Resize(table); // Resize the table
jumpDistance = 0; // Reset the jump distance
Expand Down Expand Up @@ -618,7 +619,7 @@ public bool Remove(TKey key, out TValue value)
}

// If the entry indicates a resize operation, perform the resize
if (_resizeBucket == entry.Meta)
if (entry.Meta == _resizeBucket || entry.Meta == _groupResized)
{
Resize(table); // Resize the table
jumpDistance = 0; // Reset the jump distance
Expand Down Expand Up @@ -744,7 +745,6 @@ internal void Resize(Table table)
// This prevents lost updates and ensures that all threads see the new table once the migration is complete.
// Emphasize that the operation only succeeds if _table still referencestable, thereby preventing conflicts from concurrent resize operations.


if (table._depletedCounter == table._depleted)
{
Interlocked.CompareExchange(ref _table, ctable, table);
Expand Down Expand Up @@ -820,10 +820,9 @@ internal class Table
public uint LengthMinusOne;
public uint Threshold;
public uint Length;

private uint _groups;
private uint _groupsMinusOne;
internal int _depletedCounter;
private int _chunkJackpot;
private int _jackpot;

#endregion

Expand All @@ -843,9 +842,9 @@ public Table(uint length, double _loadFactor)
Entries.AsSpan().Fill(new Entry { Meta = _emptyBucket });

_groupSize = DetermineChunkSize((uint)BitOperations.Log2(length));
_groups = (length / _groupSize) - 1;
_groupsMinusOne = (length / _groupSize) - 1;
_depleted = length * -125;
_chunkJackpot = (int)(_groupSize * _resizeBucket);
_jackpot = (int)(_groupSize * _resizeBucket);
}

private static uint DetermineChunkSize(uint length)
Expand Down Expand Up @@ -900,10 +899,9 @@ internal void Migrate(Table mTable)
{
while (_depletedCounter > _depleted)
{
// process groups
uint groupIndex = Interlocked.Increment(ref _groupIndex) & _groups;
uint groupIndex = Interlocked.Increment(ref _groupIndex) & _groupsMinusOne;
uint index = groupIndex * _groupSize;
uint end = index + _groupSize;
uint end = index + _groupSize;

ref var entry = ref Find(Entries, index);

Expand All @@ -922,8 +920,8 @@ internal void Migrate(Table mTable)
// Cas succeeded
if (result > -1)
{
mTable.EmplaceInternal(ref entry, result);
// Entry has been moved succesfully
mTable.EmplaceInternal(ref entry, result);
}

++index;
Expand All @@ -949,8 +947,8 @@ internal void Migrate(Table mTable)
++index;
} while (index < end);

// only update the depleted counter once every entry in this block is resized
Interlocked.Add(ref _depletedCounter, _chunkJackpot);
// only update the depleted counter once when every entry in this block has been moved to the new table
Interlocked.Add(ref _depletedCounter, _jackpot);
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions src/Concurrent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Faster.Map

CMap is a high-performance, thread-safe, lockfree concurrent hash map that uses open addressing, quadratic probing, and Fibonacci hashing to manage key-value pairs. The default load factor is set to 0.5, meaning the hash map will resize when it is half full.

## Tested on platforms:
* x86
* x64
* arm
* arm64

## Benchmark

The mean is divided by the length

``` ini
BenchmarkDotNet v0.13.8, Windows 11 (10.0.22621.2428/22H2/2022Update/SunValley2)
12th Gen Intel Core i5-12500H, 1 CPU, 16 logical and 12 physical cores
.NET SDK 8.0.100-rc.1.23463.5
[Host] : .NET 8.0.0 (8.0.23.41904), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.0 (8.0.23.41904), X64 RyuJIT AVX2
```
### Retrieving a million pre-generated keys



### Adding a million keys



### Updating a million keys


### Removing a million keys


### Add and resize

| Method | Length | NumberOfThreads | Mean | Error | StdDev | Median | Gen0 | Gen1 | Gen2 | Allocated |
|--------------------- |-------- |---------------- |----------:|---------:|----------:|----------:|-----------:|-----------:|----------:|----------:|
| NonBlocking | 1000000 | 1 | 324.91 ms | 6.408 ms | 9.976 ms | 324.75 ms | 6000.0000 | 4000.0000 | 1000.0000 | 132.23 MB |
| CMap | 1000000 | 1 | 62.97 ms | 1.257 ms | 2.680 ms | 64.08 ms | - | - | - | 48 MB |
| ConcurrentDictionary | 1000000 | 1 | 352.82 ms | 5.890 ms | 5.221 ms | 354.05 ms | 11000.0000 | 10000.0000 | 3000.0000 | 99.34 MB |
| | | | | | | | | | | |
| NonBlocking | 1000000 | 2 | 236.26 ms | 8.065 ms | 23.781 ms | 236.23 ms | 5000.0000 | 3000.0000 | 1000.0000 | 80.28 MB |
| CMap | 1000000 | 2 | 63.72 ms | 2.026 ms | 5.943 ms | 66.36 ms | - | - | - | 48 MB |
| ConcurrentDictionary | 1000000 | 2 | 352.58 ms | 6.963 ms | 7.151 ms | 355.23 ms | 11000.0000 | 10000.0000 | 3000.0000 | 99.79 MB |
| | | | | | | | | | | |
| NonBlocking | 1000000 | 4 | 214.85 ms | 7.172 ms | 21.035 ms | 219.20 ms | 5000.0000 | 3000.0000 | 1000.0000 | 80.73 MB |
| CMap | 1000000 | 4 | 63.48 ms | 2.080 ms | 6.132 ms | 66.42 ms | - | - | - | 48 MB |
| ConcurrentDictionary | 1000000 | 4 | 338.31 ms | 6.734 ms | 8.756 ms | 339.09 ms | 11000.0000 | 10000.0000 | 3000.0000 | 100.26 MB |
| | | | | | | | | | | |
| NonBlocking | 1000000 | 8 | 192.75 ms | 8.312 ms | 24.507 ms | 198.89 ms | 6000.0000 | 3000.0000 | 1000.0000 | 134.5 MB |
| CMap | 1000000 | 8 | 66.75 ms | 2.541 ms | 7.493 ms | 69.98 ms | - | - | - | 48 MB |
| ConcurrentDictionary | 1000000 | 8 | 336.02 ms | 6.541 ms | 6.424 ms | 338.61 ms | 11000.0000 | 10000.0000 | 3000.0000 | 99.93 MB |
| | | | | | | | | | | |
| NonBlocking | 1000000 | 16 | 173.72 ms | 5.921 ms | 17.366 ms | 176.90 ms | 6000.0000 | 4000.0000 | 1000.0000 | 135.08 MB |
| CMap | 1000000 | 16 | 59.39 ms | 1.180 ms | 2.411 ms | 59.35 ms | - | - | - | 48.01 MB |
| ConcurrentDictionary | 1000000 | 16 | 337.56 ms | 5.460 ms | 4.840 ms | 338.32 ms | 11000.0000 | 10000.0000 | 3000.0000 | 99.49 MB |
### Add string benchmark


### Create StringWrapperBenchmark (cached hashcode)

0 comments on commit 66059e2

Please sign in to comment.