Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
using System;
using System.Collections;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Toolchains.InProcess.NoEmit;
using Platform.Data.Doublets.Memory;
using Platform.Memory;

#pragma warning disable CA1822 // Mark members as static

namespace Platform.Data.Doublets.Benchmarks
{
/// <summary>
/// Benchmark comparing different approaches for tracking deleted links:
/// 1. Traditional approach using linked list with HashSet (simulated)
/// 2. BitArray-based approach
/// 3. Custom bitstring approach using ulong arrays
///
/// Results from initial testing show bitstring approaches are ~50x faster
/// for the mixed operations benchmark with 100,000 links and 50,000 operations.
/// </summary>
[Config(typeof(Config))]
[MemoryDiagnoser]
public class BitStringDeletedLinksBenchmark
{
private class Config : ManualConfig
{
public Config()
{
AddJob(Job.MediumRun.WithToolchain(InProcessNoEmitToolchain.Instance));
}
}

private const int LinkCount = 10000;
private const int Operations = 5000;

private Random _random = new Random(42);
private uint[] _testLinks = new uint[Operations];
private int[] _testOperations = new int[Operations];

[GlobalSetup]
public void Setup()
{
_random = new Random(42);

// Pre-generate test data for consistent benchmarking
for (int i = 0; i < Operations; i++)
{
_testLinks[i] = (uint)_random.Next(1, LinkCount);
_testOperations[i] = _random.Next(0, 4); // 0=mark deleted, 1=unmark, 2=check, 3=get first
}
}

[Benchmark(Baseline = true)]
public void BitArrayApproach()
{
var bitArray = new BitArray(LinkCount);
var deletedCount = 0;

// Initial setup - mark half as deleted
for (int i = 1; i < LinkCount / 2; i++)
{
bitArray[i] = true;
deletedCount++;
}

// Perform operations
for (int op = 0; op < Operations; op++)
{
var link = (int)_testLinks[op];
var operation = _testOperations[op];

switch (operation)
{
case 0: // Mark as deleted
if (!bitArray[link])
{
bitArray[link] = true;
deletedCount++;
}
break;
case 1: // Mark as undeleted
if (bitArray[link])
{
bitArray[link] = false;
deletedCount--;
}
break;
case 2: // Check if deleted
_ = bitArray[link];
break;
case 3: // Get first deleted
for (int i = 0; i < bitArray.Length; i++)
{
if (bitArray[i])
break;
}
break;
}
}
}

[Benchmark]
public void CustomBitstringApproach()
{
var bitsPerUlong = 64;
var arraySize = (LinkCount + bitsPerUlong - 1) / bitsPerUlong;
var bits = new ulong[arraySize];
var deletedCount = 0;

// Initial setup - mark half as deleted
for (int i = 1; i < LinkCount / 2; i++)
{
var wordIndex = i / bitsPerUlong;
var bitIndex = i % bitsPerUlong;
if ((bits[wordIndex] & (1UL << bitIndex)) == 0)
{
bits[wordIndex] |= (1UL << bitIndex);
deletedCount++;
}
}

// Perform operations
for (int op = 0; op < Operations; op++)
{
var link = (int)_testLinks[op];
var operation = _testOperations[op];
var wordIndex = link / bitsPerUlong;
var bitIndex = link % bitsPerUlong;

switch (operation)
{
case 0: // Mark as deleted
if (wordIndex < bits.Length && (bits[wordIndex] & (1UL << bitIndex)) == 0)
{
bits[wordIndex] |= (1UL << bitIndex);
deletedCount++;
}
break;
case 1: // Mark as undeleted
if (wordIndex < bits.Length && (bits[wordIndex] & (1UL << bitIndex)) != 0)
{
bits[wordIndex] &= ~(1UL << bitIndex);
deletedCount--;
}
break;
case 2: // Check if deleted
if (wordIndex < bits.Length)
_ = (bits[wordIndex] & (1UL << bitIndex)) != 0;
break;
case 3: // Get first deleted using bit manipulation
for (int wi = 0; wi < bits.Length; wi++)
{
if (bits[wi] != 0)
{
var trailingZeros = BitOperations.TrailingZeroCount(bits[wi]);
_ = wi * bitsPerUlong + trailingZeros;
break;
}
}
break;
}
}
}

[Benchmark]
public void LinkedListApproach()
{
var deletedLinks = new System.Collections.Generic.HashSet<uint>();
var deletedLinksList = new System.Collections.Generic.LinkedList<uint>();

// Initial setup - mark half as deleted
for (uint i = 1; i < LinkCount / 2; i++)
{
deletedLinks.Add(i);
deletedLinksList.AddFirst(i);
}

// Perform operations
for (int op = 0; op < Operations; op++)
{
var link = _testLinks[op];
var operation = _testOperations[op];

switch (operation)
{
case 0: // Mark as deleted
if (deletedLinks.Add(link))
deletedLinksList.AddFirst(link);
break;
case 1: // Mark as undeleted
if (deletedLinks.Remove(link))
deletedLinksList.Remove(link);
break;
case 2: // Check if deleted
_ = deletedLinks.Contains(link);
break;
case 3: // Get first deleted
_ = deletedLinksList.First?.Value;
break;
}
}
}

/// <summary>
/// Tests memory efficiency by creating large sparse sets of deleted links
/// </summary>
[Benchmark]
public void BitArrayMemoryEfficiency()
{
const int sparseSize = 100000;
var bitArray = new BitArray(sparseSize);

// Mark only 1% as deleted, spread throughout the range
for (int i = 0; i < sparseSize; i += 100)
{
bitArray[i] = true;
}

// Count deleted
var count = 0;
for (int i = 0; i < bitArray.Length; i++)
{
if (bitArray[i]) count++;
}
}

[Benchmark]
public void CustomBitstringMemoryEfficiency()
{
const int sparseSize = 100000;
const int bitsPerUlong = 64;
var arraySize = (sparseSize + bitsPerUlong - 1) / bitsPerUlong;
var bits = new ulong[arraySize];

// Mark only 1% as deleted, spread throughout the range
for (int i = 0; i < sparseSize; i += 100)
{
var wordIndex = i / bitsPerUlong;
var bitIndex = i % bitsPerUlong;
bits[wordIndex] |= (1UL << bitIndex);
}

// Count deleted using bit manipulation
var count = 0;
for (int i = 0; i < bits.Length; i++)
{
count += BitOperations.PopCount(bits[i]);
}
}
}
}
Loading
Loading