diff --git a/csharp/Platform.Collections.Methods.Tests/PartiallyLockFreeSizeBalancedTree.cs b/csharp/Platform.Collections.Methods.Tests/PartiallyLockFreeSizeBalancedTree.cs new file mode 100644 index 0000000..e979881 --- /dev/null +++ b/csharp/Platform.Collections.Methods.Tests/PartiallyLockFreeSizeBalancedTree.cs @@ -0,0 +1,94 @@ +using System; +using System.Numerics; +using System.Text; +using Platform.Collections.Methods.Trees; + +namespace Platform.Collections.Methods.Tests +{ + public class PartiallyLockFreeSizeBalancedTree : PartiallyLockFreeSizeBalancedTreeMethods + where TElement: IUnsignedNumber, IComparisonOperators + { + private struct TreeElement + { + public TElement Size; + public TElement Left; + public TElement Right; + } + + private readonly TreeElement[] _elements; + private TElement _allocated; + + public TElement Root; + + public TElement Count => GetSizeOrZero(Root); + + public PartiallyLockFreeSizeBalancedTree(int capacity, int balancingThreshold = 10) + : base(balancingThreshold) + { + _elements = new TreeElement[capacity]; + _allocated = TElement.One; + } + + public TElement Allocate() + { + var newNode = _allocated; + if (IsEmpty(newNode)) + { + _allocated = _allocated + TElement.One; + return newNode; + } + else + { + throw new InvalidOperationException("Allocated tree element is not empty."); + } + } + + public void Free(TElement node) + { + while ((_allocated != TElement.One) && IsEmpty(node)) + { + var lastNode = _allocated - TElement.One; + if ((lastNode == node)) + { + _allocated = lastNode; + node = node - TElement.One; + } + else + { + break; + } + } + } + + protected override ref TElement GetLeftReference(TElement node) => ref _elements[int.CreateTruncating(node)].Left; + protected override ref TElement GetRightReference(TElement node) => ref _elements[int.CreateTruncating(node)].Right; + protected override TElement GetLeft(TElement node) => _elements[int.CreateTruncating(node)].Left; + protected override TElement GetRight(TElement node) => _elements[int.CreateTruncating(node)].Right; + protected override TElement GetSize(TElement node) => _elements[int.CreateTruncating(node)].Size; + protected override void SetLeft(TElement node, TElement left) => _elements[int.CreateTruncating(node)].Left = left; + protected override void SetRight(TElement node, TElement right) => _elements[int.CreateTruncating(node)].Right = right; + protected override void SetSize(TElement node, TElement size) => _elements[int.CreateTruncating(node)].Size = size; + + protected override bool FirstIsToTheLeftOfSecond(TElement first, TElement second) => first < second; + protected override bool FirstIsToTheRightOfSecond(TElement first, TElement second) => first > second; + + protected override void PrintNodeValue(TElement node, StringBuilder sb) => sb.Append($":{node}"); + + private bool IsEmpty(TElement node) => GetSize(node) == TElement.Zero; + + /// + /// + /// Performs delayed balancing operation on the tree root. + /// + /// + /// + protected override void PerformDelayedBalancing() + { + // Apply balancing to the root when threshold is reached + if (Root != TElement.Zero) + { + ApplyDelayedBalancing(ref Root); + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Collections.Methods.Tests/TreesTests.cs b/csharp/Platform.Collections.Methods.Tests/TreesTests.cs index 8130eab..4785e96 100644 --- a/csharp/Platform.Collections.Methods.Tests/TreesTests.cs +++ b/csharp/Platform.Collections.Methods.Tests/TreesTests.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Xunit; namespace Platform.Collections.Methods.Tests @@ -47,5 +48,98 @@ public static void SizedAndThreadedAVLBalancedTreeMultipleRandomAttachAndDetachT var avlTree = new SizedAndThreadedAVLBalancedTree(10000); avlTree.TestMultipleRandomCreationsAndDeletions(ref avlTree.Root, () => avlTree.Count, _n); } + + [Fact] + public static void PartiallyLockFreeSizeBalancedTreeMultipleAttachAndDetachTest() + { + var lockFreeTree = new PartiallyLockFreeSizeBalancedTree(10000, balancingThreshold: 5); + lockFreeTree.TestMultipleCreationsAndDeletions(lockFreeTree.Allocate, lockFreeTree.Free, ref lockFreeTree.Root, () => lockFreeTree.Count, _n); + } + + [Fact] + public static void PartiallyLockFreeSizeBalancedTreeMultipleRandomAttachAndDetachTest() + { + var lockFreeTree = new PartiallyLockFreeSizeBalancedTree(10000, balancingThreshold: 10); + lockFreeTree.TestMultipleRandomCreationsAndDeletions(ref lockFreeTree.Root, () => lockFreeTree.Count, _n); + } + + [Fact] + public static void PartiallyLockFreeSizeBalancedTreeDelayedBalancingTest() + { + var lockFreeTree = new PartiallyLockFreeSizeBalancedTree(1000, balancingThreshold: 5); + + // Insert elements without triggering balancing + for (uint i = 1; i <= 4; i++) + { + var node = lockFreeTree.Allocate(); + lockFreeTree.Attach(ref lockFreeTree.Root, node); + } + + // At this point, balancing should not have been triggered + Assert.Equal(4, lockFreeTree.LockFreeAttachCount); + Assert.Equal(5, lockFreeTree.BalancingThreshold); + + // Insert one more element to trigger balancing + var triggerNode = lockFreeTree.Allocate(); + lockFreeTree.Attach(ref lockFreeTree.Root, triggerNode); + + // After triggering, the counter should reset + Assert.Equal(0, lockFreeTree.LockFreeAttachCount); + Assert.Equal(5u, lockFreeTree.Count); + } + + [Fact] + public static void PartiallyLockFreeSizeBalancedTreeForceBalancingTest() + { + var lockFreeTree = new PartiallyLockFreeSizeBalancedTree(1000, balancingThreshold: 10); + + // Insert a few elements + for (uint i = 1; i <= 3; i++) + { + var node = lockFreeTree.Allocate(); + lockFreeTree.Attach(ref lockFreeTree.Root, node); + } + + Assert.Equal(3, lockFreeTree.LockFreeAttachCount); + + // Force balancing manually + lockFreeTree.ForceBalancing(); + + // Counter should be reset after force balancing + Assert.Equal(0, lockFreeTree.LockFreeAttachCount); + Assert.Equal(3u, lockFreeTree.Count); + } + + [Fact] + public static async Task PartiallyLockFreeSizeBalancedTreeConcurrentInsertionTest() + { + var lockFreeTree = new PartiallyLockFreeSizeBalancedTree(10000, balancingThreshold: 50); + const int numberOfTasks = 10; + const int insertsPerTask = 20; + + // Create tasks that will insert elements concurrently + var tasks = new Task[numberOfTasks]; + for (int i = 0; i < numberOfTasks; i++) + { + int taskId = i; + tasks[i] = Task.Run(() => + { + for (int j = 0; j < insertsPerTask; j++) + { + var node = lockFreeTree.Allocate(); + lockFreeTree.Attach(ref lockFreeTree.Root, node); + } + }); + } + + // Wait for all tasks to complete + await Task.WhenAll(tasks); + + // Verify final count + Assert.Equal((uint)(numberOfTasks * insertsPerTask), lockFreeTree.Count); + + // Clean up resources + lockFreeTree.Dispose(); + } } } diff --git a/csharp/Platform.Collections.Methods/Trees/PartiallyLockFreeSizeBalancedTreeMethods.cs b/csharp/Platform.Collections.Methods/Trees/PartiallyLockFreeSizeBalancedTreeMethods.cs new file mode 100644 index 0000000..4b94972 --- /dev/null +++ b/csharp/Platform.Collections.Methods/Trees/PartiallyLockFreeSizeBalancedTreeMethods.cs @@ -0,0 +1,347 @@ +using System; +using System.Numerics; +using System.Threading; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Collections.Methods.Trees +{ + /// + /// + /// Represents the partially lock-free size balanced tree methods. + /// Tree is locked only for balancing. Each N inserts can be done without any locking or balancing. + /// + /// + /// + /// + public abstract class PartiallyLockFreeSizeBalancedTreeMethods : SizeBalancedTreeMethods + where TElement: IUnsignedNumber, IComparisonOperators + { + private volatile int _lockFreeAttachCount = 0; + private readonly ReaderWriterLockSlim _balancingLock = new ReaderWriterLockSlim(); + private readonly int _balancingThreshold; + + /// + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + /// The number of insertions to perform before triggering balancing. Default is 10. + /// + /// + protected PartiallyLockFreeSizeBalancedTreeMethods(int balancingThreshold = 10) + { + _balancingThreshold = Math.Max(1, balancingThreshold); + } + + /// + /// + /// Gets the current count of lock-free attachments performed since last balancing. + /// + /// + /// + public int LockFreeAttachCount => _lockFreeAttachCount; + + /// + /// + /// Gets the threshold for triggering balancing operations. + /// + /// + /// + public int BalancingThreshold => _balancingThreshold; + + protected override void BeforeAttach() + { + // Increment the lock-free attach count atomically + Interlocked.Increment(ref _lockFreeAttachCount); + } + + /// + /// + /// Attaches the core using the specified root with lock-free insertion and delayed balancing. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The node. + /// + /// + protected override void AttachCore(ref TElement root, TElement node) + { + if (root == TElement.Zero) + { + root = node; + IncrementSize(root); + } + else + { + // Perform insertion with read lock (allows concurrent reads) + _balancingLock.EnterReadLock(); + try + { + IncrementSize(root); + if (FirstIsToTheLeftOfSecond(node, root)) + { + AttachCoreWithoutBalancing(ref GetLeftReference(root), node); + } + else + { + AttachCoreWithoutBalancing(ref GetRightReference(root), node); + } + } + finally + { + _balancingLock.ExitReadLock(); + } + } + } + + /// + /// + /// Attaches the core without any balancing operations for lock-free insertion. + /// + /// + /// + /// + /// The root. + /// + /// + /// + /// The node. + /// + /// + private void AttachCoreWithoutBalancing(ref TElement root, TElement node) + { + if (root == TElement.Zero) + { + root = node; + IncrementSize(root); + } + else + { + IncrementSize(root); + if (FirstIsToTheLeftOfSecond(node, root)) + { + AttachCoreWithoutBalancing(ref GetLeftReference(root), node); + } + else + { + AttachCoreWithoutBalancing(ref GetRightReference(root), node); + } + } + } + + protected override void AfterAttach() + { + // Check if balancing is needed after N insertions + if (_lockFreeAttachCount >= _balancingThreshold) + { + TriggerDelayedBalancing(); + } + } + + /// + /// + /// Triggers delayed balancing operation with exclusive locking. + /// + /// + /// + private void TriggerDelayedBalancing() + { + // Only one thread should perform balancing + if (_lockFreeAttachCount >= _balancingThreshold) + { + _balancingLock.EnterWriteLock(); + try + { + // Double-check pattern to avoid unnecessary balancing + if (_lockFreeAttachCount >= _balancingThreshold) + { + // Reset counter atomically + Interlocked.Exchange(ref _lockFreeAttachCount, 0); + + // Perform balancing operations + PerformDelayedBalancing(); + } + } + finally + { + _balancingLock.ExitWriteLock(); + } + } + } + + /// + /// + /// Performs delayed balancing operation. Override this method to define custom balancing logic + /// or call the parent balancing methods on the tree root. + /// + /// + /// + protected virtual void PerformDelayedBalancing() + { + // Default implementation - can be overridden by concrete implementations + // to perform specific balancing operations on their root structures + } + + /// + /// + /// Applies balancing to a subtree root using custom balancing logic. + /// This method should be called from PerformDelayedBalancing() on the tree root. + /// + /// + /// + /// + /// The root of the subtree to balance. + /// + /// + protected void ApplyDelayedBalancing(ref TElement root) + { + if (root != TElement.Zero) + { + // Apply custom balancing logic similar to the original maintain operations + DelayedLeftMaintain(ref root); + DelayedRightMaintain(ref root); + } + } + + /// + /// + /// Performs left maintenance for delayed balancing. + /// + /// + /// + /// + /// The root. + /// + /// + private void DelayedLeftMaintain(ref TElement root) + { + if (root != TElement.Zero) + { + var rootLeftNode = GetLeft(root); + if (rootLeftNode != TElement.Zero) + { + var rootRightNode = GetRight(root); + var rootRightNodeSize = GetSizeOrZero(rootRightNode); + var rootLeftNodeLeftNode = GetLeft(rootLeftNode); + if (rootLeftNodeLeftNode != TElement.Zero && + (rootRightNode == TElement.Zero || (GetSizeOrZero(rootLeftNodeLeftNode)) > rootRightNodeSize)) + { + RightRotate(ref root); + } + else + { + var rootLeftNodeRightNode = GetRight(rootLeftNode); + if (rootLeftNodeRightNode != TElement.Zero && + (rootRightNode == TElement.Zero || (GetSizeOrZero(rootLeftNodeRightNode)) > rootRightNodeSize)) + { + LeftRotate(ref GetLeftReference(root)); + RightRotate(ref root); + } + else + { + return; + } + } + DelayedLeftMaintain(ref GetLeftReference(root)); + DelayedRightMaintain(ref GetRightReference(root)); + DelayedRightMaintain(ref root); + DelayedLeftMaintain(ref root); + } + } + } + + /// + /// + /// Performs right maintenance for delayed balancing. + /// + /// + /// + /// + /// The root. + /// + /// + private void DelayedRightMaintain(ref TElement root) + { + if (root != TElement.Zero) + { + var rootRightNode = GetRight(root); + if (rootRightNode != TElement.Zero) + { + var rootLeftNode = GetLeft(root); + var rootLeftNodeSize = GetSizeOrZero(rootLeftNode); + var rootRightNodeRightNode = GetRight(rootRightNode); + if (rootRightNodeRightNode != TElement.Zero && + (rootLeftNode == TElement.Zero || (GetSizeOrZero(rootRightNodeRightNode)) > rootLeftNodeSize)) + { + LeftRotate(ref root); + } + else + { + var rootRightNodeLeftNode = GetLeft(rootRightNode); + if (rootRightNodeLeftNode != TElement.Zero && + (rootLeftNode == TElement.Zero || (GetSizeOrZero(rootRightNodeLeftNode)) > rootLeftNodeSize)) + { + RightRotate(ref GetRightReference(root)); + LeftRotate(ref root); + } + else + { + return; + } + } + DelayedLeftMaintain(ref GetLeftReference(root)); + DelayedRightMaintain(ref GetRightReference(root)); + DelayedRightMaintain(ref root); + DelayedLeftMaintain(ref root); + } + } + } + + /// + /// + /// Manually triggers balancing operation regardless of the current attach count. + /// + /// + /// + public void ForceBalancing() + { + _balancingLock.EnterWriteLock(); + try + { + Interlocked.Exchange(ref _lockFreeAttachCount, 0); + PerformDelayedBalancing(); + } + finally + { + _balancingLock.ExitWriteLock(); + } + } + + /// + /// + /// Disposes the resources used by the partially lock-free tree. + /// + /// + /// + public virtual void Dispose() + { + _balancingLock?.Dispose(); + } + + /// + /// + /// Gets a value indicating whether the balancing lock is currently held by any writer. + /// + /// + /// + public bool IsBalancing => _balancingLock.IsWriteLockHeld; + } +} \ No newline at end of file