diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListHelpers.CopyFollowingItems.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListHelpers.CopyFollowingItems.Benchmarks.cs new file mode 100644 index 0000000..267749f --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListHelpers.CopyFollowingItems.Benchmarks.cs @@ -0,0 +1,36 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum RecyclableLongListHelpersCopyFollowingItemsBenchmarkType + { + Undefined, + Old, + New, + } + + public class RecyclableLongListHelpersCopyFollowingItemsBenchmarks : RecyclableBenchmarkBase + { + [Params(RecyclableLongListHelpersCopyFollowingItemsBenchmarkType.Old)] + public override RecyclableLongListHelpersCopyFollowingItemsBenchmarkType BaseDataType { get => base.BaseDataType; set => base.BaseDataType = value; } + + [Params(RecyclableLongListHelpersCopyFollowingItemsBenchmarkType.New)] + public override RecyclableLongListHelpersCopyFollowingItemsBenchmarkType DataType { get => base.DataType; set => base.DataType = value; } + + protected override Action GetTestMethod(RecyclableLongListHelpersCopyFollowingItemsBenchmarkType benchmarkType) => benchmarkType switch + { + RecyclableLongListHelpersCopyFollowingItemsBenchmarkType.Old => CopyFollowingItemsOld, + RecyclableLongListHelpersCopyFollowingItemsBenchmarkType.New => CopyFollowingItemsNew, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + protected override void PrepareData(RecyclableLongListHelpersCopyFollowingItemsBenchmarkType benchmarkType) + { + base.PrepareData(RecyclableCollectionsBenchmarkSource.Array); + base.PrepareData(RecyclableCollectionsBenchmarkSource.RecyclableLongList); + } + + public void CopyFollowingItemsOld() => RecyclableLongList.Helpers.CopyFollowingItemsOld(TestObjectsAsRecyclableLongList, 0); + public void CopyFollowingItemsNew() => RecyclableLongList.Helpers.CopyFollowingItems(TestObjectsAsRecyclableLongList, 0); + } +} diff --git a/Recyclable.Collections/RecyclableLongList.Helpers.cs b/Recyclable.Collections/RecyclableLongList.Helpers.cs index 1ec9fb6..784b66d 100644 --- a/Recyclable.Collections/RecyclableLongList.Helpers.cs +++ b/Recyclable.Collections/RecyclableLongList.Helpers.cs @@ -1,5 +1,4 @@ using Recyclable.Collections.Pools; -using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; @@ -10,7 +9,7 @@ namespace Recyclable.Collections internal static class Helpers { [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] - public static void CopyFollowingItems(RecyclableLongList list, long destinationItemIndex) + public static void CopyFollowingItemsOld(RecyclableLongList list, long destinationItemIndex) { T[][] memoryBlocks = list._memoryBlocks; int nextItemIndex = list._nextItemIndex; @@ -55,6 +54,80 @@ namespace Recyclable.Collections } } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static void CopyFollowingItems(RecyclableLongList list, long destinationItemIndex) + { + T[][] memoryBlocks = list._memoryBlocks; + int blockSizeMinus1 = list._blockSizeMinus1, + targetItemIndex = (int)(destinationItemIndex & blockSizeMinus1), + targetBlockIndex = (int)(destinationItemIndex >> list._blockSizePow2BitShift) + (targetItemIndex != 0 ? 1 : 0), + lastTakenBlockIndex = list._lastBlockWithData - 1; + + // long remainingItems, + // itemsCount = list._longCount; + + // Each iteration should + // 1. copy the remaining items in the current block (those after the deleted item) + // 2. copy the 1st item from the next block. We deleted 1 item which leaves 1 item gap in the block. + // We need to fill it. + // T[] currentMemoryBlock = memoryBlocks[targetBlockIndex]; + if (targetItemIndex < blockSizeMinus1) + { + // new Span(currentMemoryBlock, targetItemIndex + 1, blockSizeMinus1 - targetItemIndex) + // .CopyTo(new Span(currentMemoryBlock, targetItemIndex, blockSizeMinus1 - targetItemIndex)); + // remainingItems = blockSizeMinus1 - targetItemIndex; + new Span(memoryBlocks[targetBlockIndex], targetItemIndex + 1, blockSizeMinus1 - targetItemIndex) + .CopyTo(new Span(memoryBlocks[targetBlockIndex], targetItemIndex, blockSizeMinus1 - targetItemIndex)); + // new Span(currentMemoryBlock, targetItemIndex + 1, unchecked((int)remainingItems)) + // .CopyTo(new Span(currentMemoryBlock, targetItemIndex, unchecked((int)remainingItems))); + // Array.Copy(currentMemoryBlock, targetItemIndex + 1, currentMemoryBlock, targetItemIndex, remainingItems); + } + // else + // { + // remainingItems = destinationItemIndex; + // } + + while (targetBlockIndex < lastTakenBlockIndex) + { + new Span(memoryBlocks[targetBlockIndex])[blockSizeMinus1] = memoryBlocks[++targetBlockIndex][0]; + // currentMemoryBlock[blockSizeMinus1] = memoryBlocks[++targetBlockIndex][0]; + // new Span(currentMemoryBlock)[blockSizeMinus1] = memoryBlocks[++targetBlockIndex][0]; + // currentMemoryBlock = memoryBlocks[targetBlockIndex]; + + // if (remainingItems > 1) + // { + new Span(memoryBlocks[targetBlockIndex], 1, blockSizeMinus1) + .CopyTo(new Span(memoryBlocks[targetBlockIndex])); + // new Span(currentMemoryBlock, 1, blockSizeMinus1) + // .CopyTo(new Span(currentMemoryBlock)); + // Array.Copy(currentMemoryBlock, 1, currentMemoryBlock, 0, blockSizeMinus1); + // remainingItems += blockSize; + // } + // else + // { + // remainingItems = 0; + // } + // new Span(currentMemoryBlock, 1, blockSizeMinus1).CopyTo(new Span(currentMemoryBlock)); + } + + if (lastTakenBlockIndex >= 0) + { + new Span(memoryBlocks[targetBlockIndex])[blockSizeMinus1] = memoryBlocks[++targetBlockIndex][0]; + if (list._nextItemIndex != 0) + { + new Span(memoryBlocks[targetBlockIndex], 1, list._nextItemIndex - 1) + .CopyTo(new Span(memoryBlocks[targetBlockIndex])); + } + else + { + new Span(memoryBlocks[targetBlockIndex], 1, blockSizeMinus1) + .CopyTo(new Span(memoryBlocks[targetBlockIndex])); + } + } + // currentMemoryBlock = memoryBlocks[lastTakenBlockIndex]; + // Array.ConstrainedCopy(currentMemoryBlock, 1, currentMemoryBlock, 0, remainderLength); + } + public static void CopyTo(T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) { if (itemsCount <= 0) diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index e459c75..9423742 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -589,6 +589,7 @@ namespace Recyclable.CollectionsTests { // Prepare using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + var listAsArray = list.ToArray(); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -599,7 +600,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); - _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); + _ = list.Should().ContainInConsecutiveOrder(listAsArray[deleted..]); } _ = list.Should().BeEmpty(); @@ -613,6 +614,7 @@ namespace Recyclable.CollectionsTests { // Prepare using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + var listAsArray = list.ToArray(); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -623,7 +625,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); - _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); + _ = list.Should().ContainInConsecutiveOrder(listAsArray[..(int)(itemsCount - deleted)]); } _ = list.Should().BeEmpty(); @@ -637,6 +639,7 @@ namespace Recyclable.CollectionsTests { // Prepare using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + var listAsArray = list.ToArray(); // Act & Validate for (var deleted = 1; deleted <= itemsCount; deleted++) @@ -647,7 +650,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); - _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); + _ = list.Should().ContainInConsecutiveOrder(listAsArray[deleted..]); } _ = list.Should().BeEmpty(); @@ -661,14 +664,14 @@ namespace Recyclable.CollectionsTests { // Prepare using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); - var expectedData = testData.ToArray(); + var listAsArray = testData.ToArray(); int removedCount = 0; foreach (var item in testData.Reverse()) { // Act & Validate _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); removedCount++; - var expectedList = expectedData[0..((int)itemsCount - removedCount)]; + var expectedList = listAsArray[..((int)itemsCount - removedCount)]; _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.LongCount.Should().Be(expectedList.Length); _ = list.Should().ContainInConsecutiveOrder(expectedList);