Skip to content

Commit

Permalink
Add update benchmark add update method which updates only if the valu…
Browse files Browse the repository at this point in the history
…e hasnt changed
  • Loading branch information
Wsm2110 committed Jun 8, 2024
1 parent ae7afd9 commit 70071ca
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 87 deletions.
2 changes: 1 addition & 1 deletion benchmarks/Faster.Map.Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<AddBenchmark>();
BenchmarkRunner.Run<UpdateBenchmark>();
}
}
}
2 changes: 1 addition & 1 deletion benchmarks/Faster.Map.Concurrent.Benchmark/AddBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Faster.Map.Concurrent.Benchmark
{

[MarkdownExporterAttribute.GitHub]
[MemoryDiagnoser]
public class AddBenchmark
{
Expand Down
5 changes: 3 additions & 2 deletions benchmarks/Faster.Map.Concurrent.Benchmark/GetBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

namespace Faster.Map.Concurrent.Benchmark
{
[MarkdownExporterAttribute.GitHub]
[MemoryDiagnoser]
public class GetBenchmark
{
CMap<uint, uint> _map = new CMap<uint, uint>();
NonBlocking.ConcurrentDictionary<uint, uint> _block = new NonBlocking.ConcurrentDictionary<uint, uint>();
System.Collections.Concurrent.ConcurrentDictionary<uint, uint> _dic
;
System.Collections.Concurrent.ConcurrentDictionary<uint, uint> _dic ;

[Params(1000000)]
public uint Length { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Faster.Map.Concurrent.Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class Program
{
static void Main(string[] args)
{
BenchmarkRunner.Run<GetBenchmark>();
BenchmarkRunner.Run<UpdateBenchmark>();
}
}
}
116 changes: 116 additions & 0 deletions benchmarks/Faster.Map.Concurrent.Benchmark/UpdateBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Faster.Map.DenseMap;
using Faster.Map.QuadMap;
using Faster.Map.RobinHoodMap;

namespace Faster.Map.Concurrent.Benchmark
{
[MarkdownExporterAttribute.GitHub]
[MemoryDiagnoser]
public class UpdateBenchmark
{
private CMap<uint, uint> _map;
private System.Collections.Concurrent.ConcurrentDictionary<uint, uint> _concurrentMap;
private NonBlocking.ConcurrentDictionary<uint, uint> _nonBlocking;

[Params(1000000)]
public uint Length { get; set; }
private uint[] keys;

private const int N = 1000000; // Adjust as needed for your scale

[Params(1, 8, 16, 32, 64, 128 /*, 256, 512*/)] // Example thread counts to test scalability
public int NumberOfThreads { get; set; }

[GlobalSetup]
public void Setup()
{
_map = new CMap<uint, uint>(2000000);
_nonBlocking = new NonBlocking.ConcurrentDictionary<uint, uint>(NumberOfThreads, 2000000);
_concurrentMap = new System.Collections.Concurrent.ConcurrentDictionary<uint, uint>(NumberOfThreads, 1000000);

var output = File.ReadAllText("Numbers.txt");
var splittedOutput = output.Split(',');

keys = new uint[Length];

for (var index = 0; index < Length; index++)
{
keys[index] = uint.Parse(splittedOutput[index]);
}
}

[IterationSetup]
public void Clean()
{
_map = new CMap<uint, uint>(2000000);
_nonBlocking = new NonBlocking.ConcurrentDictionary<uint, uint>(NumberOfThreads, 2000000);
_concurrentMap = new System.Collections.Concurrent.ConcurrentDictionary<uint, uint>(NumberOfThreads, 1000000);

}


[Benchmark]
public void NonBlocking()
{
int numKeys = 1000000;
int segmentSize = numKeys / NumberOfThreads;

Parallel.For(0, NumberOfThreads, threadIndex =>
{
int start = threadIndex * segmentSize;
int end = (threadIndex == NumberOfThreads - 1) ? numKeys : start + segmentSize;
for (uint i = (uint)start; i < end; i++)
{
var key = keys[i];
_nonBlocking[key] = 0;
}
});
}

[Benchmark]
public void CMap()
{
int numKeys = 1000000;
int segmentSize = numKeys / NumberOfThreads;

Parallel.For(0, NumberOfThreads, threadIndex =>
{
int start = threadIndex * segmentSize;
int end = (threadIndex == NumberOfThreads - 1) ? numKeys : start + segmentSize;
for (uint i = (uint)start; i < end; i++)
{
var key = keys[i];
_map.Update(key, 0);
}
});
}

[Benchmark]
public void ConcurrentDictionary()
{
int numKeys = 1000000;
int segmentSize = numKeys / NumberOfThreads;

Parallel.For(0, NumberOfThreads, threadIndex =>
{
int start = threadIndex * segmentSize;
int end = (threadIndex == NumberOfThreads - 1) ? numKeys : start + segmentSize;
for (uint i = (uint)start; i < end; i++)
{
var key = keys[i];
_concurrentMap[key] = 0;
}
});
}


}
}
93 changes: 88 additions & 5 deletions src/Concurrent/CMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,8 @@ public bool Get(TKey key, out TValue value)
}

/// <summary>
/// The Update method updates the value associated with a given key in the hash table
/// This method demonstrates a sophisticated approach to updating values in a concurrent hash table, leveraging quadratic probing, atomic operations, and handle the ABA problem effectively.
/// The use of aggressive inlining and careful memory management ensures that the method performs efficiently even under high concurrency.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Update(TKey key, TValue newValue)
Expand Down Expand Up @@ -413,7 +414,89 @@ public bool Update(TKey key, TValue newValue)
jumpDistance = 0;
entry.Exit();
goto start;
}
}
}

// If the entry indicates an empty bucket, the key does not exist in the table
if (_emptyBucket == entry.Meta)
{
return false;
}

// Increment the jump distance and calculate the next index using triangular probing
jumpDistance += 1;
index += jumpDistance;
index &= table.LengthMinusOne; // Ensure the index wraps around the table size
} 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.
/// It calculates the hash code for the key and uses quadratic probing to find the correct bucket in the table.
/// If a matching entry is found, the method performs an atomic compare-and-swap operation to ensure thread safety.
/// If the value matches the comparison value, it updates the value; otherwise, it retries or exits as necessary.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Update(TKey key, TValue newValue, TValue comparisonValue)
{
// Calculate the hash code for the given key
var hashcode = key.GetHashCode();
byte jumpDistance = 0; // Initialize jump distance for quadratic probing

start:

// Get the current state of the table
var table = _table;
var index = table.GetBucket(hashcode); // Calculate the initial bucket index
var h2 = table.H2(hashcode); // Calculate the secondary hash

do
{
// Retrieve the entry from the table at the calculated index
ref var entry = ref Find(table.Entries, index);

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

// If the entry's metadata and key match, proceed with the update
if (h2 == entry.Meta && _comparer.Equals(key, entry.Key))
{
// Guarantee that only one thread can access the critical section at a time
// the enter method uses Interlocked.CompareExchange and thus provides a full memory fence, ensuring thread safety
// And ensures that the changes made by one thread are visible to others
entry.Enter();

if (h2 == entry.Meta)
{
// A value can be changed multiple times between the reading and writing of the value by a thread.
// This can lead to incorrect assumptions about the state of the value.
// A common way to solve this problem is to track changes to the value.
if (EqualityComparer<TValue>.Default.Equals(entry.Value, comparisonValue))
{
// Perform the critical section: update the value
entry.Value = newValue;
entry.Exit();
return true;
}

entry.Exit();
return false;
}

// Check if the table has been resized during the operation
if (_table != table)
{
// If resized, restart with the new table
jumpDistance = 0;
entry.Exit();
goto start;
}
}

// If the entry indicates an empty bucket, the key does not exist in the table
Expand Down Expand Up @@ -505,10 +588,10 @@ public bool Remove(TKey key, out TValue value)
/// <summary>
/// Clears this instance.
/// </summary>
public void Clear()
public void Clear()
{
var table = new Table(_table.Length, _loadFactor);
Interlocked.Exchange(ref _table, table);
Interlocked.Exchange(ref _table, table);
}

/// <summary>
Expand Down Expand Up @@ -718,7 +801,7 @@ public Table(uint length, double _loadFactor)
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint GetBucket(int hashcode) => _goldenRatio * (uint)hashcode >> _shift;


internal void Migrate(Table mTable, uint index)
{
Expand Down
Loading

0 comments on commit 70071ca

Please sign in to comment.