diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bcbe434 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Recyclable.Collections.Benchmarks/bin/Debug/net6.0/Recyclable.Collections.Benchmarks.dll", + "args": [], + "cwd": "${workspaceFolder}/Recyclable.Collections.Benchmarks", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..f921346 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,61 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "isBuildCommand": true, + "label": "build debug", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-f", "net7.0" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build release", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-c", "Release", + "-f", "net7.0" + ], + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index fee95be..4ca2a1b 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,338 @@ # Recyclable.Collections `Recyclable.Collections` project is an open source framework for operating dynamic lists at performance close to raw arrays, but fairly unlimited in size. It aims at providing minimal memory footprint. It implements `IList`'s interface and is targeted as direct replacements of `List`, `SortableList`, `PriorityQueue` & similar. -# Characteristicts of the classes +## Included +* `RecyclableList` +* `RecyclableLongList` +# Milestones +1. ✅ Create basic classes + 1. ✅ `RecyclableList` + 1. ✅ `RecyclableLongList` + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` +1. ✅ Create basic unit tests + 1. ✅ `RecyclableList` + 1. ✅ `RecyclableLongList` + 1. 🅿️ `RecyclableQueue` + 1. 🅿️ `RecyclableSortedList` + 1. 🅿️ `RecyclableStack` + 1. 🅿️ `RecyclableUnorderedList` +1. ✅ Optimize `RecyclableList` + 1. ✅ `Add` + 1. ✅ `AddRange` + 1. ✅ when source is `array` + 1. ✅ when source is `List` + 1. ✅ when source is `IList` + 1. ✅ when source is `RecyclableList` + 1. ✅ when source is `RecyclableLongList` + 1. ✅ when source is `IEnumerable` + 1. ✅ when source has non-enumerated count + 1. ✅ `Clear` + 1. ✅ `Contains` + 1. ✅ `CopyTo` + 1. ✅ `EnsureCapacity` + 1. 🅿️ `GetEnumerator` + 1. ✅ `IndexOf` + 1. ✅ `Insert` + 1. ✅ `Remove` + 1. ✅ `RemoveAt` + 1. ✅ `Resize` + 1. ✅ `this[int index]` +1. ✅ Port `RecyclableList` implementation to `RecyclableLongList` +1. 👉 Optimize `RecyclableLongList` + 1. ✅ `Add` + 1. ✅ `AddRange` + 1. ✅ when source is `array` + 1. ✅ when source is `List` + 1. ✅ when source is `IList` + 1. ✅ when source is `RecyclableList` + 1. ✅ when source is `RecyclableLongList` + 1. ✅ when source is `IEnumerable` + 1. ✅ when source has non-enumerated count + 1. ✅ `Clear` + 1. ✅ `Contains` + 1. 🅿️ `CopyTo` + 1. ✅ `EnsureCapacity` + 1. 🅿️ `GetEnumerator` + 1. ✅ `IndexOf` + 1. ✅ `LongIndexOf` + 1. ✅ `Insert` + 1. ✅ `Remove` + 1. ✅ `RemoveAt(int index)` + 1. ✅ `RemoveAt(long index)` + 1. ✅ `RemoveAt` + 1. ✅ `Resize` + 1. ✅ `this[int index]` + 1. ✅ `this[long index]` + 1. ✅ Rename `RecyclableList` to `RecyclableLongList` + 1. ✅ Rename `RecyclableArrayList` to `RecyclableList` + 1. ✅ Fix failing tests + 1. ✅ Hide not ready classes + 1. ✅ `RecyclableQueue` + 1. ✅ `RecyclableSortedList` + 1. ✅ `RecyclableStack` + 1. ✅ `RecyclableUnorderedList` + 1. 👉 Add support for `ReadOnlySpan` + 1. 🅿️ Implement list versioning to allow data change identification + 1. ✅ Release 0.0.3-alpha + 1. 🅿️ Implement `List` interfaces + 1. 🅿️ `ICollection` + 1. 🅿️ `IEnumerable` + 1. 🅿️ `IEnumerable` + 1. 🅿️ `IList` + 1. 🅿️ `IReadOnlyCollection` + 1. 🅿️ `IReadOnlyList` + 1. 🅿️ `ICollection` + 1. 🅿️ `IList` + 1. 🅿️ Make sure that `NeedsClearing` is used & items are cleared in + 1. 🅿️ `Clear` + 1. 🅿️ `Dispose` + 1. 🅿️ `Remove` + 1. 🅿️ `RemoveAt` + 1. 🅿️ `RemoveBlock` + 1. 🅿️ Release 0.0.3-beta + 1. 🅿️ Add support for `ulong` indexing + 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths + 1. 🅿️ Convert block indexes from `int` to `ulong` or `long` + 1. 🅿️ Final optimizations + 1. 🅿️ Replace `Math` class usages with `if` statements + 1. 🅿️ Replace `a - b > 0` & `a - b < 0` comparisons with `a > b` & `a < b` + 1. 🅿️ Replace `a + b > 0` & `a + b < 0` comparisons with `a > b` & `a < b` + 1. 🅿️ Replace `a / b` & `a * b` calculations with equivalents, where possible + 1. 🅿️ Replace virtual calls with static calls + 1. 🅿️ Replace `blockSize` sums by powers of 2, minus 1 + 1. 🅿️ Remove type castings, if possible + 1. 🅿️ Convert generic methods to non-generic + 1. 🅿️ Overflow review + 1. 🅿️ Add type casting to `long` for `<<` & `>>` operations, where required + 1. 🅿️ Make type castings `checked` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableList` + 1. 🅿️ Release 0.0.3 +1. 🅿️ Implement `RecyclableQeueue` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableQueue` + 1. 🅿️ Release 0.0.4 +1. 🅿️ Implement `RecyclableStack` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableStack` + 1. 🅿️ Release 0.0.5 +1. 🅿️ Implement `RecyclableSortedList` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableSortedList` + 1. 🅿️ Release 0.0.6 +1. 🅿️ Implement `RecyclableUnorderedList` + 1. 🅿️ Port `RecyclableLongList` optimizations to `RecyclableUnorderedList` + 1. 🅿️ Release 0.0.7 +1. 🅿️ Optimize `OneSizeArrayPool` + 1. 🅿️ Review locks + 1. 🅿️ Measure multi-threading performance +1. 🅿️ Review `RecyclableArrayPool` + 1. 🅿️ Review locks + 1. 🅿️ Measure multi-threading performance +1. 🅿️ Optimize `MemoryBucket` + 1. 🅿️ Convert to `struct`, if possible + 1. 🅿️ Find out if there are better replacements + 1. 🅿️ Release 0.0.8 +1. 🅿️ Optimize + 1. 🅿️ `IndexOfSynchronizationContext` + 1. 🅿️ `IndexOfSynchronizationContextPool` + 1. 🅿️ `ManualResetEventSlimmer` + 1. 🅿️ `ManualResetEventSlimmerPool` + 1. 🅿️ `SpinLockSlimmer` +1. 🅿️ Release 0.0.9-beta +1. 🅿️ Cleanup + 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field + 1. 🅿️ Cleanup `RecyclableLongListExtensions` + 1. 🅿️ `ListExtensions` + 1. 🅿️ `MathUtils` +1. 🅿️ Optimize + 1. 🅿️ `ListExtensions` + 1. 🅿️ `MathUtils` + 1. 🅿️ `SystemRandomNumberGenerator` +1. 🅿️ Release 1.0.0 + +# Characteristics of the classes + +## Common +* All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`. +* Memory blocks are created in 2 ways, depending on `blockSize` value + * when `blockSize < 128` ⇨ blocks are created as regular `arrays`. + * when `blockSize >= 128` ⇨ blocks are taken from and returned to `ArrayPool.Shared`, when blocks are removed. + * maximum `blockSize` value is `2_147_483_591`, which corresponds more or less to the maximum array size. + * `blockSize` MUST be a power of 2. It will be rounded up to the closest power of 2, if needed. That is due to high performance gain on some operations, like the calculation of item index in a block. +* Array pools are shared between the same `T` type. I.e. `List` will use a different pool from `List` and so on. For high concurrency environments you may want to provide your own pools, when this feature becomes available in the upcoming releases. +* Trying to access `this[int index]`, `this[long index]`, `Count`, `LongCount` etc. when `Capacity == 0` will raise `NullReferenceException`. This is by design to remove all non-critical code from the constructor. + +## `RecyclableList` +* Range: `int` +* Interfaces: `IList`, `IEnumerable`, `IDisposable` +This is the direct equivalent of `List` class, except that the arrays are taken from the shared pool, instead of directly being allocated. It proves to out perform `List` & the standard arrays in certain operations and is fairly close in others. But they may perform worse in certain scenarios. If you're interested in details, please refer to benchmarks. + +## `RecyclableLongList`, `RecyclableSortableList`, `RecyclableQueue`, `RecyclableStack` +* Range: `long` +* Interfaces: `IList`, `ILongList`, `IEnumerable`, `IDisposable` * All classes provide large storage capabilities, in practice limited only by the memory available in your system. * All data is stored in blocks, with the provided `int blockSize` or the default, currently being `10,240` items in each block. It's recommended to use smaller numbers, if you foresee storing low no. of items on the list. -* Memory blocks are created in 2 ways, depending on `blockSize` value - * when `blockSize < 100` ⇨ blocks are created as regular `arrays` - * when `blockSize >= 100` ⇨ blocks are taken from and returned to `ArrayPool.Shared`, when blocks are removed -* All classes implement `IDisposable` interface & SHOULD be disposed after use. That's to return block arrays taken from the shared pool. It may be foreseen as an issue for replacement in existing code, which obviously is missing `using` clause. But considering that `Dispose` will be called by `GC` anyway, it should cause issues in specific scenarios, only. In either case the fix is one-word addition of `using`. -* There is no memory copying to increase the capacity of the recyclable classes, anymore. If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes. -* `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradadion. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes. +* There is no memory copying to increase the capacity of the recyclable classes, unless the no. of items exceeds the no. of allocated blocks. In that case, a new array of blocks is created to accommodate . If more capacity is needed, a new memory block is allocate & added to the internal list of blocks. This is the only list that may re-allocate memory block & copy their content to grow. That shouldn't be an issue considering limited no. of blocks stored by each class in practical scenarios. Currently I'm not planning any works around that. Shall there be a need to eliminate it, you're welcomed to file a PR with the proposed changes. +* ⚠️ `IndexOf` will automatically switch to parallel search mode. Collections smaller than (currently) 850_000 items are guaranteed to find the 1st occurrence of the `item` parameter. Collections bigger than (currently) 850_000 items will be scanned using parallelization. If either case it'll return the index of the first matching item. This note also applies to `Contains`, because it internally uses `IndexOf`. The value of 850_000 may become configurable in the future, unless it proves to result in much worse performance. + +The parallelization utilizes default `Task` scheduling, resulting in non-deterministic order in which search tasks are executed. In most cases it resulted in significant performance improvements. Below you can find the benchmarks for various item counts. + +```csharp +BenchmarkDotNet=v0.13.3, OS=Windows 11 (10.0.22621.1702) +AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestCase | BaselineBenchmarkType | BenchmarkType | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | Allocated | Alloc Ratio | +|--------- |--------------------- |---------------------- |------------------- |---------------- |----------------------:|---------------------:|-------------------:|----------------------:|------:|------------:|-----------:|------------:| +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 1 | 16.60 ns | 0.060 ns | 0.056 ns | 16.57 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 1 | 19.20 ns | 0.137 ns | 0.121 ns | 19.16 ns | 1.16 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 2 | 27.64 ns | 0.188 ns | 0.176 ns | 27.65 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 2 | 31.90 ns | 0.102 ns | 0.095 ns | 31.89 ns | 1.15 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 3 | 28.35 ns | 0.146 ns | 0.137 ns | 28.42 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 3 | 32.63 ns | 0.177 ns | 0.166 ns | 32.55 ns | 1.15 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 4 | 39.05 ns | 0.112 ns | 0.105 ns | 38.99 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 4 | 44.01 ns | 0.168 ns | 0.149 ns | 43.98 ns | 1.13 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 5 | 39.77 ns | 0.163 ns | 0.152 ns | 39.69 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 5 | 46.72 ns | 0.943 ns | 1.258 ns | 46.87 ns | 1.17 | 0.03 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 6 | 51.14 ns | 0.202 ns | 0.189 ns | 51.10 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 6 | 59.75 ns | 1.076 ns | 1.105 ns | 59.75 ns | 1.17 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 7 | 51.60 ns | 0.126 ns | 0.105 ns | 51.58 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 7 | 58.40 ns | 1.125 ns | 0.997 ns | 58.38 ns | 1.13 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 8 | 61.22 ns | 0.301 ns | 0.281 ns | 61.22 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 8 | 73.96 ns | 0.397 ns | 0.372 ns | 73.98 ns | 1.21 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 9 | 62.21 ns | 0.074 ns | 0.062 ns | 62.22 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 9 | 96.22 ns | 0.712 ns | 0.631 ns | 96.16 ns | 1.55 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 10 | 75.18 ns | 0.244 ns | 0.228 ns | 75.07 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 10 | 83.85 ns | 0.224 ns | 0.209 ns | 83.74 ns | 1.12 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 11 | 74.93 ns | 0.214 ns | 0.200 ns | 74.86 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 11 | 83.78 ns | 0.484 ns | 0.453 ns | 83.53 ns | 1.12 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 12 | 86.86 ns | 0.316 ns | 0.295 ns | 86.88 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 12 | 106.61 ns | 1.246 ns | 1.224 ns | 106.88 ns | 1.23 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 13 | 88.02 ns | 0.273 ns | 0.255 ns | 87.90 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 13 | 96.74 ns | 0.074 ns | 0.066 ns | 96.73 ns | 1.10 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 14 | 100.74 ns | 0.344 ns | 0.322 ns | 100.79 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 14 | 110.46 ns | 0.359 ns | 0.336 ns | 110.27 ns | 1.10 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 15 | 100.67 ns | 0.405 ns | 0.359 ns | 100.55 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 15 | 127.81 ns | 1.472 ns | 1.305 ns | 128.04 ns | 1.27 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 16 | 114.05 ns | 0.520 ns | 0.486 ns | 113.75 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 16 | 141.99 ns | 0.510 ns | 0.477 ns | 141.82 ns | 1.24 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 17 | 114.63 ns | 0.432 ns | 0.404 ns | 114.41 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 17 | 125.91 ns | 0.363 ns | 0.322 ns | 125.79 ns | 1.10 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 18 | 127.90 ns | 0.404 ns | 0.378 ns | 128.07 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 18 | 140.47 ns | 0.452 ns | 0.423 ns | 140.24 ns | 1.10 | 0.00 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 19 | 128.56 ns | 0.375 ns | 0.350 ns | 128.41 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 19 | 152.84 ns | 1.948 ns | 1.822 ns | 152.48 ns | 1.19 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 20 | 150.88 ns | 0.538 ns | 0.477 ns | 150.97 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 20 | 184.59 ns | 0.871 ns | 0.772 ns | 184.61 ns | 1.22 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 30 | 217.63 ns | 0.967 ns | 0.905 ns | 217.71 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 30 | 244.89 ns | 1.417 ns | 1.326 ns | 244.56 ns | 1.13 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 32 | 266.32 ns | 2.378 ns | 2.224 ns | 266.71 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 32 | 310.60 ns | 1.946 ns | 1.820 ns | 310.93 ns | 1.17 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 40 | 362.07 ns | 2.741 ns | 2.564 ns | 362.60 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 40 | 401.02 ns | 1.604 ns | 1.500 ns | 400.31 ns | 1.11 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 50 | 400.35 ns | 3.445 ns | 3.223 ns | 400.41 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 50 | 485.05 ns | 3.031 ns | 2.531 ns | 485.54 ns | 1.21 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 60 | 582.48 ns | 4.689 ns | 4.386 ns | 581.95 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 60 | 664.89 ns | 3.413 ns | 3.192 ns | 663.75 ns | 1.14 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 80 | 845.29 ns | 7.416 ns | 6.937 ns | 843.90 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 80 | 915.71 ns | 5.696 ns | 5.328 ns | 916.91 ns | 1.08 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 90 | 994.91 ns | 15.228 ns | 14.244 ns | 998.73 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 90 | 1,074.04 ns | 12.721 ns | 11.277 ns | 1,077.75 ns | 1.08 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 100 | 1,130.91 ns | 20.393 ns | 19.076 ns | 1,130.99 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 100 | 1,185.56 ns | 23.173 ns | 21.676 ns | 1,184.10 ns | 1.05 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 128 | 1,610.08 ns | 16.899 ns | 15.807 ns | 1,604.90 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 128 | 1,753.55 ns | 17.254 ns | 16.140 ns | 1,752.63 ns | 1.09 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 256 | 5,750.75 ns | 28.609 ns | 26.760 ns | 5,758.04 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 256 | 5,893.43 ns | 29.453 ns | 27.551 ns | 5,887.99 ns | 1.02 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 512 | 21,669.95 ns | 427.575 ns | 399.954 ns | 21,713.08 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 512 | 21,674.73 ns | 68.941 ns | 64.487 ns | 21,668.86 ns | 1.00 | 0.02 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 1024 | 69,740.31 ns | 838.846 ns | 932.375 ns | 69,475.32 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 1024 | 68,980.90 ns | 450.707 ns | 421.591 ns | 68,923.79 ns | 0.99 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 2048 | 244,266.95 ns | 2,449.277 ns | 2,045.257 ns | 243,462.06 ns | 1.00 | 0.00 | - | NA | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 2048 | 239,887.40 ns | 854.306 ns | 799.118 ns | 240,015.48 ns | 0.98 | 0.01 | - | NA | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 4096 | 904,644.52 ns | 3,379.464 ns | 2,638.463 ns | 904,380.27 ns | 1.00 | 0.00 | 1 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 4096 | 915,459.26 ns | 6,529.766 ns | 6,107.947 ns | 917,327.34 ns | 1.01 | 0.01 | 1 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 8192 | 3,513,716.89 ns | 62,893.465 ns | 90,199.928 ns | 3,474,476.56 ns | 1.00 | 0.00 | 2 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 8192 | 3,486,690.89 ns | 14,705.600 ns | 13,755.627 ns | 3,481,303.91 ns | 0.99 | 0.02 | 2 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 16384 | 13,736,865.85 ns | 120,702.128 ns | 106,999.292 ns | 13,714,744.53 ns | 1.00 | 0.00 | 9 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 16384 | 14,167,800.31 ns | 43,856.428 ns | 41,023.330 ns | 14,149,225.00 ns | 1.03 | 0.01 | 9 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 32768 | 53,875,332.00 ns | 245,220.180 ns | 229,379.108 ns | 53,809,450.00 ns | 1.00 | 0.00 | 60 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 32768 | 55,149,804.00 ns | 273,037.835 ns | 255,399.760 ns | 55,076,500.00 ns | 1.02 | 0.01 | 60 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 65536 | 218,431,663.33 ns | 4,265,864.273 ns | 4,912,575.640 ns | 214,920,550.00 ns | 1.00 | 0.00 | 200 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 65536 | 217,449,610.26 ns | 493,374.080 ns | 411,989.702 ns | 217,368,133.33 ns | 1.01 | 0.02 | 200 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 131072 | 936,350,615.38 ns | 2,290,617.990 ns | 1,912,769.764 ns | 936,031,100.00 ns | 1.00 | 0.00 | 600 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 131072 | 928,423,416.67 ns | 1,034,931.818 ns | 808,006.689 ns | 928,198,750.00 ns | 0.99 | 0.00 | 600 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 849999 | 99,588,892,028.57 ns | 998,348,445.797 ns | 885,009,893.256 ns | 99,218,977,100.00 ns | 1.00 | 0.00 | 600 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 849999 | 102,323,847,080.00 ns | 1,028,672,431.300 ns | 962,220,830.472 ns | 101,982,058,200.00 ns | 1.03 | 0.01 | 600 B | 1.00 | +| | | | | | | | | | | | | | +| Baseline | Index(...)Cases [25] | List | RecyclableLongList | 850000 | 113.8 s | 2.26 s | 2.69 s | | 1.00 | 0.00 | 600 B | 1.00 | +| Actual | Index(...)Cases [25] | List | RecyclableLongList | 850000 | 110.3 s | 2.11 s | 2.07 s | | 0.97 | 0.03 | 399750776 B | 666,251.29 | +``` +As `RecyclableLongList` is foreseen for use-cases with big data, you should automatically benefit from it in many use-cases. + +* `Insert`, `Remove` & `RemoveAt` methods raise `NotSupportedException`. This is by design in the current version, because it involves performance degradation. I'm planning works around this in the upcoming weeks. If you need that sooner, you're welcomed to file a PR with the proposed changes. # Architecture Decisions The architecture of the classes included in this project is driven primarily by performance & memory footprints. The following decisions were made to support it. ## Code duplication -There is some level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & proved to perform worse. For this reason, properties & methods expected to be on the hotpath duplicate some code. +There is high level of code duplication in each of the classes. When you look at them, you will see they are alike & some of the code could be extracted to extension methods. It has been done & has proven to perform worse. For this reason, properties & methods expected to be on the hot-path, duplicate much of the code to get the most out of it. -## Common base `ILongList` -Having a common base class for all the classes looks very attractive. But it is also proved to perform worse, if you start making properties & methods virtual & there are subsequent calls to `base`. For this reason, each of the classes is the root class & there are hardly any virtual methods & properties. All classes are meant to be `final`, but I've not marked them as `sealed`. If you feel that you'd benefit from it, you're welcomed to inherit from them. +## Common base - `ILongList` +Having a common base class for all the classes looks very attractive. But it is also proved to perform worse, if you start making properties & methods virtual & there are subsequent calls to `base`. For this reason, each of the classes is the root class & there are hardly any virtual methods & properties. All classes are meant to be `final`, but I've not marked them as `sealed`. If you feel that you'd benefit from it, you're welcomed to inherit from them. The protected stuff is there for your use. -If you desire to have a common base for calls, each of the classes implements `IList` & `ILongList` interfaces. `ILongList` is a new interface, made alike `IList`, but unlocking full support for `long` indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of `IRecyclableList` what you could expect. +If you desire to have a common base for calls, each of the classes implements `IList` & `ILongList` interfaces. `ILongList` is a new interface, made alike `IList`, but unlocking full support for `long` indexing & use. Considering the characteristics of the classes I feel that the generic name is justified, instead of `IRecyclableList` what you could expect. -## Protection against inccorrect use & state +## Protection against incorrect use & state Having protection against invalid state of the object is an important part of software engineering. The classes included in this project provide the same level of protection as the regular `array` & `List` class. They will raise exceptions when invalid index is given. But there is no additional protection that the content & state of the objects internally used by these classes are valid. You can break them, if you want to. This decision is driven by performance measures. Considering wide use of the lists on the hot-paths, each additional check & operation starts counting. I've decided to eliminate all the checks for the sake of performance. If you feel that you need additional protection, feel free to inherit or reuse any of the provided classes to provide that. -## `this[int index]` & `Count` may overflow, but... -All the classes included in this package support `long` indexing and addressing. The `LongCount` property will always provide the total no. of items stored on the list. Since `Count` is just `LongCount` typecasted to `int`, its value may overflow. +## `this[int index]`, `Count`, `IndexOf(T item)` etc. may overflow, but... +All the classes included in this package support `long` indexing and addressing. The `LongCount` property will always provide the total no. of items stored on the list. Since `Count` is just `LongCount` type-casted to `int`, its value may overflow. But `List` addressing is kind of currently limited to `int` ranges anyway in practical scenarios. As such `Count` overflowing shouldn't be an issue when using the classes with existing code. If you use `Count` in your loops, it's recommended to replace it with `LongCount`, tough. You don't need to make any changes in `foreach` or other iterator loops. They are expected to transparently use the new classes & yield all items, beyond `int.MaxValue` range. @@ -37,20 +340,37 @@ This is by design, for the best performance. Initially there have been checks in If you foresee the overflowing as an issue, you're welcomed to propose an improvement & file a PR with proposed solution. +# Auto-properties, new language features & code style +When you look at the code you may note, that it looks like in early c# days. Where the new language features yield the same or better performance, they are & will be used. + +However, many of the new language features have proved to provide worse performance at some point in my testing. It includes auto-properties, for which I could see getters & setters generated in IL, instead of fields. Because all the classes are expected to be on the hot-paths, I've eliminated most of them. + +Similarly, you will find places in code with hints or warnings disabled. The decision was always driven by performance measures. The use case & business logic was carefully reviewed to ensure it's safe to take shortcuts, before making the change. If you run into failing scenario, please feel free to create a work item against it and/or file a PR to fix it. + +Finally, you'll find places with code commented out. That's to easily track what has been tried & what proved to perform worse. + +At any time, if you know & can show in your benchmarks that the code can be simplified without impacting the performance, feel free to file a PR with proposed changes. Please include benchmark results from your testing to speed up things. + # Thread Safety -The `public static` members of the classes are thread safe. Any instance members are not guaraneteed to be thread safe. You need to implement locking mechanism to safely use the classes in a multi-threading environment. +The `public static` members of the classes are thread safe. Any instance members are not guaranteed to be thread safe. You need to implement locking mechanism to safely use the classes in a multi-threading environment. # Use All the classes included in this package are meant to be direct replacements of their corresponding system classes. -* `List` ⇨ `RecyclableList` -* `SortedList` ⇨ `RecyclableSortedList` +* `IList` ⇨ `ILongList` +* `List` ⇨ `RecyclableLongList` * `Queue` ⇨ `RecyclableQueue` * `PriorityQueue` ⇨ `RecyclableSortedList` +* `SortedList` ⇨ `RecyclableSortedList` * `Stack` ⇨ `RecyclableStack` -* `IList` ⇨ `ILongList` Examples of each of the above classes are provided below. Please refer automated tests for more. + ## `RecyclableList` +It's the closest equivalent to `List` in regards to functionality. It supports items' removal & delivers the best performance in most scenarios. But it's limited to `int` range, tough. If you find that acceptable, my recommendation is to use this class as the direct replacement for `List`. + +Do note, that it implements `ILongList` interface, too. This is to allow having common code base regardless, if you use the basic `RecyclableList` or it's `long`-range version - `RecyclableLongList`. By doing that you can switch between them with highly limited changes in code. + +## `RecyclableLongList` ```CSharp public void RecyclableListExample() @@ -58,7 +378,7 @@ public void RecyclableListExample() int[] testNumbers = new[] { 1, 2, 2, 3 }; // Create - using var list = new RecyclableList(); + using var list = new RecyclableLongList(); // Add items foreach (var index in testNumbers) @@ -85,4 +405,10 @@ public void RecyclableListExample() list.Clear(); } -``` \ No newline at end of file +``` +## `RecyclableList` + +## `RecyclableList` + +## `RecyclableList` + diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs new file mode 100644 index 0000000..18aeaf4 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add.cs @@ -0,0 +1,51 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + public void PooledList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(ClearMode.Auto); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + public void RecyclableList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + public void RecyclableLongList_Add() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableLongList(); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs new file mode 100644 index 0000000..542f5a7 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsArray.cs @@ -0,0 +1,35 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + var list = new List(); + list.AddRange(data); + } + + public void PooledList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); + list.AddRange(data); + } + + public void RecyclableList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new RecyclableList(); + list.AddRange(data); + } + + public void RecyclableLongList_AddRangeWhenSourceIsArray() + { + var data = TestObjects; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs new file mode 100644 index 0000000..d16e91c --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIEnumerable.cs @@ -0,0 +1,35 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + var list = new List(); + list.AddRange(data); + } + + public void PooledList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); + list.AddRange(data); + } + + public void RecyclableList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new RecyclableList(); + list.AddRange(data); + } + + public void RecyclableLongList_AddRangeWhenSourceIsIEnumerable() + { + var data = TestObjectsAsIEnumerable; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs new file mode 100644 index 0000000..54a660b --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsIList.cs @@ -0,0 +1,35 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsRecyclableLongList; + var list = new List(); + list.AddRange(data); + } + + public void PooledList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsPooledList; + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); + list.AddRange(data); + } + + public void RecyclableList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsRecyclableLongList; + using var list = new RecyclableList(); + list.AddRange(data); + } + + public void RecyclableLongList_AddRangeWhenSourceIsIList() + { + var data = (IList)TestObjectsAsRecyclableLongList; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs new file mode 100644 index 0000000..580ebbb --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsList.cs @@ -0,0 +1,35 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + var list = new List(); + list.AddRange(data); + } + + public void PooledList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); + list.AddRange(data); + } + + public void RecyclableList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableList(); + list.AddRange(data); + } + + public void RecyclableLongList_AddRangeWhenSourceIsList() + { + var data = TestObjectsAsList; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs new file mode 100644 index 0000000..9f28def --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRangeWhenSourceIsSameType.cs @@ -0,0 +1,28 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void PooledList_AddRangeWhenSourceIsPooledList() + { + var data = TestObjectsAsPooledList; + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); + list.AddRange(data); + } + + public void RecyclableList_AddRangeWhenSourceIsRecyclableList() + { + var data = TestObjectsAsRecyclableList; + using var list = new RecyclableList(); + list.AddRange(data); + } + + public void RecyclableLongList_AddRangeWhenSourceIsRecyclableList() + { + var data = TestObjectsAsRecyclableList; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs new file mode 100644 index 0000000..8616914 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/AddRange_WithCapacity.cs @@ -0,0 +1,39 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(dataCount); + list.AddRange(data); + } + + public void PooledList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(capacity: dataCount, ClearMode.Auto); + list.AddRange(data); + } + + public void RecyclableList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(dataCount); + list.AddRange(data); + } + + public void RecyclableLongList_AddRange_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + list.AddRange(data); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs new file mode 100644 index 0000000..0838d36 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Add_WithCapacity.cs @@ -0,0 +1,51 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + var list = new List(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + public void PooledList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new PooledList(capacity: dataCount, ClearMode.Auto); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + public void RecyclableList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableList(dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + + public void RecyclableLongList_Add_WithCapacity() + { + var data = TestObjects; + var dataCount = TestObjectCount; + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: dataCount); + for (var i = 0; i < dataCount; i++) + { + list.Add(data[i]); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs new file mode 100644 index 0000000..07c1901 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_FirstItems.cs @@ -0,0 +1,60 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i]) >= 0); + } + } + + public void List_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + public void PooledList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + public void RecyclableList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + + public void RecyclableLongList_Contains_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs new file mode 100644 index 0000000..30a5dfe --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Contains_LastItems.cs @@ -0,0 +1,60 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + public void List_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + public void PooledList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + public void RecyclableList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + + public void RecyclableLongList_Contains_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Contains(data[^(i + 1)])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs new file mode 100644 index 0000000..7c30a64 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Count.cs @@ -0,0 +1,35 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_Count() + { + var data = TestObjects; + DoNothing(data.Length); + } + + public void List_Count() + { + var data = TestObjectsAsList; + DoNothing(data.Count); + } + + public void PooledList_Count() + { + var data = TestObjectsAsPooledList; + DoNothing(data.Count); + } + + public void RecyclableList_Count() + { + var data = TestObjectsAsRecyclableList; + DoNothing(data.Count); + } + + public void RecyclableLongList_Count() + { + var data = TestObjectsAsRecyclableLongList; + DoNothing(data.Count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs new file mode 100644 index 0000000..cccc65a --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create.cs @@ -0,0 +1,31 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public static void List_Create() + { + var list = new List(); + DoNothing(list); + } + + public static void PooledList_Create() + { + using var list = new PooledList(ClearMode.Auto); + DoNothing(list); + } + + public static void RecyclableList_Create() + { + using var list = new RecyclableList(); + DoNothing(list); + } + + public static void RecyclableLongList_Create() + { + using var list = new RecyclableLongList(); + DoNothing(list); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs new file mode 100644 index 0000000..372f2da --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Create_WithCapacity.cs @@ -0,0 +1,37 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_Create_WithCapacity() + { + var data = new long[TestObjectCount]; + DoNothing(data); + } + + public void List_Create_WithCapacity() + { + var list = new List(TestObjectCount); + DoNothing(list); + } + + public void PooledList_Create_WithCapacity() + { + using var list = new PooledList(capacity: TestObjectCount, ClearMode.Auto); + DoNothing(list); + } + + public void RecyclableList_Create_WithCapacity() + { + using var list = new RecyclableList(TestObjectCount); + DoNothing(list); + } + + public void RecyclableLongList_Create_WithCapacity() + { + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(list); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs new file mode 100644 index 0000000..b8fcb3f --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/GetItem..cs @@ -0,0 +1,158 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_GetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void List_GetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void PooledList_GetItem() + { + var data = TestObjectsAsPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void RecyclableList_GetItem() + { + var data = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void RecyclableLongList_GetItem() + { + var data = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void RecyclableLongList_GetItem_AsSpan() + { + var data = TestObjectsAsRecyclableLongList; + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + + var memoryBlocks = new Span(data.AsArray, 0, data.LastBlockWithData + 1); + for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) + { + var memoryBlock = new Span(memoryBlocks[blockIndex], 0, blockSize); + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + + if (nextItemBlockIndex == data.LastBlockWithData) + { + var memoryBlock = new Span(memoryBlocks[nextItemBlockIndex], 0, data.NextItemIndex); + for (var itemIndex = 0; itemIndex < memoryBlock.Length; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + } + + public void RecyclableLongList_GetItem_AsArray() + { + var data = TestObjectsAsRecyclableLongList; + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + + var memoryBlocks = data.AsArray; + for (var blockIndex = 0; blockIndex < nextItemBlockIndex; blockIndex++) + { + var memoryBlock = memoryBlocks[blockIndex]; + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + + if (nextItemBlockIndex == data.LastBlockWithData) + { + var memoryBlock = memoryBlocks[nextItemBlockIndex]; + blockSize = data.NextItemIndex; + for (var itemIndex = 0; itemIndex < blockSize; itemIndex++) + { + DoNothing(memoryBlock[itemIndex]); + } + } + } + + public void RecyclableLongList_GetItem_AsForEach() + { + var data = TestObjectsAsRecyclableLongList; + foreach (var item in data) + { + DoNothing(item); + } + } + + public void RecyclableLongList_GetItem_AsWhile() + { + var data = TestObjectsAsRecyclableLongList; + long dataCount = TestObjectCount; + if (dataCount == 0) + { + return; + } + + int blockSize = data.BlockSize; + int nextItemBlockIndex = data.NextItemBlockIndex; + int itemIndex = 0; + int blockIndex = 0; + var memoryBlocks = data.AsArray; + var memoryBlock = memoryBlocks[0]; + while (blockIndex < nextItemBlockIndex) + { + DoNothing(memoryBlock[itemIndex]); + if (itemIndex + 1 < blockSize) + { + itemIndex++; + } + else + { + blockIndex++; + memoryBlock = memoryBlocks[blockIndex]; + itemIndex = 0; + } + } + + if (nextItemBlockIndex == data.LastBlockWithData) + { + memoryBlock = memoryBlocks[nextItemBlockIndex]; + blockSize = data.NextItemIndex; + while (itemIndex < blockSize) + { + DoNothing(memoryBlock[itemIndex++]); + } + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs new file mode 100644 index 0000000..e1d182c --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_BestAndWorstCases.cs @@ -0,0 +1,77 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i])); + DoNothing(Array.IndexOf(list, data[^(i + 1)])); + } + } + + public void List_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void PooledList_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableList_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableLongList_IndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableLongList_LongIndexOf_BestAndWorstCases() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + DoNothing(list.LongIndexOf(data[^(i + 1)])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs new file mode 100644 index 0000000..f552702 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_FirstItems.cs @@ -0,0 +1,71 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[i])); + } + } + + public void List_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void PooledList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableLongList_IndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableLongList_LongIndexOf_FirstItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs new file mode 100644 index 0000000..1b7681f --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/IndexOf_LastItems.cs @@ -0,0 +1,71 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjects; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(Array.IndexOf(list, data[^(i + 1)])); + } + } + + public void List_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void PooledList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsPooledList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableLongList_IndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[^(i + 1)])); + } + } + + public void RecyclableLongList_LongIndexOf_LastItems() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCountForSlowMethods; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.LongIndexOf(data[^(i + 1)])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs new file mode 100644 index 0000000..9640ecd --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/LongCount.cs @@ -0,0 +1,17 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_LongCount() + { + var data = TestObjects; + DoNothing(data.LongLength); + } + + public void RecyclableLongList_LongCount() + { + var data = TestObjectsAsRecyclableLongList; + DoNothing(data.LongCount); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs index 630e3b9..b741a94 100644 --- a/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RecyclableCollections.Benchmarks.cs @@ -17,35 +17,35 @@ namespace Recyclable.Collections.Benchmarks public RecyclableCollectionsBenchmarks() : base() { TestObjectCount = 1; - // BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.PooledList; + BaselineBenchmarkType = RecyclableCollectionsBenchmarkSource.List; BenchmarkType = RecyclableCollectionsBenchmarkSource.RecyclableLongList; } [Params ( - //RecyclableCollectionsBenchmarkType.Add_WithCapacity, - //RecyclableCollectionsBenchmarkType.Add, - //RecyclableCollectionsBenchmarkType.AddRange_WithCapacity, - //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray, - //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable, - //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList, - //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList, - //RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType, - //RecyclableCollectionsBenchmarkType.Contains_FirstItems, - //RecyclableCollectionsBenchmarkType.Contains_LastItems, - //RecyclableCollectionsBenchmarkType.Count, - //RecyclableCollectionsBenchmarkType.Create_WithCapacity, - //RecyclableCollectionsBenchmarkType.Create, - //RecyclableCollectionsBenchmarkType.GetItem, - RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases - //RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, - //RecyclableCollectionsBenchmarkType.IndexOf_LastItems - //RecyclableCollectionsBenchmarkType.LongCount, - //RecyclableCollectionsBenchmarkType.Remove_FirstItems, - //RecyclableCollectionsBenchmarkType.Remove_LastItems, - //RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems, - //RecyclableCollectionsBenchmarkType.RemoveAt_LastItems, - //RecyclableCollectionsBenchmarkType.SetItem + RecyclableCollectionsBenchmarkType.Add_WithCapacity, + RecyclableCollectionsBenchmarkType.Add, + RecyclableCollectionsBenchmarkType.AddRange_WithCapacity, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsArray, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIEnumerable, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsIList, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsList, + RecyclableCollectionsBenchmarkType.AddRangeWhenSourceIsSameType, + RecyclableCollectionsBenchmarkType.Contains_FirstItems, + RecyclableCollectionsBenchmarkType.Contains_LastItems, + RecyclableCollectionsBenchmarkType.Count, + RecyclableCollectionsBenchmarkType.Create_WithCapacity, + RecyclableCollectionsBenchmarkType.Create, + RecyclableCollectionsBenchmarkType.GetItem, + RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases, + RecyclableCollectionsBenchmarkType.IndexOf_FirstItems, + RecyclableCollectionsBenchmarkType.IndexOf_LastItems, + RecyclableCollectionsBenchmarkType.LongCount, + RecyclableCollectionsBenchmarkType.Remove_FirstItems, + RecyclableCollectionsBenchmarkType.Remove_LastItems, + RecyclableCollectionsBenchmarkType.RemoveAt_FirstItems, + RecyclableCollectionsBenchmarkType.RemoveAt_LastItems, + RecyclableCollectionsBenchmarkType.SetItem )] public RecyclableCollectionsBenchmarkType TestCase { get; set; } = RecyclableCollectionsBenchmarkType.IndexOf_BestAndWorstCases; @@ -58,10 +58,10 @@ namespace Recyclable.Collections.Benchmarks [Params ( - //RecyclableCollectionsBenchmarkSource.Array, - //RecyclableCollectionsBenchmarkSource.List, - //RecyclableCollectionsBenchmarkSource.PooledList - //RecyclableCollectionsBenchmarkSource.RecyclableList, + RecyclableCollectionsBenchmarkSource.Array, + RecyclableCollectionsBenchmarkSource.List, + RecyclableCollectionsBenchmarkSource.PooledList, + RecyclableCollectionsBenchmarkSource.RecyclableList, RecyclableCollectionsBenchmarkSource.RecyclableLongList )] public override RecyclableCollectionsBenchmarkSource BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs new file mode 100644 index 0000000..f4edfe8 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_FirstItems.cs @@ -0,0 +1,51 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_RemoveAt_FirstItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + public void PooledList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Auto); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + public void RecyclableList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + + public void RecyclableLongList_RemoveAt_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + list.RemoveAt(i); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs new file mode 100644 index 0000000..30e6af3 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/RemoveAt_LastItems.cs @@ -0,0 +1,51 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_RemoveAt_LastItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + public void PooledList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Auto); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + public void RecyclableList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + + public void RecyclableLongList_RemoveAt_LastItems() + { + var data = TestObjects; + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + list.RemoveAt(i); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs new file mode 100644 index 0000000..3f18e65 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_FirstItems.cs @@ -0,0 +1,51 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_Remove_FirstItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + public void PooledList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Auto); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + public void RecyclableList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + + public void RecyclableLongList_Remove_FirstItems() + { + var data = TestObjects; + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.Remove(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs new file mode 100644 index 0000000..dfa2bef --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/Remove_LastItems.cs @@ -0,0 +1,51 @@ +using Collections.Pooled; + +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void List_Remove_LastItems() + { + var data = TestObjects; + var list = new List(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + public void PooledList_Remove_LastItems() + { + var data = TestObjects; + using var list = new PooledList(data, ClearMode.Auto); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + public void RecyclableList_Remove_LastItems() + { + var data = TestObjects; + using var list = new RecyclableList(data); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + + public void RecyclableLongList_Remove_LastItems() + { + var data = TestObjects; + using var list = new RecyclableLongList(data, minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + var dataCount = TestObjectCount / 10 > 0 ? TestObjectCount / 10 : TestObjectCount; + for (var i = data.Length - 1; i > data.Length - dataCount - 1; i--) + { + DoNothing(list.Remove(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs new file mode 100644 index 0000000..aad2f78 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Benchmarks/SetItem.cs @@ -0,0 +1,56 @@ +namespace Recyclable.Collections.Benchmarks +{ + public partial class RecyclableCollectionsBenchmarks + { + public void Array_SetItem() + { + var data = TestObjects; + var dataCount = TestObjectCount; + long index = 0; + for (var i = 0; i < dataCount; i++) + { + data[index] = TestObjects[index++]; + } + } + + public void List_SetItem() + { + var data = TestObjectsAsList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + public void PooledList_SetItem() + { + var data = TestObjectsAsPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + public void RecyclableList_SetItem() + { + var data = TestObjectsAsRecyclableList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + + public void RecyclableLongList_SetItem() + { + var data = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + data[i] = TestObjects[i]; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs new file mode 100644 index 0000000..faaaf4e --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Core/DataGenerator.cs @@ -0,0 +1,19 @@ +namespace Recyclable.Collections.Benchmarks.Core +{ + public static class DataGenerator + { + public static long[] EnumerateTestObjects(long testObjectCount) + { + var result = new long[testObjectCount]; + Console.WriteLine("******* TEST DATA ARRAY CREATED *******"); + + Span resultSpan = result; + for (int i = 0; i < testObjectCount; i++) + { + resultSpan[i] = i; + } + + return result; + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs b/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs deleted file mode 100644 index c438886..0000000 --- a/Recyclable.Collections.Benchmarks/DelegateVsComparerBenchmark.cs +++ /dev/null @@ -1,253 +0,0 @@ -using BenchmarkDotNet.Attributes; -using MiscUtil; -using MiscUtil.Extensions; - -namespace Recyclable.Collections.Benchmarks -{ - public delegate T Compare(T x, T y); - - public readonly struct MyComparerReadOnlyStruct : IComparer - { - public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - public struct MyComparerStruct : IComparer - { - public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - public class MyComparer : IComparer - { - public int Compare(long x, long y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0, - }; - } - - [MemoryDiagnoser] - public class DelegateVsComparerBenchmark - { - private static readonly Comparer _comparer = Comparer.Default; - - private const int _objectCount = 1_000_000; - private static readonly IEnumerable _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x); - private static readonly IEnumerable> _testListOfFunctions = _testObjects.Select>(static x => new MyComparer().Compare); - private static readonly IEnumerable> _testListOfComparers = _testObjects.Select(x => (IComparer)new MyComparer()); - private static readonly IEnumerable> _testListOfDelegates = _testObjects.Select(x => new Compare((x, y) => new MyComparer().Compare(x, y))); - private static readonly IEnumerable> _testListOfLocalFunctions = GetLocalFunctions(_testObjects); - private static readonly IEnumerable _testListOfClasses = _testObjects.Select(x => new MyComparer()); - private static readonly IEnumerable> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct()); - private static readonly IEnumerable> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer)new MyComparerStruct()); - private static IEnumerable _testListOfUnboxedStructs() where TComparer : IComparer, new() - { - foreach (var item in _testObjects) - { - yield return new TComparer(); - } - } - - private static IEnumerable> GetLocalFunctions(IEnumerable testObjects) - { - static int compareFunc(long x, long y) - { - return (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }; - } - - foreach (var item in testObjects) - { - yield return compareFunc; - } - } - - private static void TestWithComparer(IComparer comparer) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); - } - } - - [Benchmark(Baseline = true)] - public void Comparer() - { - TestWithComparer(_comparer); - } - - private static void TestWithFunc(Func compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - [Benchmark] - public void Func() - { - TestWithFunc((x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - [Benchmark] - public void StaticFunc() - { - TestWithFunc(static (x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - private static void TestWithDelegate(Compare compareFunc) - { - var testData = _testObjects.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) - { - _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); - } - } - - [Benchmark] - public void Delegate() - { - TestWithDelegate((x, y) => (x - y) switch - { - > 0 => 1, - < 0 => -1, - _ => 0 - }); - } - - [Benchmark] - public void ListOfFuncs() - { - const long x = 2, y = 5; - var testData = _testListOfFunctions.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareFunc = testData[itemIdx]; - _ = compareFunc(x, y); - } - } - - [Benchmark] - public void ListOfLocalFuncs() - { - const long x = 2, y = 5; - var testData = _testListOfLocalFunctions.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareFunc = testData[itemIdx]; - _ = compareFunc(x, y); - } - } - - [Benchmark] - public void ListOfComparers() - { - const long x = 2, y = 5; - var testData = _testListOfComparers.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - [Benchmark] - public void ListOfClasses() - { - const long x = 2, y = 5; - var testData = _testListOfClasses.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - [Benchmark] - public void ListOfStructs() - { - const long x = 2, y = 5; - var testData = _testListOfStructs.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - [Benchmark] - public void ListOfBoxedStructs() - { - const long x = 2, y = 5; - var testData = _testListOfBoxedStructs.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - [Benchmark] - public void ListOfUnboxedStructs() - { - const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - [Benchmark] - public void ListOfUnboxedReadOnlyStructs() - { - const long x = 2, y = 5; - var testData = _testListOfUnboxedStructs, long>().ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var comparer = testData[itemIdx]; - _ = comparer.Compare(x, y); - } - } - - [Benchmark] - public void ListOfDelegates() - { - const long x = 2, y = 5; - var testData = _testListOfDelegates.ToArray(); - for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) - { - var compareDelegate = testData[itemIdx]; - _ = compareDelegate(x, y); - } - } - } -} diff --git a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs b/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs deleted file mode 100644 index 79d6b71..0000000 --- a/Recyclable.Collections.Benchmarks/ListVsLinkedListBenchmark.cs +++ /dev/null @@ -1,223 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Recyclable.Collections.Benchmarks -{ - [MemoryDiagnoser] - public class ListVsLinkedListBenchmark - { - private const int _objectCount = 1_000_000; - private const int _blockSize = 100_000; - private static readonly object[] _testObjects = Enumerable.Range(1, _objectCount).Select(x => new object()).ToArray(); - - private static void DoNothing(T obj) - { - _ = obj; - } - - [Benchmark] - public void Array() - { - var list = new object[_testObjects.LongLength]; - for (var i = 0; i < _testObjects.Length; i++) - { - list[i] = _testObjects[i]; - } - - for (var i = 0; i < list.LongLength; i++) - { - DoNothing(list[i]); - } - } - - [Benchmark(Baseline = true)] - public void RecyclableList() - { - using var list = new RecyclableList(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Add(_testObjects[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(_testObjects[i]); - } - - list.Clear(); - } - - [Benchmark] - public void List() - { - var list = new List(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Add(_testObjects[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(list[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void LinkedList() - { - var list = new LinkedList(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - _ = list.AddLast(_testObjects[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - - [Benchmark] - public void RecyclableQueue() - { - var list = new RecyclableQueue(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Enqueue(_testObjects[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - - //[Benchmark] - public void Queue() - { - var list = new Queue(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Enqueue(_testObjects[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - - //[Benchmark] - public void PriorityQueue() - { - var list = new PriorityQueue(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Enqueue(_testObjects[i], i); - } - - while (list.Count > 0) - { - var node = list.Dequeue(); - DoNothing(node); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableSortedList_InUpdateMode() - { - var list = new RecyclableSortedList(_blockSize); - list.BeginUpdate(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Add(i, _testObjects[i]); - } - - list.EndUpdate(); - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(_testObjects[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableSortedList() - { - var list = new RecyclableSortedList(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Add(i, _testObjects[i]); - } - - for (var i = 0; i < list.LongCount; i++) - { - DoNothing(_testObjects[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void SortedList() - { - var list = new SortedList(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Add(i, _testObjects[i]); - } - - for (var i = 0; i < list.Count; i++) - { - DoNothing(_testObjects[i]); - } - - list.Clear(); - } - - //[Benchmark] - public void RecyclableStack() - { - var list = new RecyclableStack(_blockSize); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Push(_testObjects[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - - //[Benchmark] - public void Stack() - { - var list = new Stack(); - for (var i = 0; i < _testObjects.LongLength; i++) - { - list.Push(_testObjects[i]); - } - - foreach (var node in list) - { - DoNothing(node); - } - - list.Clear(); - } - } -} diff --git a/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs new file mode 100644 index 0000000..e46231a --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/ArraySizeLimitPoc.Benchmarks.cs @@ -0,0 +1,70 @@ +using BenchmarkDotNet.Attributes; +using System.Buffers; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum ArraySizeLimitPocBenchmarkType + { + ArraySizeLimit + } + + public class ArraySizeLimitPocBenchmarks : PocBenchmarkBase + { + public static void Run() + { + var benchmark = new ArraySizeLimitPocBenchmarks(); + benchmark.Setup(); + FindArraySizeLimit(); + benchmark.Cleanup(); + } + + [Params(2_147_483_605)] + public override int TestObjectCount { get => base.TestObjectCount; set => base.TestObjectCount = value; } + + [Benchmark(OperationsPerInvoke = 1)] + public void Max_Allowed_Size_2_147_483_605() + { + long[] arr = ArrayPool.Shared.Rent(TestObjectCount); + DoNothing(arr); + } + + [Benchmark(OperationsPerInvoke = 1)] + public static void FindArraySizeLimit() + { + var pool = ArrayPool.Shared; + var bufferSize = int.MaxValue / 2; + var oldValue = 0; + while (true) + { + try + { + Console.Write($"Allocating {bufferSize}..."); + var arr = pool.Rent(bufferSize); + Console.WriteLine("success"); + pool.Return(arr); + arr = null; + + var nextValue = bufferSize - oldValue; + oldValue = bufferSize; + bufferSize = checked(bufferSize + nextValue); + + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + } + catch (Exception) + { + Console.WriteLine("error"); + var nextValue = (bufferSize - oldValue) >> 1; + if (nextValue == 0) + { + break; + } + + bufferSize = oldValue + nextValue; + } + } + + Console.WriteLine("All done"); + throw new Exception($"Everything was successful. You can create {oldValue} byte big arrays. Exception is logged to prevent more benchmarks."); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs new file mode 100644 index 0000000..ea60943 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/BoolOrComparePoc.Benchmarks.cs @@ -0,0 +1,63 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum BoolOrComparePocBenchmarkType + { + Bool, + Compare, + } + + [MemoryDiagnoser] + public class BoolOrComparePocBenchmarks : PocBenchmarkBase + { + [Params(BoolOrComparePocBenchmarkType.Bool, BoolOrComparePocBenchmarkType.Compare)] + public override BoolOrComparePocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(8)] + public override int TestObjectCount { get => base.TestObjectCount; set => base.TestObjectCount = value; } + + [Params(true)] + public bool TestBool = true; + + public static void Run() + { + var benchmark = new BoolOrComparePocBenchmarks(); + benchmark.Setup(); + benchmark.Bool(); + benchmark.Compare(); + benchmark.Cleanup(); + } + + public void Bool() + { + if (TestBool) + { + DoNothing(TestBool); + } + else + { + DoNothing(TestBool); + } + } + + public void Compare() + { + if (TestObjectCount >= RecyclableDefaults.MinPooledArrayLength) + { + DoNothing(TestBool); + } + else + { + DoNothing(TestBool); + } + } + + protected override Action? GetTestMethod(BoolOrComparePocBenchmarkType benchmarkType) => benchmarkType switch + { + BoolOrComparePocBenchmarkType.Bool => Bool, + BoolOrComparePocBenchmarkType.Compare => Compare, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs new file mode 100644 index 0000000..c926998 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/DelegateVsComparerPoc.Benchmarks.cs @@ -0,0 +1,357 @@ +using BenchmarkDotNet.Attributes; +using MiscUtil; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum DelegateVsComparerPocBenchmarkType + { + Comparer, + Delegate, + Func, + ListOfBoxedStructs, + ListOfClasses, + ListOfComparers, + ListOfDelegates, + ListOfFuncs, + ListOfLocalFuncs, + ListOfStructs, + ListOfUnboxedReadOnlyStructs, + ListOfUnboxedStructs, + StaticFunc, + } + + [MemoryDiagnoser] + public class DelegateVsComparerPocBenchmarks : PocBenchmarkBase + { + [Params + ( + DelegateVsComparerPocBenchmarkType.Delegate, + DelegateVsComparerPocBenchmarkType.Func, + DelegateVsComparerPocBenchmarkType.ListOfBoxedStructs, + DelegateVsComparerPocBenchmarkType.ListOfClasses, + DelegateVsComparerPocBenchmarkType.ListOfComparers, + DelegateVsComparerPocBenchmarkType.ListOfDelegates, + DelegateVsComparerPocBenchmarkType.ListOfFuncs, + DelegateVsComparerPocBenchmarkType.ListOfLocalFuncs, + DelegateVsComparerPocBenchmarkType.ListOfStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedReadOnlyStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedStructs, + DelegateVsComparerPocBenchmarkType.StaticFunc + )] + public override DelegateVsComparerPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(DelegateVsComparerPocBenchmarkType.Comparer)] + public override DelegateVsComparerPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + #region Dummy classes + + private delegate T Compare(T x, T y); + + public readonly struct MyComparerReadOnlyStruct : IComparer + { + public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + private struct MyComparerStruct : IComparer + { + public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + private class MyComparer : IComparer + { + public int Compare(long x, long y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0, + }; + } + + #endregion + + public static void Run() + { + var benchmark = new DelegateVsComparerPocBenchmarks(); + benchmark.Setup(); + benchmark.Comparer(); + benchmark.Delegate(); + benchmark.Func(); + benchmark.ListOfBoxedStructs(); + benchmark.ListOfClasses(); + benchmark.ListOfComparers(); + benchmark.ListOfDelegates(); + benchmark.ListOfFuncs(); + benchmark.ListOfLocalFuncs(); + benchmark.ListOfStructs(); + benchmark.ListOfUnboxedReadOnlyStructs(); + benchmark.ListOfUnboxedStructs(); + benchmark.StaticFunc(); + benchmark.Cleanup(); + } + + private static readonly Comparer _comparer = Comparer.Default; + private IEnumerable? _testObjects; + private IEnumerable>? _testListOfFunctions; + private IEnumerable>? _testListOfComparers; + private IEnumerable>? _testListOfDelegates; + private IEnumerable>? _testListOfLocalFunctions; + private IEnumerable? _testListOfClasses; + private IEnumerable>? _testListOfStructs; + private IEnumerable>? _testListOfBoxedStructs; + private IEnumerable TestListOfUnboxedStructs() where TComparer : IComparer, new() + { + foreach (var _ in _testObjects!) + { + yield return new TComparer(); + } + } + + private static IEnumerable> GetLocalFunctions(IEnumerable testObjects) + { + static int compareFunc(long x, long y) + { + return (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }; + } + + foreach (var _ in testObjects) + { + yield return compareFunc; + } + } + + private void TestWithComparer(IComparer comparer) + { + var testData = _testObjects!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]); + } + } + + private void TestWithDelegate(Compare compareFunc) + { + var testData = _testObjects!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + private void TestWithFunc(Func compareFunc) + { + var testData = _testObjects!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++) + { + _ = compareFunc(testData[itemIdx], testData[itemIdx + 1]); + } + } + + public void Comparer() + { + TestWithComparer(_comparer); + } + + public void Func() + { + TestWithFunc((x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + public void StaticFunc() + { + TestWithFunc(static (x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + public void Delegate() + { + TestWithDelegate((x, y) => (x - y) switch + { + > 0 => 1, + < 0 => -1, + _ => 0 + }); + } + + public void ListOfFuncs() + { + const long x = 2, y = 5; + var testData = _testListOfFunctions!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareFunc = testData[itemIdx]; + _ = compareFunc(x, y); + } + } + + public void ListOfLocalFuncs() + { + const long x = 2, y = 5; + var testData = _testListOfLocalFunctions!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareFunc = testData[itemIdx]; + _ = compareFunc(x, y); + } + } + + public void ListOfComparers() + { + const long x = 2, y = 5; + var testData = _testListOfComparers!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + public void ListOfClasses() + { + const long x = 2, y = 5; + var testData = _testListOfClasses!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + public void ListOfStructs() + { + const long x = 2, y = 5; + var testData = _testListOfStructs!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + public void ListOfBoxedStructs() + { + const long x = 2, y = 5; + var testData = _testListOfBoxedStructs!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + public void ListOfUnboxedStructs() + { + const long x = 2, y = 5; + var testData = TestListOfUnboxedStructs, long>().ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + public void ListOfUnboxedReadOnlyStructs() + { + const long x = 2, y = 5; + var testData = TestListOfUnboxedStructs, long>().ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var comparer = testData[itemIdx]; + _ = comparer.Compare(x, y); + } + } + + public void ListOfDelegates() + { + const long x = 2, y = 5; + var testData = _testListOfDelegates!.ToArray(); + for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++) + { + var compareDelegate = testData[itemIdx]; + _ = compareDelegate(x, y); + } + } + + protected override Action? GetTestMethod(DelegateVsComparerPocBenchmarkType benchmarkType) => benchmarkType switch + { + DelegateVsComparerPocBenchmarkType.Comparer => Comparer, + DelegateVsComparerPocBenchmarkType.Delegate => Delegate, + DelegateVsComparerPocBenchmarkType.Func => Func, + DelegateVsComparerPocBenchmarkType.ListOfBoxedStructs => ListOfBoxedStructs, + DelegateVsComparerPocBenchmarkType.ListOfClasses => ListOfClasses, + DelegateVsComparerPocBenchmarkType.ListOfComparers => ListOfComparers, + DelegateVsComparerPocBenchmarkType.ListOfDelegates => ListOfDelegates, + DelegateVsComparerPocBenchmarkType.ListOfFuncs => ListOfFuncs, + DelegateVsComparerPocBenchmarkType.ListOfLocalFuncs => ListOfLocalFuncs, + DelegateVsComparerPocBenchmarkType.ListOfStructs => ListOfStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedReadOnlyStructs => ListOfUnboxedReadOnlyStructs, + DelegateVsComparerPocBenchmarkType.ListOfUnboxedStructs => ListOfUnboxedStructs, + DelegateVsComparerPocBenchmarkType.StaticFunc => StaticFunc, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + public override void Setup() + { + Console.WriteLine("******* SETTING UP COMMON TEST DATA *******"); + _testObjects = Enumerable.Range(1, TestObjectCount).Select(x => (long)x); + base.Setup(); + } + + public override void Cleanup() + { + _testObjects = null; + base.Cleanup(); + } + + protected override void PrepareData(T benchmarkType) + { + switch (benchmarkType) + { + case DelegateVsComparerPocBenchmarkType.ListOfBoxedStructs: + _testListOfBoxedStructs = _testObjects!.Select(_ => (IComparer)new MyComparerStruct()); + break; + case DelegateVsComparerPocBenchmarkType.ListOfClasses: + _testListOfClasses = _testObjects!.Select(_ => new MyComparer()); + break; + case DelegateVsComparerPocBenchmarkType.ListOfComparers: + _testListOfComparers = _testObjects!.Select(_ => (IComparer)new MyComparer()); + break; + case DelegateVsComparerPocBenchmarkType.ListOfDelegates: + _testListOfDelegates = _testObjects!.Select(_ => new Compare((x, y) => new MyComparer().Compare(x, y))); + break; + case DelegateVsComparerPocBenchmarkType.ListOfFuncs: + _testListOfFunctions = _testObjects!.Select>(static _ => new MyComparer().Compare); + break; + case DelegateVsComparerPocBenchmarkType.ListOfLocalFuncs: + _testListOfLocalFunctions = GetLocalFunctions(_testObjects!); + break; + case DelegateVsComparerPocBenchmarkType.ListOfStructs: + _testListOfStructs = _testObjects!.Select(_ => new MyComparerStruct()); + break; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs new file mode 100644 index 0000000..2fb2154 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/ModuloPoc.Benchmarks.cs @@ -0,0 +1,374 @@ +using BenchmarkDotNet.Attributes; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class ModuloPocBenchmarks + { + public static void Run() + { + var benchmark = new ModuloPocBenchmarks(); + benchmark.Setup(); + benchmark.AddWithFor(); + benchmark.AddWithForOptimized(); + benchmark.DividerAndModulusWithOperator(); + benchmark.MathDivRem(); + benchmark.MixedApproachWithForLoopOptimized(); + benchmark.MixedApproachWithSubtractWithStackalloc(); + benchmark.MixedApproachWithSubtractWithWhile(); + benchmark.MixedApproachWithSubtractWithWhileAsVector(); + benchmark.MixedApproachWithSubtractWithWhileWithSse2(); + benchmark.SubtractWithWhile(); + benchmark.SubtractWithWhileWithSse2(); + benchmark.XDivYMulX(); + benchmark.XDivYMulXWithLocalVar(); + } + + private long _cutoff; + private Vector128 _increment; + private long _lastCaseIndex; + + [GlobalSetup] + public void Setup() + { + _cutoff = BlockSize << 4; + _increment = Vector128.Create(-BlockSize, 1L); + _lastCaseIndex = 0; + } + + private static void DoNothing(T result, T remainder, [CallerMemberName] string? callerName = null) + { + //Console.WriteLine($"{callerName}: result = {result}, remainder = {remainder}"); + } + + //[Params(123, 10, 2, int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 10, int.MaxValue / 15, int.MaxValue / 20)] + [Params(int.MaxValue, int.MaxValue / 2, int.MaxValue / 3, int.MaxValue / 4, int.MaxValue / 6, int.MaxValue / 8, int.MaxValue / 10, int.MaxValue / 100)] + //[Params(int.MaxValue / 10, int.MaxValue / 20, int.MaxValue / 100)] + public int BlockSize { get; set; } = int.MaxValue; + + [Params(int.MaxValue, int.MaxValue / 1000, int.MaxValue / 10000)] + //[Params(int.MaxValue)] + //[Params(int.MaxValue * 100L)] + public long TestNumber { get; set; } = int.MaxValue; + + private void CalculateDividerAndModulusWithOperator(long i) + { + DoNothing(i / BlockSize, i % BlockSize); + } + + private void CalculateWithMathDivRem(long i) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + + private void CalculateWithAddWithFor(long i) + { + int result = 0; + int remainder = 0; + int blockSize = BlockSize; + for (long iteration = 0; iteration < i; iteration++) + { + if (remainder > blockSize - 1) + { + remainder = 0; + result++; + } + + remainder++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithAddWithForOptimized(long i) + { + long total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + + private void CalculateWithXDivYMulX(long i) + { + DoNothing(i / BlockSize, i - (i / BlockSize * BlockSize)); + } + + private void CalculateWithXDivYMulXWithLocalVar(long i) + { + int blockSize = BlockSize; + int result = (int)(i / blockSize); + DoNothing(result, i - (result * blockSize)); + } + + private void CalculateWithSubtractWithWhile(long i) + { + int result = 0; + long remainder = i; + int blockSize = BlockSize; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + + private void CalculateWithSubractWithWhileWithSse2(long i) + { + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + var inc = _increment; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, inc); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + + private void CalculateWithMixedApproachWithSubractWithWhile(long i) + { + long remainder; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out remainder), remainder); + } + else + { + int result = 0; + int blockSize = BlockSize; + remainder = i; + while (remainder >= blockSize) + { + remainder -= blockSize; + result++; + } + + DoNothing(result, remainder); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileAsVector(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var state = new Vector(new[] { 0L, i, 0, 0 }); + var increment = new Vector(new[] { 1L, -BlockSize, 0, 0 }); + int blockSize = BlockSize; + while (state[1] >= blockSize) + { + state += increment; + } + + DoNothing(state[0], state[1]); + } + } + + private void CalculateWithMixedApproachWithSubtractWithWhileWithSse2(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + var increment = _increment; + var state = Vector128.Create(i, 0); + int blockSize = BlockSize; + while (state.GetElement(0) >= blockSize) + { + state = Sse2.Add(state, increment); + } + + DoNothing(state.GetElement(1), state.GetElement(0)); + } + } + + private void CalculateWithMixedApproachWithSubtractWithStackalloc(long i) + { + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out long remainder), remainder); + } + else + { + Span state = stackalloc long[2] { i, 0 };// Vector128.Create(BlockSize, 0); + int blockSize = BlockSize; + while (state[0] >= blockSize) + { + state[0] -= blockSize; + state[1]++; + } + + DoNothing(state[1], state[0]); + } + } + + private void CalculateWithMixedApproachWithForLoopOptimized(long i) + { + long total; + if (i >= _cutoff) + { + DoNothing(Math.DivRem(i, BlockSize, out total), total); + } + else + { + total = 0; + int result = 0; + int blockSize = BlockSize; + while (total + blockSize <= i) + { + total += blockSize; + result++; + } + + DoNothing(result, i - total); + } + } + + [Benchmark(Baseline = true)] + public void DividerAndModulusWithOperator() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateDividerAndModulusWithOperator(i); + } + } + + [Benchmark] + public void MathDivRem() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMathDivRem(i); + } + } + + [Benchmark] + public void AddWithFor() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithFor(i); + } + } + + [Benchmark(Baseline = true)] + public void AddWithForOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithAddWithForOptimized(i); + } + } + + [Benchmark] + public void XDivYMulX() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulX(i); + } + } + + [Benchmark] + public void XDivYMulXWithLocalVar() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithXDivYMulXWithLocalVar(i); + } + } + + [Benchmark] + public void SubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubtractWithWhile(i); + } + } + + [Benchmark] + public void SubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithSubractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhile() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubractWithWhile(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileAsVector() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileAsVector(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithWhileWithSse2() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithWhileWithSse2(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithSubtractWithStackalloc() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithSubtractWithStackalloc(i); + } + } + + [Benchmark(Baseline = false)] + public void MixedApproachWithForLoopOptimized() + { + long testNumber = TestNumber; + for (long i = testNumber; i >= _lastCaseIndex; i--) + { + CalculateWithMixedApproachWithForLoopOptimized(i); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs new file mode 100644 index 0000000..8565d27 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/PocBenchmarkBase.cs @@ -0,0 +1,121 @@ +using BenchmarkDotNet.Attributes; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public abstract class PocBenchmarkBase + { + public virtual TBenchmarkType? BenchmarkType { get; set; } + public virtual TBenchmarkType? BaselineBenchmarkType { get; set; } + + private Action? _testMethod; + protected Action TestMethod + { + get => _testMethod ?? throw new OperationCanceledException("Skip - Custom benchmark run in progress"); + set => _testMethod = value; + } + + private Action? _baselineMethod; + protected Action BaselineMethod + { + get => _baselineMethod ?? throw new OperationCanceledException("Skip - No baseline defined"); + set => _baselineMethod = value; + } + + [Benchmark(Baseline = true)] + public void Baseline() => BaselineMethod.Invoke(); + + [Benchmark] + public void Actual() => TestMethod.Invoke(); + + // 0 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524_288 + // 1048576 2097152 4194304 8388608 16777216 33554432 67108864 134217728 268435456 536870912 + // 1073741824 2147483648 + + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 1_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 100_000_000)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 850_000 - 1, 1_000_000, RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1_000_000, 10_000_000, 100_000_000, 1_000_000_000)] + // [Params(192, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144)] + // [Params(524_288, 786_432, 1_048_576)] + // [Params(262_144, 393_216, 524_288)] + [Params(850_000)] + // [Params(RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 10_000_000, 33_554_432, 50_000_000, 100_000_000)] + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, RecyclableDefaults.MinItemsCountForParallelization - 1)] + //[Params(RecyclableDefaults.MinItemsCountForParallelization, 1_048_576, 2_097_152, 4_194_304, 8_388_608, 16_777_216, 33_554_432, RecyclableDefaults.MaxPooledBlockSize)] + // [Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 32, 40, 50, 60, 80, 90, 100, 128)] + // [Params(RecyclableDefaults.MinItemsCountForParallelization * 3)] + // [Params(1_048_576)] + public virtual int TestObjectCount { get; set; } = 1; //33_554_432; //524_288; //131_072; + public virtual int TestObjectCountForSlowMethods => (TestObjectCount / 2) + 1;// Math.Min(TestObjectCount, 131_072); + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200)] + //[Params(10, 20, 50, 100)] + //[Params(1, 10)] + public int Divider = 10; + //public int BlockSize = 1_048_576;//int.MaxValue - 1; + public int BlockSize => TestObjectCount switch + { + 0 => 1, + > 0 and <= 10_240 => TestObjectCount, + _ => TestObjectCount / Divider > 0 ? TestObjectCount / Divider : TestObjectCount + }; + + public byte BlockSizePow2BitShift => checked((byte)(31 - BitOperations.LeadingZeroCount((uint)BlockSize))); + + [MethodImpl(MethodImplOptions.NoInlining)] + protected static void DoNothing(in T item) + { + _ = item; + } + + protected static Exception CreateUnknownBenchmarkTypeException(T benchmarkType) + { + return new InvalidOperationException($"******* UNKNOWN BENCHMARK TYPE {{{benchmarkType}}} *******"); + } + + protected static Exception CreateMethodNotFoundException(in string methodName, in string? className) + { + return new MethodAccessException($"******* METHOD {{{methodName}}} NOT FOUND IN CLASS {{{className}}} *******"); + } + + protected virtual void PrepareData(T benchmarkType) { } + protected virtual Action? GetTestMethod(TBenchmarkType? benchmarkType) => null; + + [GlobalCleanup] + public virtual void Cleanup() + { + _baselineMethod = null; + _testMethod = null; + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); + } + + [GlobalSetup] + public virtual void Setup() + { + Console.WriteLine($"******* SETTING UP TEST CASE FOR BENCHMARK {{{BenchmarkType}}} *******"); + + if (!EqualityComparer.Default.Equals(BaselineBenchmarkType, default)) + { + Console.WriteLine("******* SETTING UP BASELINE DATA *******"); + PrepareData(BaselineBenchmarkType); + _baselineMethod = GetTestMethod(BaselineBenchmarkType); + } + + //~ We don't want to prepare data if baseline data is the same is benchmark data. + //~ One would override another - waste of time & resources. + if (!EqualityComparer.Default.Equals(BaselineBenchmarkType, BenchmarkType)) + { + Console.WriteLine("******* SETTING UP TEST CASE DATA *******"); + //~ If BaselineBenchmarkType == null, then we'll come here only if BaselineBenchmarkType != null. + PrepareData(BenchmarkType!); + _testMethod = GetTestMethod(BenchmarkType); + } + + Console.WriteLine("******* DATA PREPARED *******"); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs new file mode 100644 index 0000000..3f0146e --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs @@ -0,0 +1,2858 @@ +using Recyclable.Collections.Pools; +using System.Buffers; +using System.Collections; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + #region RecyclableLongList v1 + + internal class RecyclableLongListV1 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private int _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => (int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void DoRemoveAt(long index) + { + int blockSize = _blockSize; + int nextItemIndex = _nextItemIndex; + T[][] memoryBlocks = _memoryBlocks; + _longCount--; + if (index < _longCount) + { + int sourceBlockIndex = (int)((index + 1) >> _blockSizePow2Shift); + int sourceItemIndex = (int)((index + 1) & (blockSize - 1)); + + int targetBlockIndex = (int)(index >> _blockSizePow2Shift); + int targetItemIndex = (int)(index & (blockSize - 1)); + + int lastTakenBlockIndex = LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != _nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? Math.Min(blockSize - sourceItemIndex, blockSize - targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + if (nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + new Span(memoryBlocks[_nextItemBlockIndex], _nextItemIndex, 1)[0] = default; +#pragma warning restore CS8601 + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + (blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + (lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long Resize(ref T[][]? source, int minBlockSize, long oldCapacity, long newCapacity, ArrayPool memoryBlocksPool, ArrayPool blockArrayPool) + { + int minBlockSizePowerToShift = 31 - BitOperations.LeadingZeroCount((uint)minBlockSize); + int sourceBlockCount = (int)(oldCapacity >> minBlockSizePowerToShift); + int requiredBlockCount = (int)(newCapacity >> minBlockSizePowerToShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0); + int blockIndex; + T[][]? newMemoryBlocks; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(source, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(source!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(source!, true); + } + } + + source = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return newMemoryBlocks.Length << minBlockSizePowerToShift; + } + + internal long EnsureCapacity(long requestedCapacity) + { + long newCapacity = _capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + newCapacity = Resize(ref _memoryBlocks!, _blockSize, _capacity, newCapacity, _memoryBlocksPool, _blockArrayPool); + if (newCapacity > 0 && _capacity == 0) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = MathUtils.GetPow2Shift(_blockSize); + } + + _capacity = newCapacity; + return newCapacity; + } + + public RecyclableLongListV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public RecyclableLongListV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + blockArrayPool ??= _defaultBlockArrayPool; + _memoryBlocksPool = memoryBlocksPool; + _blockArrayPool = blockArrayPool; + + if (expectedItemsCount > 0) + { + _capacity = Resize(ref _memoryBlocks!, (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize), 0, expectedItemsCount.Value, memoryBlocksPool, blockArrayPool); + if (_memoryBlocks.Length > 0) + { + minBlockSize = _memoryBlocks[0].Length; + } + + _blockSize = minBlockSize; + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + } + else + { + _blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize); + _blockSizePow2Shift = MathUtils.GetPow2Shift(minBlockSize); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[(int)(index >> _blockSizePow2Shift)])[(int)(index & (_blockSize - 1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(_longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListV1 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = (int)Math.Min(itemsCount - copiedCount, itemsSpan.Length); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = (int)(targetCapacity & (blockSize - 1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _blockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is RecyclableLongListV1 sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : (int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public void Insert(int index, T item) => throw new NotSupportedException(); + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, (int)_longCount) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + public bool Remove(T item) + { + var itemIndex = LongIndexOf(item); + if (itemIndex >= 0) + { + DoRemoveAt(itemIndex); + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + public void RemoveAt(int index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + public void RemoveAt(long index) + { + if (index > _longCount - 1 || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + DoRemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListV1() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableLongList v2 + + internal class RecyclableLongListV2 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2Shift; + private int _nextItemBlockIndex; + private int _nextItemIndex; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastTakenBlockIndex => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => checked((int)(_capacity >> _blockSizePow2Shift) + ((_capacity & (_blockSize - 1)) > 0 ? 1 : 0)); + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongListV2 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceItemIndex = checked((int)((index + 1) & (blockSize - 1))); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetItemIndex = checked((int)(index & (blockSize - 1))); + + int lastTakenBlockIndex = list.LastTakenBlockIndex; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _memoryBlocks.Length; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongListV2 list, int minBlockSize, byte minBlockSizeBitShift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = checked((int)(list._capacity >> minBlockSizeBitShift)); + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizeBitShift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && (minBlockSize >= RecyclableDefaults.MinPooledArrayLength)) + { + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, sourceBlockCount - requiredBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++]!, NeedsClearing); + } + } + + ArrayPool memoryBlocksPool = list._memoryBlocksPool; + + // Allocate new memory block for all arrays + T[][]? newMemoryBlocks = requiredBlockCount < RecyclableDefaults.MinPooledArrayLength + ? (new T[requiredBlockCount][]) + : memoryBlocksPool.Rent(requiredBlockCount); + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, newMemoryBlocks.Length - sourceBlockCount); + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + return (long)newMemoryBlocks.Length << minBlockSizeBitShift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long EnsureCapacity(RecyclableLongListV2 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = RecyclableLongListV2.Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + if (newCapacity > 0 && blockSize != list._memoryBlocks[0].Length) + { + blockSize = list._memoryBlocks[0].Length; + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockArrayPool = RecyclableArrayPool.Shared(blockSize); + } + + list._blockSizePow2Shift = (byte)MathUtils.GetPow2Shift(blockSize); + list._blockSize = blockSize; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public RecyclableLongListV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks!.Length > 0 && _blockSize != _memoryBlocks[0].Length) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; + } + } + else + { + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + + public RecyclableLongListV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) + { + memoryBlocksPool ??= _defaultMemoryBlocksPool; + _memoryBlocksPool = memoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = minBlockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(minBlockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_memoryBlocks!.Length > 0 && minBlockSize != _memoryBlocks[0].Length) + { + _blockSize = _memoryBlocks[0].Length; + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)minBlockSize)); + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool = RecyclableArrayPool.Shared(_blockSize); + } + } + else + { + _blockSize = minBlockSize; + } + } + else + { + _blockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + _blockArrayPool = _blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(_blockSize) + : RecyclableArrayPool.Null; + + _blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)_blockSize)); + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & (_blockSize - 1)))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & (_blockSize - 1)]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & (_blockSize - 1)] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListV2 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._memoryBlocks.Length); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & (blockSize - 1)) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & (blockSize - 1))); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _memoryBlocks.Length); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _memoryBlocks.Length; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is RecyclableLongListV2 sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + } + } + + _capacity = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOf(item, _memoryBlocks, LastTakenBlockIndex, _blockSize, _nextItemIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize < RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSize - 1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListV2() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableLongList v3 + + internal class RecyclableLongListV3 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2Shift; + private int _blockSizeMinus1; + private bool _blockPoolingRequired; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongListV3 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2Shift)); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2Shift)); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongListV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + Span memoryBlocksSpan; + + // Release excessive blocks if we're downsizing + if (requiredBlockCount < sourceBlockCount && list._blockPoolingRequired) + { + memoryBlocksSpan = new(list._memoryBlocks, requiredBlockCount, requiredBlockCount - sourceBlockCount); + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + blockArrayPool.Return(memoryBlocksSpan[blockIndex++], NeedsClearing); + } + } + + ArrayPool memoryBlocksPool = list._memoryBlocksPool; + + // Allocate new memory block for all arrays + bool memoryBlockPoolingRequired = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength; + T[][] newMemoryBlocks = memoryBlockPoolingRequired ? memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + memoryBlocksPool.Return(list._memoryBlocks, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + memoryBlocksSpan = new(newMemoryBlocks, sourceBlockCount, requiredBlockCount - sourceBlockCount); + if (list._blockPoolingRequired) + { + if (sourceBlockCount == 0) + { + memoryBlocksSpan[0] = blockArrayPool.Rent(minBlockSize); + minBlockSize = memoryBlocksSpan[0].Length; + blockIndex = 1; + } + else + { + blockIndex = 0; + } + + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = blockArrayPool.Rent(minBlockSize); + } + } + else + { + blockIndex = 0; + while (blockIndex < memoryBlocksSpan.Length) + { + memoryBlocksSpan[blockIndex++] = new T[minBlockSize]; + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableLongListV3 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2Shift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + if (blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + list._blockPoolingRequired = true; + list._blockArrayPool = blockArrayPool ?? RecyclableArrayPool.Shared(blockSize); + } + else + { + list._blockPoolingRequired = false; + list._blockArrayPool = RecyclableArrayPool.Null; + } + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableLongListV3 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2Shift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2Shift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2Shift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2Shift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2Shift])[index & _blockSizeMinus1] = value; + } + + public T[][] MemoryBlocks { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListV3 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_blockPoolingRequired) + { + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + int memoryBlocksCount = memoryBlocksSpan.Length; + ArrayPool blockArrayPool = _blockArrayPool; + for (int toRemoveIdx = 0; toRemoveIdx < memoryBlocksCount; toRemoveIdx++) + { + blockArrayPool.Return(memoryBlocksSpan[toRemoveIdx], NeedsClearing); + memoryBlocksSpan[toRemoveIdx] = _emptyBlockArray; + } + } + + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0) + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOf(item, _memoryBlocks, LastBlockWithData, _blockSize, _nextItemIndex); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockPoolingRequired) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListV3() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + public partial class RecyclableLongListPocBenchmarks + { + public void RecyclableLongList_EnsureCapacityV1_ByPowOf2() + { + using var list = new RecyclableLongListV1(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacityV2_ByPowOf2() + { + using var list = new RecyclableLongListV2(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacityV3_ByPowOf2() + { + using var list = new RecyclableLongListV3(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacity_ByPowOf2() + { + using var list = new RecyclableLongList(minBlockSize: BlockSize, expectedItemsCount: TestObjectCount); + DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacityV1_ByBlockSize() + { + using var list = new RecyclableLongListV1(); + DoNothing(_ensureCapacityV1Func.Invoke(list, new object[] { TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacityV2_ByBlockSize() + { + using var list = new RecyclableLongListV2(); + DoNothing(_ensureCapacityV2Func.Invoke(null, new object[] { list, TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacityV3_ByBlockSize() + { + using var list = new RecyclableLongListV3(); + DoNothing(_ensureCapacityV3Func.Invoke(null, new object[] { list, TestObjectCount })); + } + + public void RecyclableLongList_EnsureCapacity_ByBlockSize() + { + using var list = new RecyclableLongList(); + DoNothing(_ensureCapacityNewFunc.Invoke(null, new object[] { list, TestObjectCount })); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs new file mode 100644 index 0000000..9fb3289 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs @@ -0,0 +1,4727 @@ +using Microsoft.Extensions.ObjectPool; +using Recyclable.Collections.Parallel; +using Recyclable.Collections.Pools; +using System.Buffers; +using System.Collections; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + #region ItemRangesIterator V1 + + internal sealed class ItemRangeV1 : IDisposable + { + public int BlockIndex; + public int StartingItemIndex; + public long ItemsToSearchCount; + internal ObjectPool? _assignedPool; + + public ItemRangeV1() + { + + } + + public ItemRangeV1(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + BlockIndex = blockIndex; + ItemsToSearchCount = itemsToSearchCount; + StartingItemIndex = startingItemIndex; + } + + public void Dispose() + { + if (_assignedPool != null) + { + var pool = _assignedPool; + _assignedPool = null; + pool!.Return(this); + } + + GC.SuppressFinalize(this); + } + } + + internal static class ItemRangePoolV1 + { + [ThreadStatic] + private static readonly ObjectPool _shared; + public static ObjectPool Shared => _shared; + + static ItemRangePoolV1() + { + _shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + } + + public static ItemRangeV1 Create(int blockIndex, int startingItemIndex, long itemsToSearchCount) + { + var itemRange = _shared.Get(); + itemRange.BlockIndex = blockIndex; + itemRange.ItemsToSearchCount = itemsToSearchCount; + itemRange.StartingItemIndex = startingItemIndex; + return itemRange; + } + } + + internal static class ItemRangesIteratorV1 + { + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] + public static void Iterate(int blockIndex, int blockSize, byte blockSizePow2BitShift, long itemsCount, long step, Func action) + { + int itemIndex = 0; + ItemRangeV1 itemRange; + while (itemsCount > step) + { + itemRange = ItemRangePoolV1.Create(blockIndex, itemIndex, step); + if (!action(itemRange)) + { + break; + } + + itemsCount -= step; + blockIndex += (int)((itemIndex + step) >> blockSizePow2BitShift); + itemIndex = (int)((itemIndex + step) & (blockSize - 1)); + //itemIndex += step; + //while (itemIndex >= blockSize) + //{ + // blockIndex++; + // itemIndex -= blockSize; + //} + } + + itemRange = ItemRangePoolV1.Create(blockIndex, itemIndex, itemsCount); + _ = action(itemRange); + } + } + + #endregion + + #region RecyclableLongList IndexOf v1 + + internal class RecyclableLongListIndexOfV1 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2BitShift; + private int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCountIndexOfStep; + protected long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongListIndexOfV1 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2BitShift)); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2BitShift)); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableLongListIndexOfV1 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int startingItemIndex = itemRange.StartingItemIndex; + T[][] memoryBlocks = list._memoryBlocks; + int index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, startingItemIndex, (int)Math.Min(blockSize - startingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - startingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndex; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? -1 + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) + //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private long DoIndexOfParallel(T item) + { + var itemFoundSignal = ManualResetEventSlimmerPool.Create(false); + using var allDoneSignal = new Barrier(1); //BarrierSlimmer.Create(1); + long foundItemIndex = -1L; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIteratorV1.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + itemFoundSignal.Dispose(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndex; + + //int blockIndex = 0; + //int itemIndex = 0; + //long itemsCount = _longCount; + //while (itemsCount > step) + //{ + // if (!LocalIndexOf(new(blockIndex, itemIndex, step))) + // { + // break; + // } + + // itemsCount -= step; + // //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + // blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift); + // itemIndex = (int)((itemIndex + step) & _blockSizeMinus1); + // //itemIndex += step; + // //while (itemIndex >= blockSize) + // //{ + // // blockIndex++; + // // itemIndex -= blockSize; + // //} + //} + + //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRangeV1 itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongListIndexOfV1 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + }; + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV1 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableLongListIndexOfV1 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV1(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV1(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListIndexOfV1 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + //// Optimization for tiny collections to avoid any vars etc. + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListIndexOfV1() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableLongList IndexOf v2 + + internal class RecyclableLongListIndexOfV2 : IDisposable, IList + { + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2BitShift; + private int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCountIndexOfStep; + protected long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongListIndexOfV2 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = checked((int)((index + 1) >> list._blockSizePow2BitShift)); + int sourceItemIndex = checked((int)((index + 1) & list._blockSizeMinus1)); + + int targetBlockIndex = checked((int)(index >> list._blockSizePow2BitShift)); + int targetItemIndex = checked((int)(index & list._blockSizeMinus1)); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + return; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableLongListIndexOfV2 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndex; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndex; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? -1 + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + //? Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(itemRange.List._nextItemIndex, itemsToSearchCount))) + //: Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private long DoIndexOfParallel(T item) + { + var itemFoundSignal = ManualResetEventSlimmerPool.Create(false); + using var allDoneSignal = new Barrier(1); + long foundItemIndex = -1L; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIteratorV1.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + itemFoundSignal.Dispose(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndex; + + //int blockIndex = 0; + //int itemIndex = 0; + //long itemsCount = _longCount; + //while (itemsCount > step) + //{ + // if (!LocalIndexOf(new(blockIndex, itemIndex, step))) + // { + // break; + // } + + // itemsCount -= step; + // //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + // blockIndex += (int)((itemIndex + step) >> _blockSizePow2BitShift); + // itemIndex = (int)((itemIndex + step) & _blockSizeMinus1); + // //itemIndex += step; + // //while (itemIndex >= blockSize) + // //{ + // // blockIndex++; + // // itemIndex -= blockSize; + // //} + //} + + //_ = LocalIndexOf(new(blockIndex, itemIndex, itemsCount)); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRangeV1 itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndex; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndex; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongListIndexOfV2 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV2 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableLongListIndexOfV2 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV2(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV2(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListIndexOfV2 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableLongListIndexOfV2 sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + //// Optimization for tiny collections to avoid any vars etc. + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListIndexOfV2() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableLongList IndexOf v3 + + internal class RecyclableLongListIndexOfV3 : IDisposable, IList + { + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + private int _blockSize; + private byte _blockSizePow2BitShift; + private int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + protected T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + protected long _longCountIndexOfStep; + protected long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongListIndexOfV3 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2BitShift); + int sourceItemIndex = (int)((index + 1) & list._blockSizeMinus1); + + int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); + int targetItemIndex = (int)(index & list._blockSizeMinus1); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private static long DoLongIndexOfParallel(in RecyclableLongListIndexOfV3 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndexLong; + } + + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 || blockIndex > list.LastBlockWithData + ? ItemNotFoundIndex + : blockIndex == list._nextItemBlockIndex + ? Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount) + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + + return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private long DoIndexOfParallel(T item) + { + var itemFoundSignal = ManualResetEventSlimmerPool.Create(false); + using var allDoneSignal = new Barrier(1); + long foundItemIndex = ItemNotFoundIndexLong; + Exception? ex = null; + + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + ItemRangesIteratorV1.Iterate(0, _blockSize, _blockSizePow2BitShift, _longCount, _longCountIndexOfStep, LocalIndexOf); + allDoneSignal.SignalAndWait(); + itemFoundSignal.Dispose(); + if (ex == null) + { + return foundItemIndex; + } + + ex.CaptureAndRethrow(); + return ItemNotFoundIndexLong; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + bool LocalIndexOf(ItemRangeV1 itemRange) + { + _ = allDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = DoLongIndexOfParallel(this, itemRange, item, itemFoundSignal); + if (index >= 0) + { + itemFoundSignal.Set(); + foundItemIndex = index; + } + } + catch (Exception e) + { + //_ = Interlocked.Exchange(ref ex, e); + lock (allDoneSignal) + { + ex = e; + } + } + finally + { + itemRange.Dispose(); + allDoneSignal.RemoveParticipant(); + } + }); + //.ContinueWith(x => + //{ + // if (x.Exception != null) + // { + // lock (allDoneSignal) + // { + // ex = x.Exception; + // } + // } + //}); + + return !itemFoundSignal.IsSet; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndexLong; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongListIndexOfV3 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV3 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableLongListIndexOfV3 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV3(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV3(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListIndexOfV3 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndexLong + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListIndexOfV3() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + #region RecyclableLongList IndexOf v4 + + internal static class RecyclableLongListIndexOfV4Extensions + { + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + public static long DoLongIndexOfParallel(this RecyclableLongListIndexOfV4 list, in ItemRangeV1 itemRange, in T itemToFind, in ManualResetEventSlimmer itemFoundSignal) + { + int blockSize = list._blockSize; + int blockIndex = itemRange.BlockIndex; + long itemsToSearchCount = itemRange.ItemsToSearchCount; + int index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, itemRange.StartingItemIndex, (int)Math.Min(blockSize - itemRange.StartingItemIndex, itemsToSearchCount)); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return (blockIndex << list._blockSizePow2BitShift) + index; + } + + // We don't need Math.Min here, because if itemsToSearchCount < blockSize - itemRange.StartingItemIndex, then it would mean that + // there are no more items and we already scanned all. We want to break the loop. Subtracting a higher value from itemsToSearchCount + // will always result in a negative result. + itemsToSearchCount -= blockSize - itemRange.StartingItemIndex; + if (itemsToSearchCount <= 0) + { + return ItemNotFoundIndexLong; + } + + //T itemToFind = itemRange.ItemToFind; + //int lastBlockWithData = itemRange.LastBlockWithData; + blockIndex++; + while (itemsToSearchCount > blockSize) + { + index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return ItemNotFoundIndexLong; + } + else if (index >= 0) + { + return ((long)blockIndex << list._blockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + blockIndex++; + } + + index = itemsToSearchCount <= 0 + ? ItemNotFoundIndex + : Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, (int)itemsToSearchCount); + + return !itemFoundSignal.IsSet && index >= 0 ? ((long)blockIndex << list._blockSizePow2BitShift) + index : ItemNotFoundIndexLong; + } + } + + internal sealed class ParallelSynchronizationContextV4 : IDisposable + { + public readonly ManualResetEventSlimmer ItemFoundSignal; + public readonly Barrier AllDoneSignal; + public long FoundItemIndex; + public Exception? Exception; + + public ParallelSynchronizationContextV4(int participantCount) + { + ItemFoundSignal = ManualResetEventSlimmerPool.Create(false); + AllDoneSignal = new(participantCount); + } + + public void Dispose() + { + ItemFoundSignal.Dispose(); + AllDoneSignal.Dispose(); + + GC.SuppressFinalize(this); + } + } + + internal class RecyclableLongListIndexOfV4 : IDisposable, IList + { + private const long ItemNotFoundIndexLong = -1L; + private const int ItemNotFoundIndex = -1; + private const double OptimalParallelSearchStep = 0.329; + + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + internal int _blockSize; + internal byte _blockSizePow2BitShift; + internal int _blockSizeMinus1; + private int _nextItemBlockIndex; + private int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + internal T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + public int LastBlockWithData => _nextItemBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + + internal long _longCountIndexOfStep; + internal long _longCount; + + public long LongCount + { + get => _longCount; + set + { + _longCount = value; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongListIndexOfV4 list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2BitShift); + int sourceItemIndex = (int)((index + 1) & list._blockSizeMinus1); + + int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); + int targetItemIndex = (int)(index & list._blockSizeMinus1); + + int lastTakenBlockIndex = list.LastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + _longCount += copied + i - _longCount; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _longCountIndexOfStep = Math.Max((long)(requiredCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)] + private long DoIndexOfParallel(T item) + { + var synchronizationContext = new ParallelSynchronizationContextV4(1); + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + Iterate(synchronizationContext, this, item, ScheduleIndexOfTasks); + + synchronizationContext.AllDoneSignal.SignalAndWait(); + if (synchronizationContext.Exception == null) + { + return synchronizationContext.FoundItemIndex; + } + + synchronizationContext.Exception.CaptureAndRethrow(); + return ItemNotFoundIndexLong; + } + + private static bool ScheduleIndexOfTasks(ParallelSynchronizationContextV4 context, RecyclableLongListIndexOfV4 list, T itemToFind, ItemRangeV1 itemRange) + { + _ = context.AllDoneSignal.AddParticipant(); + _ = Task.Factory.StartNew(() => + { + try + { + var index = list.DoLongIndexOfParallel(itemRange, itemToFind, context.ItemFoundSignal); + if (index >= 0) + { + context.ItemFoundSignal.Set(); + _ = Interlocked.Exchange(ref context.FoundItemIndex, index); + } + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.AllDoneSignal.RemoveParticipant(); + } + }); + + return !context.ItemFoundSignal.IsSet; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.NoInlining)] + public static void Iterate(in ParallelSynchronizationContextV4 context, RecyclableLongListIndexOfV4 list, in T itemToFind, Func, T, ItemRangeV1, bool> action) + { + int itemIndex = 0; + ItemRangeV1 itemRange; + int blockIndex = 0; + long itemsCount = list._longCount; + long step = list._longCountIndexOfStep; + while (itemsCount > step) + { + itemRange = new(blockIndex, itemIndex, step); + if (!action(context, list, itemToFind, itemRange)) + { + break; + } + + itemsCount -= step; + //blockIndex = (int)Math.DivRem(itemIndex + step, blockSize, out itemIndex); + blockIndex += (int)((itemIndex + step) >> list._blockSizePow2BitShift); + itemIndex = (int)((itemIndex + step) & list._blockSizeMinus1); + //itemIndex += step; + //while (itemIndex >= blockSize) + //{ + // blockIndex++; + // itemIndex -= blockSize; + //} + } + + itemRange = new(blockIndex, itemIndex, itemsCount); + _ = action(context, list, itemToFind, itemRange); + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + protected static long DoIndexOf(T item, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + Span memoryBlocksSpan = new(memoryBlocks, 0, lastBlockIndex + 1); + int itemIndex, blockIndex; + for (blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))); + } + } + + if (blockIndex == lastBlockIndex) + { + itemIndex = nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, blockSize); + } + else + { + return ItemNotFoundIndexLong; + } + + return itemIndex >= 0 ? itemIndex + ((long)lastBlockIndex << (31 - BitOperations.LeadingZeroCount((uint)blockSize))) : ItemNotFoundIndexLong; + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongListIndexOfV4 list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableLongListIndexOfV4 list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableLongListIndexOfV4 list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV4(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongListIndexOfV4(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + + _longCount++; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + } + + public void AddRange(RecyclableLongListIndexOfV4 items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _longCountIndexOfStep = Math.Max((long)(targetCapacity * OptimalParallelSearchStep), 1L); + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _longCount = 0; + _longCountIndexOfStep = 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _nextItemBlockIndex; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndex + : checked((int)DoIndexOfParallel(item)); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) => _longCount == 0 + ? ItemNotFoundIndexLong + : _longCount <= _blockSize + ? Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) + : DoIndexOfParallel(item); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + _longCountIndexOfStep = Math.Max((long)Math.Floor(_longCount * OptimalParallelSearchStep), 1L); + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongListIndexOfV4() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } + + #endregion + + public partial class RecyclableLongListPocBenchmarks + { + public void RecyclableLongList_IndexOfV1() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongListIndexOfV1; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void PooledList_IndexOf() + { + var data = TestObjects; + var list = TestPooledList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableLongList_IndexOfV2() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongListIndexOfV2; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableLongList_IndexOfV3() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongListIndexOfV3; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableLongList_IndexOfV4() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongListIndexOfV4; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + + public void RecyclableLongList_IndexOf() + { + var data = TestObjects; + var list = TestObjectsAsRecyclableLongList; + var dataCount = TestObjectCount; + for (var i = 0; i < dataCount; i++) + { + DoNothing(list.IndexOf(data[i])); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs new file mode 100644 index 0000000..0a067a8 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.cs @@ -0,0 +1,157 @@ +using System.Reflection; +using BenchmarkDotNet.Attributes; +using Collections.Pooled; +using Recyclable.Collections.Benchmarks.Core; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum RecyclableLongListPocBenchmarkType + { + IndexOfV1, + IndexOfV2, + IndexOfV3, + IndexOfV4, + IndexOf_PooledList, + IndexOf, + EnsureCapacity_ByPowOf2, + EnsureCapacityV1_ByPowOf2, + EnsureCapacityV2_ByPowOf2, + EnsureCapacityV3_ByPowOf2, + } + + [MemoryDiagnoser] + public partial class RecyclableLongListPocBenchmarks : PocBenchmarkBase + { + [Params(RecyclableLongListPocBenchmarkType.IndexOf)] + public override RecyclableLongListPocBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + // [Params(BenchmarkType.Array, BenchmarkType.List, BenchmarkType.RecyclableList, BenchmarkType.RecyclableLongList)] + [Params(RecyclableLongListPocBenchmarkType.IndexOf_PooledList)] + public override RecyclableLongListPocBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + private long[]? _testArray; + public long[] TestObjects => _testArray ?? throw new NullReferenceException("Something is wrong and test objects are null"); + + public RecyclableLongList TestObjectsAsRecyclableLongList => _testRecyclableLongList ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongList)} is null"); + private RecyclableLongListIndexOfV1? _testRecyclableLongListIndexOfV1; + internal RecyclableLongListIndexOfV1 TestObjectsAsRecyclableLongListIndexOfV1 => _testRecyclableLongListIndexOfV1 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV1)} is null"); + private RecyclableLongListIndexOfV2? _testRecyclableLongListIndexOfV2; + private RecyclableLongList? _testRecyclableLongList; + internal RecyclableLongListIndexOfV2 TestObjectsAsRecyclableLongListIndexOfV2 => _testRecyclableLongListIndexOfV2 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV2)} is null"); + private RecyclableLongListIndexOfV3? _testRecyclableLongListIndexOfV3; + internal RecyclableLongListIndexOfV3 TestObjectsAsRecyclableLongListIndexOfV3 => _testRecyclableLongListIndexOfV3 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV3)} is null"); + private RecyclableLongListIndexOfV4? _testRecyclableLongListIndexOfV4; + internal RecyclableLongListIndexOfV4 TestObjectsAsRecyclableLongListIndexOfV4 => _testRecyclableLongListIndexOfV4 ?? throw new NullReferenceException($"Something is wrong and {nameof(RecyclableLongListIndexOfV4)} is null"); + private PooledList? _testPooledList; + internal PooledList TestPooledList => _testPooledList ?? throw new NullReferenceException($"Something is wrong and {nameof(PooledList)} is null"); + + private static readonly MethodInfo _ensureCapacityNewFunc; + private static readonly MethodInfo _ensureCapacityV1Func; + private static readonly MethodInfo _ensureCapacityV2Func; + private static readonly MethodInfo _ensureCapacityV3Func; + + static RecyclableLongListPocBenchmarks() + { + _ensureCapacityNewFunc = typeof(RecyclableLongList).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongList)}"); + + _ensureCapacityV1Func = typeof(RecyclableLongListV1).GetMethod("EnsureCapacity", BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongListV1)}"); + + _ensureCapacityV2Func = typeof(RecyclableLongListV2).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.Public) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongListV2)}"); + + _ensureCapacityV3Func = typeof(RecyclableLongListV3).GetMethod("EnsureCapacity", BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new NullReferenceException($"Method EnsureCapacity not found in class {nameof(RecyclableLongListV3)}"); + } + + public static void Run() + { + var benchmark = new RecyclableLongListPocBenchmarks(); + benchmark.Setup(); + // benchmark.RecyclableLongList_EnsureCapacity_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacityV1_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacityV2_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacityV3_ByPowOf2(); + // benchmark.RecyclableLongList_EnsureCapacity_ByBlockSize(); + // benchmark.RecyclableLongList_EnsureCapacityV1_ByBlockSize(); + // benchmark.RecyclableLongList_EnsureCapacityV2_ByBlockSize(); + // benchmark.RecyclableLongList_EnsureCapacityV3_ByBlockSize(); + benchmark.RecyclableLongList_IndexOf(); + benchmark.RecyclableLongList_IndexOfV1(); + benchmark.RecyclableLongList_IndexOfV2(); + benchmark.RecyclableLongList_IndexOfV3(); + benchmark.RecyclableLongList_IndexOfV4(); + benchmark.Cleanup(); + } + + public override void Setup() + { + Console.WriteLine("******* SETTING UP EXPECTED RESULTS DATA *******"); + _testArray = DataGenerator.EnumerateTestObjects(TestObjectCount); + + base.Setup(); + } + + public override void Cleanup() + { + _testArray = null; + _testRecyclableLongList?.Dispose(); + _testRecyclableLongList = null; + _testRecyclableLongListIndexOfV1?.Dispose(); + _testRecyclableLongListIndexOfV1 = null; + _testRecyclableLongListIndexOfV2?.Dispose(); + _testRecyclableLongListIndexOfV2 = null; + _testRecyclableLongListIndexOfV3?.Dispose(); + _testRecyclableLongListIndexOfV3 = null; + _testRecyclableLongListIndexOfV4?.Dispose(); + _testRecyclableLongListIndexOfV4 = null; + _testPooledList?.Dispose(); + _testPooledList = null; + + base.Cleanup(); + } + + protected override Action? GetTestMethod(RecyclableLongListPocBenchmarkType benchmarkType) => benchmarkType switch + { + RecyclableLongListPocBenchmarkType.IndexOfV1 => () => RecyclableLongList_IndexOfV1(), + RecyclableLongListPocBenchmarkType.IndexOfV2 => () => RecyclableLongList_IndexOfV2(), + RecyclableLongListPocBenchmarkType.IndexOfV3 => () => RecyclableLongList_IndexOfV3(), + RecyclableLongListPocBenchmarkType.IndexOfV4 => () => RecyclableLongList_IndexOfV4(), + RecyclableLongListPocBenchmarkType.IndexOf_PooledList => () => PooledList_IndexOf(), + RecyclableLongListPocBenchmarkType.IndexOf => () => RecyclableLongList_IndexOf(), + RecyclableLongListPocBenchmarkType.EnsureCapacity_ByPowOf2 => () => RecyclableLongList_EnsureCapacity_ByPowOf2(), + RecyclableLongListPocBenchmarkType.EnsureCapacityV1_ByPowOf2 => () => RecyclableLongList_EnsureCapacityV1_ByPowOf2(), + RecyclableLongListPocBenchmarkType.EnsureCapacityV2_ByPowOf2 => () => RecyclableLongList_EnsureCapacityV2_ByPowOf2(), + RecyclableLongListPocBenchmarkType.EnsureCapacityV3_ByPowOf2 => () => RecyclableLongList_EnsureCapacityV3_ByPowOf2(), + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + protected override void PrepareData(T benchmarkType) + { + base.PrepareData(benchmarkType); + + switch (BenchmarkType) + { + case RecyclableLongListPocBenchmarkType.IndexOfV1: + _testRecyclableLongListIndexOfV1 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOfV2: + _testRecyclableLongListIndexOfV2 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOfV3: + _testRecyclableLongListIndexOfV3 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOfV4: + _testRecyclableLongListIndexOfV4 = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + case RecyclableLongListPocBenchmarkType.IndexOf_PooledList: + _testPooledList = new PooledList(TestObjects, ClearMode.Auto); + break; + case RecyclableLongListPocBenchmarkType.IndexOf: + _testRecyclableLongList = new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt new file mode 100644 index 0000000..d4413b6 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPos.Benchmarks.IndexOf.txt @@ -0,0 +1,26 @@ +BenchmarkDotNet=v0.13.3, OS=ubuntu 20.04 +Intel Xeon Platinum 8272CL CPU 2.60GHz, 1 CPU, 4 logical and 2 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | Mean | Error | StdDev | Ratio | RatioSD | +|------------------------- |--------:|---------:|---------:|------:|--------:| +| RecyclableLongList_IndexOfV1 | 2.274 s | 0.0447 s | 0.0894 s | 1.00 | 0.04 | +| RecyclableLongList_IndexOfV2 | 2.302 s | 0.0456 s | 0.0750 s | 1.00 | 0.00 | +| RecyclableLongList_IndexOf | 2.234 s | 0.0446 s | 0.0681 s | 0.97 | 0.05 | + +// * Hints * +Outliers + RecyclableLongListPocBenchmarks.RecyclableLongList_IndexOfV2: Default -> 2 outliers were removed (2.54 s, 2.56 s) + RecyclableLongListPocBenchmarks.RecyclableLongList_IndexOf: Default -> 1 outlier was detected (2.06 s) + +// * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 s : 1 Second (1 sec) + diff --git a/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs new file mode 100644 index 0000000..bad353e --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RefVsInstanceMemberPoc.Benchmarks.cs @@ -0,0 +1,362 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public class RefVsInstanceMemberPocBenchmarks + { + public static void Run() + { + var benchmark = new RefVsInstanceMemberPocBenchmarks(); + benchmark.ArrayInstanceMember2(); + benchmark.ArrayInstanceMember3(); + benchmark.ArrayInstanceMember4(); + benchmark.ArrayInstanceMember5(); + benchmark.ArrayInstanceMember6(); + benchmark.ArrayInstanceMemberLocalVar5(); + benchmark.ArrayInstanceMemberLocalVar6(); + benchmark.IntInstanceMember2(); + benchmark.IntInstanceMember3(); + benchmark.IntInstanceMember4(); + benchmark.IntInstanceMember5(); + benchmark.RefToArray2(); + benchmark.RefToArray3(); + benchmark.RefToArray4(); + benchmark.RefToArray5(); + benchmark.RefToArray6(); + benchmark.RefToInt2(); + benchmark.RefToInt3(); + } + + private readonly TestCase _testCase = new(); + private static void DoNothing(int count) + { + _ = count; + } + + private class TestCase + { + private int[] _items = new int[123]; + private int _count = 123; + private static int Add(int count) + { + return count; + } + + public int Count + { + get => _count; + set => _count = value; + } + + public ref int GetCountAsRefCount2() + { + ref int count = ref _count; + count = 234; + return ref count; + } + + public ref int GetCountAsRefCount3() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + return ref count; + } + + public ref int GetCountAsRefCount4() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + _ = TestCase.Add(count); + return ref count; + } + + public ref int GetCountAsRefCount5() + { + ref int count = ref _count; + count = 234; + _ = TestCase.Add(count); + _ = TestCase.Add(count); + _ = TestCase.Add(count); + return ref count; + } + + public int GetCountAsInstanceMember2() + { + _count = 234; + return _count; + } + + public int GetCountAsInstanceMember3() + { + _count = 234; + _ = TestCase.Add(_count); + return _count; + } + + public int GetCountAsInstanceMember4() + { + _count = 234; + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + return _count; + } + + public int GetCountAsInstanceMember5() + { + _count = 234; + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + _ = TestCase.Add(_count); + return _count; + } + + public int GetLengthAsArray2() + { + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray3() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray4() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray5() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArray6() + { + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + _ = TestCase.Add(_items.Length); + return _items.Length; + } + + public int GetLengthAsArrayLocalVar5() + { + var items = _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsArrayLocalVar6() + { + var items = _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray2() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray3() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray4() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray5() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + + public int GetLengthAsRefArray6() + { + ref int[] items = ref _items; + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + _ = TestCase.Add(items.Length); + return items.Length; + } + } + + [Benchmark] + public void RefToInt2() + { + var count = _testCase.GetCountAsRefCount2(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt3() + { + var count = _testCase.GetCountAsRefCount3(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt4() + { + var count = _testCase.GetCountAsRefCount4(); + DoNothing(count); + } + + [Benchmark] + public void RefToInt5() + { + var count = _testCase.GetCountAsRefCount5(); + DoNothing(count); + } + + [Benchmark(Baseline = true)] + public void IntInstanceMember2() + { + var count = _testCase.GetCountAsInstanceMember2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember3() + { + var count = _testCase.GetCountAsInstanceMember3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember4() + { + var count = _testCase.GetCountAsInstanceMember4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void IntInstanceMember5() + { + var count = _testCase.GetCountAsInstanceMember5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray2() + { + var count = _testCase.GetLengthAsRefArray2(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray3() + { + var count = _testCase.GetLengthAsRefArray3(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray4() + { + var count = _testCase.GetLengthAsRefArray4(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray5() + { + var count = _testCase.GetLengthAsRefArray5(); + DoNothing(count); + } + + [Benchmark] + public void RefToArray6() + { + var count = _testCase.GetLengthAsRefArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember2() + { + var count = _testCase.GetLengthAsArray2(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember3() + { + var count = _testCase.GetLengthAsArray3(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember4() + { + var count = _testCase.GetLengthAsArray4(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember5() + { + var count = _testCase.GetLengthAsArray5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMember6() + { + var count = _testCase.GetLengthAsArray6(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar5() + { + var count = _testCase.GetLengthAsArrayLocalVar5(); + DoNothing(count); + } + + [Benchmark(Baseline = false)] + public void ArrayInstanceMemberLocalVar6() + { + var count = _testCase.GetLengthAsArrayLocalVar6(); + DoNothing(count); + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs new file mode 100644 index 0000000..d3b8223 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/RoundBenchmarks.cs @@ -0,0 +1,45 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum RoundBenchmarkType + { + MathFloor, + MathRound, + WithTypecasting, + } + + public class RoundBenchmarks : PocBenchmarkBase + { + [Params(RoundBenchmarkType.MathFloor)] + public override RoundBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + [Params(RoundBenchmarkType.MathRound, RoundBenchmarkType.WithTypecasting)] + public override RoundBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + private const double OptimalParallelSearchStep = 0.329; + + public void RoundWithMathFloor() + { + DoNothing((long)Math.Floor(TestObjectCount * OptimalParallelSearchStep)); + } + + public void RoundWithMathRound() + { + DoNothing((long)Math.Round(TestObjectCount * OptimalParallelSearchStep, 0)); + } + + public void RoundWithTypeCasting() + { + DoNothing((long)(TestObjectCount * OptimalParallelSearchStep)); + } + + protected override Action GetTestMethod(RoundBenchmarkType benchmarkType) => benchmarkType switch + { + RoundBenchmarkType.MathFloor => RoundWithMathFloor, + RoundBenchmarkType.MathRound => RoundWithMathRound, + RoundBenchmarkType.WithTypecasting => RoundWithTypeCasting, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs new file mode 100644 index 0000000..b270c14 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.cs @@ -0,0 +1,99 @@ +using BenchmarkDotNet.Attributes; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum SpanVsArrayBenchmarkType + { + ArrayGetWithVar, + ArrayGetWithField, + SpanGet, + ArraySetWithVar, + ArraySetWithField, + SpanSet + } + + public class SpanVsArrayBenchmarks : RecyclableBenchmarkBase + { + // [Params(SpanVsArrayBenchmarkType.ArrayGetWithVar, SpanVsArrayBenchmarkType.SpanGet)] + // [Params(SpanVsArrayBenchmarkType.ArraySetWithVar, SpanVsArrayBenchmarkType.SpanSet)] + [Params + ( + SpanVsArrayBenchmarkType.ArrayGetWithVar, SpanVsArrayBenchmarkType.SpanGet, + SpanVsArrayBenchmarkType.ArraySetWithField, SpanVsArrayBenchmarkType.ArraySetWithVar, SpanVsArrayBenchmarkType.SpanSet + )] + public override SpanVsArrayBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(SpanVsArrayBenchmarkType.ArrayGetWithField)] + // [Params(SpanVsArrayBenchmarkType.ArraySetWithField)] + public override SpanVsArrayBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + protected override Action GetTestMethod(SpanVsArrayBenchmarkType benchmarkType) => benchmarkType switch + { + SpanVsArrayBenchmarkType.ArrayGetWithVar => ArrayGetWithVar, + SpanVsArrayBenchmarkType.ArrayGetWithField => ArrayGetWithField, + SpanVsArrayBenchmarkType.SpanGet => SpanGet, + SpanVsArrayBenchmarkType.ArraySetWithVar => ArraySetWithVar, + SpanVsArrayBenchmarkType.ArraySetWithField => ArraySetWithField, + SpanVsArrayBenchmarkType.SpanSet => SpanSet, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + public void ArrayGetWithVar() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void ArrayGetWithField() + { + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + DoNothing(_testObjects![i]); + } + } + + public void SpanGet() + { + var dataCount = TestObjectCount; + Span data = new(_testObjects, 0, dataCount); + for (int i = 0; i < dataCount; i++) + { + DoNothing(data[i]); + } + } + + public void ArraySetWithVar() + { + var data = TestObjects; + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + data[i] = i; + } + } + + public void ArraySetWithField() + { + var dataCount = TestObjectCount; + for (long i = 0; i < dataCount; i++) + { + _testObjects![i] = i; + } + } + + public void SpanSet() + { + var dataCount = TestObjectCount; + Span data = new(_testObjects, 0, dataCount); + for (int i = 0; i < dataCount; i++) + { + data[i] = i; + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt new file mode 100644 index 0000000..09b9d3d --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/SpanVsArrayBenchmarks.txt @@ -0,0 +1,1224 @@ +Docker Desktop - Windows Host + +| Method | TestObjectCount | Mean | Error | StdDev | Ratio | RatioSD | +|------------------ |---------------- |--------------------:|------------------:|------------------:|------:|--------:| +| ArrayGetWithVar | 1 | 1.5515 ns | 0.0611 ns | 0.0542 ns | 5.78 | 0.63 | +| ArrayGetWithField | 1 | 0.2697 ns | 0.0287 ns | 0.0268 ns | 1.00 | 0.00 | +| SpanGet | 1 | 0.5019 ns | 0.0117 ns | 0.0098 ns | 1.88 | 0.20 | +| | | | | | | | +| ArrayGetWithVar | 2 | 1.7411 ns | 0.0135 ns | 0.0113 ns | 2.78 | 0.09 | +| ArrayGetWithField | 2 | 0.6258 ns | 0.0215 ns | 0.0191 ns | 1.00 | 0.00 | +| SpanGet | 2 | 0.7584 ns | 0.0172 ns | 0.0161 ns | 1.21 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 3 | 2.0160 ns | 0.0264 ns | 0.0247 ns | 2.19 | 0.07 | +| ArrayGetWithField | 3 | 0.9226 ns | 0.0309 ns | 0.0258 ns | 1.00 | 0.00 | +| SpanGet | 3 | 1.0568 ns | 0.0240 ns | 0.0213 ns | 1.15 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 4 | 2.2360 ns | 0.0200 ns | 0.0167 ns | 1.89 | 0.07 | +| ArrayGetWithField | 4 | 1.1815 ns | 0.0457 ns | 0.0427 ns | 1.00 | 0.00 | +| SpanGet | 4 | 1.2586 ns | 0.0343 ns | 0.0321 ns | 1.07 | 0.05 | +| | | | | | | | +| ArrayGetWithVar | 5 | 2.5662 ns | 0.0591 ns | 0.0523 ns | 1.45 | 0.06 | +| ArrayGetWithField | 5 | 1.7752 ns | 0.0663 ns | 0.0554 ns | 1.00 | 0.00 | +| SpanGet | 5 | 2.3339 ns | 0.0548 ns | 0.0512 ns | 1.31 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 6 | 2.9409 ns | 0.0605 ns | 0.0566 ns | 1.46 | 0.03 | +| ArrayGetWithField | 6 | 2.0072 ns | 0.0225 ns | 0.0188 ns | 1.00 | 0.00 | +| SpanGet | 6 | 2.5796 ns | 0.0595 ns | 0.0557 ns | 1.28 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 7 | 3.2910 ns | 0.0638 ns | 0.0596 ns | 1.27 | 0.05 | +| ArrayGetWithField | 7 | 2.6211 ns | 0.0830 ns | 0.0923 ns | 1.00 | 0.00 | +| SpanGet | 7 | 3.0480 ns | 0.0499 ns | 0.0416 ns | 1.18 | 0.05 | +| | | | | | | | +| ArrayGetWithVar | 8 | 3.6615 ns | 0.1036 ns | 0.1348 ns | 1.34 | 0.05 | +| ArrayGetWithField | 8 | 2.7175 ns | 0.0753 ns | 0.0668 ns | 1.00 | 0.00 | +| SpanGet | 8 | 3.2317 ns | 0.0873 ns | 0.0681 ns | 1.19 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 9 | 4.1159 ns | 0.1028 ns | 0.0912 ns | 1.39 | 0.06 | +| ArrayGetWithField | 9 | 2.9648 ns | 0.0797 ns | 0.0707 ns | 1.00 | 0.00 | +| SpanGet | 9 | 3.4643 ns | 0.0946 ns | 0.0838 ns | 1.17 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 10 | 4.5874 ns | 0.1178 ns | 0.1210 ns | 1.38 | 0.06 | +| ArrayGetWithField | 10 | 3.3250 ns | 0.0664 ns | 0.0588 ns | 1.00 | 0.00 | +| SpanGet | 10 | 3.6339 ns | 0.0678 ns | 0.0601 ns | 1.09 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 11 | 5.0294 ns | 0.0905 ns | 0.0803 ns | 1.41 | 0.04 | +| ArrayGetWithField | 11 | 3.5648 ns | 0.0933 ns | 0.0872 ns | 1.00 | 0.00 | +| SpanGet | 11 | 4.0259 ns | 0.1108 ns | 0.1319 ns | 1.13 | 0.05 | +| | | | | | | | +| ArrayGetWithVar | 12 | 5.2701 ns | 0.1157 ns | 0.1025 ns | 1.42 | 0.04 | +| ArrayGetWithField | 12 | 3.7002 ns | 0.0957 ns | 0.0848 ns | 1.00 | 0.00 | +| SpanGet | 12 | 4.3132 ns | 0.1102 ns | 0.0977 ns | 1.17 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 13 | 5.7393 ns | 0.1438 ns | 0.1345 ns | 1.41 | 0.03 | +| ArrayGetWithField | 13 | 4.0569 ns | 0.1034 ns | 0.0863 ns | 1.00 | 0.00 | +| SpanGet | 13 | 4.4506 ns | 0.0812 ns | 0.0720 ns | 1.10 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 14 | 6.0965 ns | 0.1023 ns | 0.0854 ns | 1.40 | 0.03 | +| ArrayGetWithField | 14 | 4.3728 ns | 0.0803 ns | 0.0751 ns | 1.00 | 0.00 | +| SpanGet | 14 | 4.6293 ns | 0.1178 ns | 0.2124 ns | 1.09 | 0.06 | +| | | | | | | | +| ArrayGetWithVar | 15 | 7.0026 ns | 0.1261 ns | 0.1053 ns | 1.42 | 0.02 | +| ArrayGetWithField | 15 | 4.9188 ns | 0.0506 ns | 0.0422 ns | 1.00 | 0.00 | +| SpanGet | 15 | 5.1823 ns | 0.1318 ns | 0.1233 ns | 1.05 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 16 | 8.3418 ns | 0.1920 ns | 0.1702 ns | 1.51 | 0.04 | +| ArrayGetWithField | 16 | 5.5086 ns | 0.1068 ns | 0.0947 ns | 1.00 | 0.00 | +| SpanGet | 16 | 5.4778 ns | 0.1416 ns | 0.2246 ns | 1.02 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 17 | 8.1520 ns | 0.0699 ns | 0.0620 ns | 1.69 | 0.01 | +| ArrayGetWithField | 17 | 4.8123 ns | 0.0442 ns | 0.0369 ns | 1.00 | 0.00 | +| SpanGet | 17 | 5.5824 ns | 0.0741 ns | 0.0693 ns | 1.16 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 18 | 7.4813 ns | 0.0414 ns | 0.0367 ns | 1.42 | 0.01 | +| ArrayGetWithField | 18 | 5.2566 ns | 0.0312 ns | 0.0260 ns | 1.00 | 0.00 | +| SpanGet | 18 | 5.9601 ns | 0.0816 ns | 0.0763 ns | 1.13 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 19 | 7.7273 ns | 0.0835 ns | 0.0781 ns | 1.49 | 0.02 | +| ArrayGetWithField | 19 | 5.2033 ns | 0.0313 ns | 0.0261 ns | 1.00 | 0.00 | +| SpanGet | 19 | 6.3312 ns | 0.1551 ns | 0.1593 ns | 1.22 | 0.03 | +| | | | | | | | +| ArrayGetWithVar | 20 | 7.9391 ns | 0.0515 ns | 0.0457 ns | 1.37 | 0.03 | +| ArrayGetWithField | 20 | 5.8071 ns | 0.1415 ns | 0.1390 ns | 1.00 | 0.00 | +| SpanGet | 20 | 5.9330 ns | 0.0500 ns | 0.0443 ns | 1.02 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 30 | 10.6019 ns | 0.1303 ns | 0.1219 ns | 1.29 | 0.02 | +| ArrayGetWithField | 30 | 8.2413 ns | 0.1019 ns | 0.0851 ns | 1.00 | 0.00 | +| SpanGet | 30 | 8.4137 ns | 0.1042 ns | 0.0924 ns | 1.02 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 32 | 11.0617 ns | 0.1536 ns | 0.1362 ns | 1.27 | 0.02 | +| ArrayGetWithField | 32 | 8.6916 ns | 0.1022 ns | 0.0906 ns | 1.00 | 0.00 | +| SpanGet | 32 | 8.8803 ns | 0.0642 ns | 0.0536 ns | 1.02 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 40 | 12.8859 ns | 0.0889 ns | 0.0742 ns | 1.21 | 0.01 | +| ArrayGetWithField | 40 | 10.6318 ns | 0.0740 ns | 0.0618 ns | 1.00 | 0.00 | +| SpanGet | 40 | 10.8128 ns | 0.0622 ns | 0.0519 ns | 1.02 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 50 | 15.2773 ns | 0.1358 ns | 0.1204 ns | 1.17 | 0.01 | +| ArrayGetWithField | 50 | 13.0385 ns | 0.0772 ns | 0.0603 ns | 1.00 | 0.00 | +| SpanGet | 50 | 13.2485 ns | 0.0861 ns | 0.0719 ns | 1.02 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 60 | 19.4536 ns | 0.3026 ns | 0.2682 ns | 1.25 | 0.02 | +| ArrayGetWithField | 60 | 15.5407 ns | 0.1195 ns | 0.0998 ns | 1.00 | 0.00 | +| SpanGet | 60 | 15.7285 ns | 0.0886 ns | 0.0740 ns | 1.01 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 80 | 23.6613 ns | 0.3427 ns | 0.3206 ns | 0.95 | 0.01 | +| ArrayGetWithField | 80 | 24.9804 ns | 0.1885 ns | 0.1671 ns | 1.00 | 0.00 | +| SpanGet | 80 | 20.7933 ns | 0.2044 ns | 0.1912 ns | 0.83 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 90 | 25.2124 ns | 0.2229 ns | 0.1976 ns | 1.09 | 0.01 | +| ArrayGetWithField | 90 | 23.0452 ns | 0.1873 ns | 0.1660 ns | 1.00 | 0.00 | +| SpanGet | 90 | 23.1852 ns | 0.1221 ns | 0.1142 ns | 1.01 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 100 | 38.4832 ns | 0.3491 ns | 0.3094 ns | 1.23 | 0.02 | +| ArrayGetWithField | 100 | 31.3061 ns | 0.2504 ns | 0.2220 ns | 1.00 | 0.00 | +| SpanGet | 100 | 31.1156 ns | 0.3401 ns | 0.3015 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 128 | 45.1141 ns | 0.2042 ns | 0.1594 ns | 1.18 | 0.01 | +| ArrayGetWithField | 128 | 38.3883 ns | 0.3717 ns | 0.3477 ns | 1.00 | 0.00 | +| SpanGet | 128 | 37.9068 ns | 0.2695 ns | 0.2250 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 256 | 77.4174 ns | 0.6796 ns | 0.6024 ns | 1.12 | 0.01 | +| ArrayGetWithField | 256 | 68.8870 ns | 0.4120 ns | 0.3441 ns | 1.00 | 0.00 | +| SpanGet | 256 | 68.4291 ns | 0.4472 ns | 0.4183 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 512 | 139.0822 ns | 1.1336 ns | 1.0049 ns | 1.06 | 0.01 | +| ArrayGetWithField | 512 | 131.7129 ns | 0.8504 ns | 0.7101 ns | 1.00 | 0.00 | +| SpanGet | 512 | 131.8641 ns | 0.9284 ns | 0.8684 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 1024 | 266.5707 ns | 4.5696 ns | 5.4397 ns | 1.04 | 0.02 | +| ArrayGetWithField | 1024 | 257.5734 ns | 1.2839 ns | 1.1382 ns | 1.00 | 0.00 | +| SpanGet | 1024 | 256.7122 ns | 1.0711 ns | 0.9495 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 2048 | 511.6425 ns | 1.5722 ns | 1.3129 ns | 1.00 | 0.01 | +| ArrayGetWithField | 2048 | 509.8994 ns | 2.5392 ns | 2.2509 ns | 1.00 | 0.00 | +| SpanGet | 2048 | 507.5335 ns | 4.0426 ns | 3.3758 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 4096 | 1,019.1013 ns | 10.1205 ns | 9.4668 ns | 1.00 | 0.01 | +| ArrayGetWithField | 4096 | 1,018.5594 ns | 4.6897 ns | 4.1573 ns | 1.00 | 0.00 | +| SpanGet | 4096 | 1,012.1960 ns | 7.4658 ns | 6.6183 ns | 0.99 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 8192 | 2,017.1872 ns | 14.3314 ns | 12.7044 ns | 1.00 | 0.01 | +| ArrayGetWithField | 8192 | 2,023.1594 ns | 12.9330 ns | 11.4648 ns | 1.00 | 0.00 | +| SpanGet | 8192 | 2,014.0275 ns | 14.3402 ns | 12.7123 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 1000000 | 245,965.8295 ns | 1,733.9504 ns | 1,447.9271 ns | 1.00 | 0.01 | +| ArrayGetWithField | 1000000 | 245,337.9044 ns | 1,055.8869 ns | 881.7134 ns | 1.00 | 0.00 | +| SpanGet | 1000000 | 245,687.0731 ns | 1,561.6594 ns | 1,219.2409 ns | 1.00 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 2147483591 | 525,684,023.0769 ns | 3,207,250.2244 ns | 2,678,199.1945 ns | 0.99 | 0.01 | +| ArrayGetWithField | 2147483591 | 529,688,963.4615 ns | 3,381,258.2043 ns | 2,823,503.7386 ns | 1.00 | 0.00 | +| SpanGet | 2147483591 | 529,698,631.2857 ns | 4,050,107.8977 ns | 3,590,315.1583 ns | 1.00 | 0.01 | + +// * Warnings * +MultimodalDistribution + SpanVsArrayBenchmarks.SpanGet: Default -> It seems that the distribution is bimodal (mValue = 3.54) + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (3.23 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was detected (1.78 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (2.12 ns, 2.14 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (3.33 ns, 3.35 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.26 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (2.59 ns, 2.62 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 2 outliers were detected (2.57 ns, 2.65 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (3.86 ns, 3.91 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (4.31 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (3.58 ns..4.00 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (3.71 ns, 3.73 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (4.94 ns, 4.98 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (5.00 ns, 5.79 ns, 5.85 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.62 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (5.22 ns..5.28 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (6.25 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.91 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (5.41 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (6.96 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (5.20 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (5.55 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (7.00 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.16 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (7.55 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (5.25 ns, 5.66 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.38 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 4 outliers were removed (6.16 ns..6.78 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.37 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (8.13 ns, 8.29 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 6 outliers were removed (7.09 ns..7.33 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (8.48 ns, 9.28 ns, 9.33 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.81 ns, 6.84 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (10.70 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (7.53 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (8.13 ns..8.25 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (10.23 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.56 ns, 6.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.33 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.89 ns, 7.03 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (6.89 ns, 7.00 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.66 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (8.03 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (10.35 ns, 10.41 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (10.22 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (12.95 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (10.60 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (10.67 ns, 10.75 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (14.69 ns, 14.85 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (12.44 ns, 12.66 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (12.61 ns, 12.77 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (17.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (14.97 ns..14.98 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (15.18 ns, 15.61 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (22.40 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (17.54 ns, 18.13 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (17.74 ns, 17.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (26.15 ns, 27.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (27.26 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (25.32 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (41.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (33.96 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (33.76 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (47.36 ns..47.54 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (40.60 ns, 40.85 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (81.64 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (72.86 ns, 73.52 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (139.97 ns, 145.75 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (137.07 ns, 138.83 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (286.19 ns, 314.20 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (267.48 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (265.60 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (522.93 ns, 540.60 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (508.42 ns, 525.02 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (525.76 ns, 527.95 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 3 outliers were detected (1.01 us, 1.01 us, 1.07 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (1.07 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (2.08 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.10 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (2.12 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (256.02 us, 256.31 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (250.53 us, 250.70 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (252.66 us..282.22 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 4 outliers were detected (519.00 ms, 522.14 ms, 531.86 ms, 536.31 ms) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (544.93 ms, 551.49 ms) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (557.67 ms) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + +BenchmarkDotNet=v0.13.3, OS=Windows 11 (10.0.22621.1555) +AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +|------------------ |---------------- |--------------------:|----------------:|----------------:|--------------------:|------:|--------:| +| ArrayGetWithVar | 1 | 1.4115 ns | 0.0127 ns | 0.0119 ns | 1.4133 ns | 6.74 | 0.18 | +| ArrayGetWithField | 1 | 0.2095 ns | 0.0051 ns | 0.0045 ns | 0.2107 ns | 1.00 | 0.00 | +| SpanGet | 1 | 0.2636 ns | 0.0024 ns | 0.0021 ns | 0.2632 ns | 1.26 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 2 | 1.7398 ns | 0.0122 ns | 0.0114 ns | 1.7406 ns | 2.84 | 0.14 | +| ArrayGetWithField | 2 | 0.6150 ns | 0.0407 ns | 0.0318 ns | 0.6095 ns | 1.00 | 0.00 | +| SpanGet | 2 | 0.4950 ns | 0.0079 ns | 0.0066 ns | 0.4981 ns | 0.81 | 0.05 | +| | | | | | | | | +| ArrayGetWithVar | 3 | 2.2776 ns | 0.0778 ns | 0.2294 ns | 2.4503 ns | 2.21 | 0.25 | +| ArrayGetWithField | 3 | 1.0145 ns | 0.0439 ns | 0.0410 ns | 1.0062 ns | 1.00 | 0.00 | +| SpanGet | 3 | 0.7529 ns | 0.0250 ns | 0.0234 ns | 0.7476 ns | 0.74 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 4 | 2.2875 ns | 0.0630 ns | 0.0559 ns | 2.2669 ns | 2.27 | 0.06 | +| ArrayGetWithField | 4 | 1.0066 ns | 0.0164 ns | 0.0153 ns | 1.0050 ns | 1.00 | 0.00 | +| SpanGet | 4 | 0.9895 ns | 0.0123 ns | 0.0115 ns | 0.9922 ns | 0.98 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 5 | 2.6949 ns | 0.0633 ns | 0.0592 ns | 2.6701 ns | 1.42 | 0.16 | +| ArrayGetWithField | 5 | 1.9059 ns | 0.0682 ns | 0.1868 ns | 1.8684 ns | 1.00 | 0.00 | +| SpanGet | 5 | 1.5092 ns | 0.0392 ns | 0.0348 ns | 1.5102 ns | 0.79 | 0.09 | +| | | | | | | | | +| ArrayGetWithVar | 6 | 3.1465 ns | 0.0931 ns | 0.1143 ns | 3.0808 ns | 1.69 | 0.06 | +| ArrayGetWithField | 6 | 1.8594 ns | 0.0124 ns | 0.0116 ns | 1.8588 ns | 1.00 | 0.00 | +| SpanGet | 6 | 2.0726 ns | 0.0716 ns | 0.0879 ns | 2.0578 ns | 1.11 | 0.05 | +| | | | | | | | | +| ArrayGetWithVar | 7 | 3.2474 ns | 0.0525 ns | 0.0410 ns | 3.2404 ns | 1.51 | 0.02 | +| ArrayGetWithField | 7 | 2.1444 ns | 0.0223 ns | 0.0198 ns | 2.1434 ns | 1.00 | 0.00 | +| SpanGet | 7 | 2.3791 ns | 0.0572 ns | 0.0535 ns | 2.3924 ns | 1.11 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 8 | 3.6571 ns | 0.1017 ns | 0.2055 ns | 3.5399 ns | 1.51 | 0.07 | +| ArrayGetWithField | 8 | 2.3724 ns | 0.0196 ns | 0.0174 ns | 2.3738 ns | 1.00 | 0.00 | +| SpanGet | 8 | 2.6088 ns | 0.0644 ns | 0.0602 ns | 2.6322 ns | 1.10 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 9 | 3.8172 ns | 0.0061 ns | 0.0057 ns | 3.8155 ns | 1.46 | 0.02 | +| ArrayGetWithField | 9 | 2.6231 ns | 0.0313 ns | 0.0293 ns | 2.6143 ns | 1.00 | 0.00 | +| SpanGet | 9 | 2.9815 ns | 0.0446 ns | 0.0372 ns | 2.9849 ns | 1.14 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 10 | 4.3250 ns | 0.1161 ns | 0.2237 ns | 4.1797 ns | 1.36 | 0.07 | +| ArrayGetWithField | 10 | 3.1594 ns | 0.0278 ns | 0.0246 ns | 3.1674 ns | 1.00 | 0.00 | +| SpanGet | 10 | 2.9200 ns | 0.0834 ns | 0.0780 ns | 2.9040 ns | 0.92 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 11 | 4.8931 ns | 0.1262 ns | 0.2308 ns | 4.9847 ns | 1.44 | 0.09 | +| ArrayGetWithField | 11 | 3.3435 ns | 0.0487 ns | 0.0432 ns | 3.3299 ns | 1.00 | 0.00 | +| SpanGet | 11 | 3.3408 ns | 0.0429 ns | 0.0380 ns | 3.3398 ns | 1.00 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 12 | 5.2640 ns | 0.1334 ns | 0.2077 ns | 5.3314 ns | 1.59 | 0.06 | +| ArrayGetWithField | 12 | 3.2920 ns | 0.0112 ns | 0.0093 ns | 3.2942 ns | 1.00 | 0.00 | +| SpanGet | 12 | 3.5210 ns | 0.0979 ns | 0.0916 ns | 3.4896 ns | 1.07 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 13 | 5.3287 ns | 0.0715 ns | 0.0669 ns | 5.3427 ns | 1.34 | 0.02 | +| ArrayGetWithField | 13 | 3.9872 ns | 0.0499 ns | 0.0467 ns | 3.9771 ns | 1.00 | 0.00 | +| SpanGet | 13 | 4.0250 ns | 0.0803 ns | 0.0751 ns | 4.0180 ns | 1.01 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 14 | 5.5447 ns | 0.1082 ns | 0.0845 ns | 5.5129 ns | 1.35 | 0.03 | +| ArrayGetWithField | 14 | 4.1072 ns | 0.0352 ns | 0.0329 ns | 4.1011 ns | 1.00 | 0.00 | +| SpanGet | 14 | 4.3101 ns | 0.1149 ns | 0.2042 ns | 4.4471 ns | 1.05 | 0.06 | +| | | | | | | | | +| ArrayGetWithVar | 15 | 5.9592 ns | 0.1007 ns | 0.0942 ns | 5.9521 ns | 1.38 | 0.02 | +| ArrayGetWithField | 15 | 4.3303 ns | 0.0146 ns | 0.0137 ns | 4.3332 ns | 1.00 | 0.00 | +| SpanGet | 15 | 4.5349 ns | 0.0952 ns | 0.0795 ns | 4.5473 ns | 1.05 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 16 | 6.2242 ns | 0.1520 ns | 0.2081 ns | 6.1473 ns | 1.30 | 0.05 | +| ArrayGetWithField | 16 | 4.8017 ns | 0.0312 ns | 0.0292 ns | 4.8077 ns | 1.00 | 0.00 | +| SpanGet | 16 | 4.6087 ns | 0.1206 ns | 0.1128 ns | 4.5630 ns | 0.96 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 17 | 6.5752 ns | 0.1582 ns | 0.2057 ns | 6.5724 ns | 1.44 | 0.05 | +| ArrayGetWithField | 17 | 4.5792 ns | 0.0274 ns | 0.0243 ns | 4.5754 ns | 1.00 | 0.00 | +| SpanGet | 17 | 5.1442 ns | 0.0570 ns | 0.0533 ns | 5.1497 ns | 1.12 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 18 | 6.6317 ns | 0.1327 ns | 0.1108 ns | 6.5870 ns | 1.17 | 0.02 | +| ArrayGetWithField | 18 | 5.6728 ns | 0.0296 ns | 0.0277 ns | 5.6707 ns | 1.00 | 0.00 | +| SpanGet | 18 | 4.9786 ns | 0.0787 ns | 0.0736 ns | 4.9597 ns | 0.88 | 0.02 | +| | | | | | | | | +| ArrayGetWithVar | 19 | 6.9428 ns | 0.0477 ns | 0.0398 ns | 6.9283 ns | 1.33 | 0.01 | +| ArrayGetWithField | 19 | 5.2353 ns | 0.0249 ns | 0.0221 ns | 5.2269 ns | 1.00 | 0.00 | +| SpanGet | 19 | 5.2300 ns | 0.0300 ns | 0.0266 ns | 5.2179 ns | 1.00 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 20 | 7.4626 ns | 0.0782 ns | 0.0653 ns | 7.4410 ns | 1.28 | 0.04 | +| ArrayGetWithField | 20 | 5.7347 ns | 0.1435 ns | 0.1815 ns | 5.6633 ns | 1.00 | 0.00 | +| SpanGet | 20 | 5.4807 ns | 0.0085 ns | 0.0075 ns | 5.4807 ns | 0.94 | 0.03 | +| | | | | | | | | +| ArrayGetWithVar | 30 | 10.4216 ns | 0.2361 ns | 0.2986 ns | 10.4605 ns | 1.31 | 0.04 | +| ArrayGetWithField | 30 | 7.9253 ns | 0.0101 ns | 0.0089 ns | 7.9231 ns | 1.00 | 0.00 | +| SpanGet | 30 | 7.8815 ns | 0.0056 ns | 0.0046 ns | 7.8799 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 32 | 10.9016 ns | 0.2460 ns | 0.2928 ns | 10.9234 ns | 1.20 | 0.04 | +| ArrayGetWithField | 32 | 9.0887 ns | 0.1490 ns | 0.1393 ns | 9.0872 ns | 1.00 | 0.00 | +| SpanGet | 32 | 8.3599 ns | 0.0023 ns | 0.0018 ns | 8.3594 ns | 0.92 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 40 | 12.1384 ns | 0.0332 ns | 0.0259 ns | 12.1259 ns | 1.18 | 0.00 | +| ArrayGetWithField | 40 | 10.2890 ns | 0.0161 ns | 0.0142 ns | 10.2858 ns | 1.00 | 0.00 | +| SpanGet | 40 | 10.2771 ns | 0.0087 ns | 0.0082 ns | 10.2725 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 50 | 14.5209 ns | 0.0094 ns | 0.0074 ns | 14.5204 ns | 1.14 | 0.00 | +| ArrayGetWithField | 50 | 12.7090 ns | 0.0251 ns | 0.0235 ns | 12.7142 ns | 1.00 | 0.00 | +| SpanGet | 50 | 12.6737 ns | 0.0104 ns | 0.0082 ns | 12.6719 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 60 | 16.9274 ns | 0.0157 ns | 0.0131 ns | 16.9289 ns | 1.12 | 0.00 | +| ArrayGetWithField | 60 | 15.0932 ns | 0.0385 ns | 0.0341 ns | 15.0869 ns | 1.00 | 0.00 | +| SpanGet | 60 | 15.0897 ns | 0.0438 ns | 0.0409 ns | 15.0743 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 80 | 22.2228 ns | 0.1792 ns | 0.1676 ns | 22.1639 ns | 1.07 | 0.01 | +| ArrayGetWithField | 80 | 20.8031 ns | 0.1991 ns | 0.1863 ns | 20.8343 ns | 1.00 | 0.00 | +| SpanGet | 80 | 20.2242 ns | 0.1555 ns | 0.1378 ns | 20.2300 ns | 0.97 | 0.01 | +| | | | | | | | | +| ArrayGetWithVar | 90 | 24.1033 ns | 0.0211 ns | 0.0176 ns | 24.0949 ns | 1.08 | 0.00 | +| ArrayGetWithField | 90 | 22.2535 ns | 0.0180 ns | 0.0160 ns | 22.2475 ns | 1.00 | 0.00 | +| SpanGet | 90 | 22.2278 ns | 0.0063 ns | 0.0052 ns | 22.2260 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 100 | 35.7519 ns | 0.1380 ns | 0.1291 ns | 35.7208 ns | 1.18 | 0.00 | +| ArrayGetWithField | 100 | 30.3591 ns | 0.0262 ns | 0.0219 ns | 30.3633 ns | 1.00 | 0.00 | +| SpanGet | 100 | 30.1375 ns | 0.0272 ns | 0.0241 ns | 30.1293 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 128 | 42.4070 ns | 0.0753 ns | 0.0668 ns | 42.3966 ns | 1.15 | 0.00 | +| ArrayGetWithField | 128 | 37.0188 ns | 0.0491 ns | 0.0436 ns | 37.0096 ns | 1.00 | 0.00 | +| SpanGet | 128 | 36.9420 ns | 0.1566 ns | 0.1465 ns | 36.8590 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 256 | 72.3880 ns | 0.5299 ns | 0.4957 ns | 72.2868 ns | 1.08 | 0.01 | +| ArrayGetWithField | 256 | 67.2595 ns | 0.1700 ns | 0.1590 ns | 67.1937 ns | 1.00 | 0.00 | +| SpanGet | 256 | 66.5708 ns | 0.1292 ns | 0.1145 ns | 66.5350 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 512 | 133.9119 ns | 0.3940 ns | 0.3290 ns | 133.9845 ns | 1.05 | 0.00 | +| ArrayGetWithField | 512 | 127.9565 ns | 0.1367 ns | 0.1141 ns | 127.9270 ns | 1.00 | 0.00 | +| SpanGet | 512 | 127.7372 ns | 0.1064 ns | 0.0943 ns | 127.7265 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 1024 | 255.4683 ns | 0.7553 ns | 0.6695 ns | 255.1568 ns | 1.02 | 0.00 | +| ArrayGetWithField | 1024 | 250.5938 ns | 0.2526 ns | 0.2239 ns | 250.5821 ns | 1.00 | 0.00 | +| SpanGet | 1024 | 250.7683 ns | 0.6080 ns | 0.5687 ns | 250.6417 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 2048 | 500.8917 ns | 1.0776 ns | 1.0080 ns | 500.4943 ns | 1.01 | 0.00 | +| ArrayGetWithField | 2048 | 495.4565 ns | 0.3916 ns | 0.3270 ns | 495.4714 ns | 1.00 | 0.00 | +| SpanGet | 2048 | 495.2572 ns | 0.4029 ns | 0.3364 ns | 495.2063 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 4096 | 992.1368 ns | 2.8754 ns | 2.6897 ns | 990.8590 ns | 1.01 | 0.00 | +| ArrayGetWithField | 4096 | 986.0712 ns | 0.8923 ns | 0.7910 ns | 985.8429 ns | 1.00 | 0.00 | +| SpanGet | 4096 | 984.9568 ns | 0.4072 ns | 0.3610 ns | 985.0019 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 8192 | 1,968.7538 ns | 0.2638 ns | 0.2060 ns | 1,968.7178 ns | 1.00 | 0.00 | +| ArrayGetWithField | 8192 | 1,966.1739 ns | 2.8904 ns | 2.5623 ns | 1,964.7451 ns | 1.00 | 0.00 | +| SpanGet | 8192 | 1,967.1070 ns | 5.1706 ns | 4.8366 ns | 1,964.2349 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 1000000 | 239,186.3037 ns | 31.2120 ns | 26.0634 ns | 239,181.6895 ns | 1.00 | 0.00 | +| ArrayGetWithField | 1000000 | 239,251.0824 ns | 190.1186 ns | 177.8370 ns | 239,231.1279 ns | 1.00 | 0.00 | +| SpanGet | 1000000 | 239,076.0725 ns | 42.7034 ns | 37.8555 ns | 239,069.7632 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArrayGetWithVar | 2147483591 | 513,362,891.6667 ns | 51,845.5735 ns | 40,477.6135 ns | 513,367,700.0000 ns | 1.00 | 0.00 | +| ArrayGetWithField | 2147483591 | 513,708,415.3846 ns | 214,407.5874 ns | 179,040.0460 ns | 513,678,800.0000 ns | 1.00 | 0.00 | +| SpanGet | 2147483591 | 513,460,507.1429 ns | 68,937.1375 ns | 61,110.9768 ns | 513,482,050.0000 ns | 1.00 | 0.00 | + +// * Warnings * +MultimodalDistribution + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> It seems that the distribution is bimodal (mValue = 3.76) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> It seems that the distribution can have several modes (mValue = 3.11) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> It seems that the distribution can have several modes (mValue = 3) + SpanVsArrayBenchmarks.SpanGet: Default -> It seems that the distribution is bimodal (mValue = 3.7) + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were detected (2.89 ns, 2.89 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (1.70 ns, 1.72 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (1.78 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was detected (3.22 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (2.30 ns..2.31 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (1.99 ns, 2.03 ns, 2.03 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (4.22 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were detected (2.49 ns, 2.49 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (3.18 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (5.32 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (5.54 ns..5.70 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (3.71 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was detected (3.69 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (3.84 ns, 3.94 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was detected (3.93 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (4.61 ns, 4.66 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.74 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (5.13 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (4.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed, 3 outliers were detected (4.78 ns, 4.82 ns, 4.83 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was detected (6.67 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (7.60 ns..7.80 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (5.80 ns, 6.25 ns, 6.30 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.06 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (6.20 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (8.53 ns, 8.53 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (8.60 ns, 8.88 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (6.83 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (6.86 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (9.40 ns, 9.72 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (7.04 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 4 outliers were detected (11.40 ns..11.44 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (9.46 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (9.41 ns, 9.45 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were detected (11.72 ns, 11.76 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was detected (10.28 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (9.87 ns..9.93 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (16.07 ns..16.45 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (11.86 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (16.06 ns..16.08 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (14.23 ns..14.41 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (18.48 ns, 18.50 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (16.73 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (22.24 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (25.69 ns, 25.74 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (23.83 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (23.77 ns, 23.80 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (31.96 ns, 32.11 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (31.75 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (44.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (39.06 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (69.39 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 4 outliers were detected (135.67 ns, 135.69 ns, 137.00 ns, 137.67 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (132.07 ns, 132.46 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (130.78 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (259.74 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 3 outliers were detected (252.78 ns, 252.79 ns, 253.80 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (500.68 ns, 502.22 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (500.38 ns, 500.51 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (991.97 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 3 outliers were detected (986.70 ns, 986.75 ns, 991.81 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (1.97 us..1.98 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (1.98 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (239.28 us, 239.68 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (240.97 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (513.53 ms..514.07 ms) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (515.12 ms, 515.88 ms) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 2 outliers were detected (513.32 ms, 513.60 ms) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Median : Value separating the higher half of all measurements (50th percentile) + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec)BenchmarkDotNet=v0.13.3, OS=ubuntu 20.04 + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + +Intel Xeon Platinum 8272CL CPU 2.60GHz, 1 CPU, 4 l +ogical and 2 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Ratio | RatioSD | +|------------------ |---------------- |--------------------:|------------------:|------------------:|------:|--------:| +| ArrayGetWithVar | 1 | 1.7144 ns | 0.0035 ns | 0.0033 ns | 4.97 | 0.01 | +| ArrayGetWithField | 1 | 0.3450 ns | 0.0009 ns | 0.0008 ns | 1.00 | 0.00 | +| SpanGet | 1 | 0.3670 ns | 0.0007 ns | 0.0007 ns | 1.06 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 2 | 1.7904 ns | 0.0007 ns | 0.0006 ns | 4.58 | 0.01 | +| ArrayGetWithField | 2 | 0.3912 ns | 0.0010 ns | 0.0009 ns | 1.00 | 0.00 | +| SpanGet | 2 | 0.6750 ns | 0.0021 ns | 0.0018 ns | 1.73 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 3 | 2.4049 ns | 0.0006 ns | 0.0005 ns | 2.50 | 0.00 | +| ArrayGetWithField | 3 | 0.9626 ns | 0.0018 ns | 0.0015 ns | 1.00 | 0.00 | +| SpanGet | 3 | 1.5743 ns | 0.0010 ns | 0.0008 ns | 1.64 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 4 | 2.9208 ns | 0.0017 ns | 0.0013 ns | 1.94 | 0.00 | +| ArrayGetWithField | 4 | 1.5076 ns | 0.0024 ns | 0.0021 ns | 1.00 | 0.00 | +| SpanGet | 4 | 1.9629 ns | 0.0027 ns | 0.0024 ns | 1.30 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 5 | 3.9197 ns | 0.0028 ns | 0.0025 ns | 1.57 | 0.00 | +| ArrayGetWithField | 5 | 2.4945 ns | 0.0048 ns | 0.0042 ns | 1.00 | 0.00 | +| SpanGet | 5 | 2.7509 ns | 0.0344 ns | 0.0322 ns | 1.10 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 6 | 3.5986 ns | 0.0261 ns | 0.0232 ns | 1.72 | 0.02 | +| ArrayGetWithField | 6 | 2.0963 ns | 0.0163 ns | 0.0152 ns | 1.00 | 0.00 | +| SpanGet | 6 | 3.0419 ns | 0.0818 ns | 0.0765 ns | 1.45 | 0.04 | +| | | | | | | | +| ArrayGetWithVar | 7 | 3.9044 ns | 0.0373 ns | 0.0349 ns | 1.64 | 0.02 | +| ArrayGetWithField | 7 | 2.3830 ns | 0.0172 ns | 0.0161 ns | 1.00 | 0.00 | +| SpanGet | 7 | 3.7281 ns | 0.0007 ns | 0.0006 ns | 1.57 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 8 | 4.1182 ns | 0.0017 ns | 0.0015 ns | 1.50 | 0.00 | +| ArrayGetWithField | 8 | 2.7423 ns | 0.0008 ns | 0.0007 ns | 1.00 | 0.00 | +| SpanGet | 8 | 3.9225 ns | 0.0006 ns | 0.0005 ns | 1.43 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 9 | 4.3981 ns | 0.0023 ns | 0.0018 ns | 1.53 | 0.00 | +| ArrayGetWithField | 9 | 2.8798 ns | 0.0008 ns | 0.0007 ns | 1.00 | 0.00 | +| SpanGet | 9 | 4.2915 ns | 0.0005 ns | 0.0005 ns | 1.49 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 10 | 4.6168 ns | 0.0045 ns | 0.0042 ns | 1.40 | 0.00 | +| ArrayGetWithField | 10 | 3.3040 ns | 0.0007 ns | 0.0006 ns | 1.00 | 0.00 | +| SpanGet | 10 | 4.3824 ns | 0.0087 ns | 0.0082 ns | 1.33 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 11 | 5.0594 ns | 0.0063 ns | 0.0049 ns | 1.35 | 0.00 | +| ArrayGetWithField | 11 | 3.7469 ns | 0.0008 ns | 0.0006 ns | 1.00 | 0.00 | +| SpanGet | 11 | 4.7152 ns | 0.0148 ns | 0.0138 ns | 1.26 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 12 | 5.1340 ns | 0.0012 ns | 0.0010 ns | 1.26 | 0.00 | +| ArrayGetWithField | 12 | 4.0666 ns | 0.0080 ns | 0.0071 ns | 1.00 | 0.00 | +| SpanGet | 12 | 5.0472 ns | 0.0073 ns | 0.0069 ns | 1.24 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 13 | 5.6316 ns | 0.0015 ns | 0.0012 ns | 1.28 | 0.01 | +| ArrayGetWithField | 13 | 4.3903 ns | 0.0249 ns | 0.0221 ns | 1.00 | 0.00 | +| SpanGet | 13 | 5.3302 ns | 0.0088 ns | 0.0078 ns | 1.21 | 0.01 | +| | | | | | | | +| ArrayGetWithVar | 14 | 5.8691 ns | 0.0060 ns | 0.0056 ns | 1.22 | 0.00 | +| ArrayGetWithField | 14 | 4.7932 ns | 0.0059 ns | 0.0056 ns | 1.00 | 0.00 | +| SpanGet | 14 | 5.6502 ns | 0.0179 ns | 0.0158 ns | 1.18 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 15 | 6.6377 ns | 0.0028 ns | 0.0022 ns | 1.27 | 0.00 | +| ArrayGetWithField | 15 | 5.2252 ns | 0.0014 ns | 0.0013 ns | 1.00 | 0.00 | +| SpanGet | 15 | 5.9511 ns | 0.0158 ns | 0.0148 ns | 1.14 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 16 | 6.8981 ns | 0.0119 ns | 0.0112 ns | 1.24 | 0.00 | +| ArrayGetWithField | 16 | 5.5505 ns | 0.0114 ns | 0.0106 ns | 1.00 | 0.00 | +| SpanGet | 16 | 6.2346 ns | 0.0200 ns | 0.0177 ns | 1.12 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 17 | 7.7612 ns | 0.0088 ns | 0.0078 ns | 1.33 | 0.00 | +| ArrayGetWithField | 17 | 5.8474 ns | 0.0084 ns | 0.0078 ns | 1.00 | 0.00 | +| SpanGet | 17 | 6.5241 ns | 0.0204 ns | 0.0191 ns | 1.12 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 18 | 8.0601 ns | 0.0168 ns | 0.0149 ns | 1.24 | 0.02 | +| ArrayGetWithField | 18 | 6.4858 ns | 0.1023 ns | 0.0957 ns | 1.00 | 0.00 | +| SpanGet | 18 | 6.8394 ns | 0.0342 ns | 0.0320 ns | 1.05 | 0.02 | +| | | | | | | | +| ArrayGetWithVar | 19 | 8.5406 ns | 0.0016 ns | 0.0013 ns | 1.27 | 0.00 | +| ArrayGetWithField | 19 | 6.7132 ns | 0.0154 ns | 0.0128 ns | 1.00 | 0.00 | +| SpanGet | 19 | 7.1487 ns | 0.0293 ns | 0.0274 ns | 1.06 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 20 | 8.8478 ns | 0.0129 ns | 0.0121 ns | 1.26 | 0.00 | +| ArrayGetWithField | 20 | 7.0262 ns | 0.0066 ns | 0.0058 ns | 1.00 | 0.00 | +| SpanGet | 20 | 7.4524 ns | 0.0215 ns | 0.0190 ns | 1.06 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 30 | 12.6107 ns | 0.0573 ns | 0.0536 ns | 1.18 | 0.01 | +| ArrayGetWithField | 30 | 10.7281 ns | 0.0160 ns | 0.0150 ns | 1.00 | 0.00 | +| SpanGet | 30 | 10.5565 ns | 0.0429 ns | 0.0380 ns | 0.98 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 32 | 13.2197 ns | 0.0958 ns | 0.0896 ns | 1.15 | 0.01 | +| ArrayGetWithField | 32 | 11.5303 ns | 0.0275 ns | 0.0244 ns | 1.00 | 0.00 | +| SpanGet | 32 | 11.7189 ns | 0.0331 ns | 0.0309 ns | 1.02 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 40 | 17.1164 ns | 0.0674 ns | 0.0598 ns | 1.04 | 0.00 | +| ArrayGetWithField | 40 | 16.4160 ns | 0.0464 ns | 0.0434 ns | 1.00 | 0.00 | +| SpanGet | 40 | 14.8238 ns | 0.0468 ns | 0.0438 ns | 0.90 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 50 | 18.5728 ns | 0.0593 ns | 0.0554 ns | 1.02 | 0.00 | +| ArrayGetWithField | 50 | 18.1323 ns | 0.0233 ns | 0.0218 ns | 1.00 | 0.00 | +| SpanGet | 50 | 18.3620 ns | 0.0352 ns | 0.0312 ns | 1.01 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 60 | 27.7889 ns | 0.0589 ns | 0.0551 ns | 1.08 | 0.00 | +| ArrayGetWithField | 60 | 25.7314 ns | 0.0342 ns | 0.0320 ns | 1.00 | 0.00 | +| SpanGet | 60 | 19.3356 ns | 0.0274 ns | 0.0243 ns | 0.75 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 80 | 50.0414 ns | 0.3840 ns | 0.3592 ns | 1.16 | 0.01 | +| ArrayGetWithField | 80 | 43.3203 ns | 0.0732 ns | 0.0685 ns | 1.00 | 0.00 | +| SpanGet | 80 | 46.2991 ns | 0.1615 ns | 0.1348 ns | 1.07 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 90 | 30.3271 ns | 0.0299 ns | 0.0250 ns | 0.92 | 0.00 | +| ArrayGetWithField | 90 | 32.9228 ns | 0.0448 ns | 0.0397 ns | 1.00 | 0.00 | +| SpanGet | 90 | 30.1212 ns | 0.0279 ns | 0.0247 ns | 0.91 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 100 | 50.9207 ns | 0.0524 ns | 0.0438 ns | 1.15 | 0.00 | +| ArrayGetWithField | 100 | 44.3034 ns | 0.0203 ns | 0.0169 ns | 1.00 | 0.00 | +| SpanGet | 100 | 48.1193 ns | 0.0569 ns | 0.0533 ns | 1.09 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 128 | 63.5936 ns | 0.0735 ns | 0.0687 ns | 1.17 | 0.00 | +| ArrayGetWithField | 128 | 54.3614 ns | 0.0326 ns | 0.0305 ns | 1.00 | 0.00 | +| SpanGet | 128 | 59.9921 ns | 0.0440 ns | 0.0390 ns | 1.10 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 256 | 101.1193 ns | 0.0666 ns | 0.0556 ns | 1.00 | 0.00 | +| ArrayGetWithField | 256 | 101.0048 ns | 0.0942 ns | 0.0835 ns | 1.00 | 0.00 | +| SpanGet | 256 | 97.7434 ns | 0.0330 ns | 0.0257 ns | 0.97 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 512 | 176.4697 ns | 0.0506 ns | 0.0473 ns | 0.90 | 0.00 | +| ArrayGetWithField | 512 | 195.4723 ns | 0.0439 ns | 0.0389 ns | 1.00 | 0.00 | +| SpanGet | 512 | 173.3604 ns | 0.0621 ns | 0.0551 ns | 0.89 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 1024 | 327.4840 ns | 0.0646 ns | 0.0540 ns | 0.85 | 0.00 | +| ArrayGetWithField | 1024 | 385.4179 ns | 0.4587 ns | 0.4066 ns | 1.00 | 0.00 | +| SpanGet | 1024 | 324.5606 ns | 0.4749 ns | 0.4210 ns | 0.84 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 2048 | 630.3876 ns | 0.4305 ns | 0.4027 ns | 0.82 | 0.00 | +| ArrayGetWithField | 2048 | 764.5638 ns | 1.1467 ns | 1.0726 ns | 1.00 | 0.00 | +| SpanGet | 2048 | 627.6572 ns | 0.7236 ns | 0.6768 ns | 0.82 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 4096 | 1,234.8919 ns | 0.1193 ns | 0.0996 ns | 0.81 | 0.00 | +| ArrayGetWithField | 4096 | 1,520.9689 ns | 0.7341 ns | 0.6508 ns | 1.00 | 0.00 | +| SpanGet | 4096 | 1,232.3784 ns | 0.6339 ns | 0.5293 ns | 0.81 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 8192 | 2,444.7565 ns | 0.4708 ns | 0.3931 ns | 0.81 | 0.00 | +| ArrayGetWithField | 8192 | 3,032.8116 ns | 0.4344 ns | 0.3391 ns | 1.00 | 0.00 | +| SpanGet | 8192 | 2,442.6178 ns | 0.5773 ns | 0.4821 ns | 0.81 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 1000000 | 295,415.8654 ns | 48.2387 ns | 42.7623 ns | 0.80 | 0.00 | +| ArrayGetWithField | 1000000 | 369,338.8544 ns | 163.6322 ns | 136.6403 ns | 1.00 | 0.00 | +| SpanGet | 1000000 | 295,432.4293 ns | 186.8233 ns | 145.8593 ns | 0.80 | 0.00 | +| | | | | | | | +| ArrayGetWithVar | 2147483591 | 635,510,534.2000 ns | 1,123,953.3384 ns | 1,051,346.6501 ns | 0.80 | 0.00 | +| ArrayGetWithField | 2147483591 | 792,820,286.0714 ns | 158,248.9330 ns | 140,283.5572 ns | 1.00 | 0.00 | +| SpanGet | 2147483591 | 634,263,796.3077 ns | 73,366.8521 ns | 61,264.6443 ns | 0.80 | 0.00 | + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.25 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (3.68 ns, 3.69 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (2.26 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (2.55 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (4.29 ns, 4.29 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed, 3 outliers were detected (2.82 ns, 2.83 ns, 2.83 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (3.44 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (4.82 ns..4.83 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (3.38 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (3.84 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (5.80 ns, 5.91 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.38 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (5.54 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (5.61 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (6.02 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (4.61 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (5.79 ns, 5.79 ns, 5.79 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (6.29 ns..6.29 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (4.75 ns, 4.77 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (6.16 ns, 6.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (5.18 ns, 5.18 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (6.99 ns..8.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (5.62 ns, 5.62 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (7.02 ns, 7.02 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (5.96 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (7.75 ns..7.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (6.43 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (7.22 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (7.56 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (8.53 ns..8.54 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (8.17 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.68 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (9.99 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 3 outliers were removed (10.44 ns..10.44 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (8.62 ns, 8.93 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (8.93 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (9.38 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 2 outliers were detected (12.35 ns, 12.51 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (13.46 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed (19.70 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed, 4 outliers were detected (20.15 ns..20.19 ns, 20.29 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (21.29 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was detected (44.96 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed, 3 outliers were detected (47.84 ns, 48.47 ns, 48.59 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (32.39 ns, 32.48 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (34.93 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (32.13 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (52.69 ns, 52.92 ns, 52.93 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (46.28 ns, 46.29 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (63.12 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (103.46 ns, 103.46 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (103.80 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (100.34 ns..100.90 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (202.86 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (176.24 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (330.17 ns, 330.82 ns) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (394.09 ns) + SpanVsArrayBenchmarks.SpanGet: Default -> 1 outlier was removed (328.45 ns) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (1.24 us, 1.24 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed, 2 outliers were detected (1.52 us, 1.53 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 2 outliers were removed (2.45 us, 2.45 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 3 outliers were removed (3.04 us..3.04 us) + SpanVsArrayBenchmarks.ArrayGetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (295.35 us, 295.66 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 2 outliers were removed (369.92 us, 370.16 us) + SpanVsArrayBenchmarks.SpanGet: Default -> 3 outliers were removed (296.57 us..306.32 us) + SpanVsArrayBenchmarks.ArrayGetWithField: Default -> 1 outlier was removed (793.21 ms) + SpanVsArrayBenchmarks.SpanGet: Default -> 2 outliers were removed (634.57 ms, 634.59 ms) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + + BenchmarkDotNet=v0.13.3, OS=Windows 11 (10.0.22621.1555) +AMD Ryzen 7 4700U with Radeon Graphics, 1 CPU, 8 logical and 8 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +|------------------ |---------------- |----------------------:|-------------------:|-------------------:|----------------------:|------:|--------:| +| ArraySetWithVar | 1 | 1.4697 ns | 0.0243 ns | 0.0227 ns | 1.4777 ns | 4.29 | 0.29 | +| ArraySetWithField | 1 | 0.3436 ns | 0.0225 ns | 0.0210 ns | 0.3492 ns | 1.00 | 0.00 | +| SpanSet | 1 | 0.5153 ns | 0.0077 ns | 0.0069 ns | 0.5159 ns | 1.50 | 0.09 | +| | | | | | | | | +| ArraySetWithVar | 2 | 1.7058 ns | 0.0124 ns | 0.0116 ns | 1.7081 ns | 2.44 | 0.02 | +| ArraySetWithField | 2 | 0.7001 ns | 0.0017 ns | 0.0013 ns | 0.7000 ns | 1.00 | 0.00 | +| SpanSet | 2 | 1.1681 ns | 0.0445 ns | 0.0417 ns | 1.1618 ns | 1.68 | 0.07 | +| | | | | | | | | +| ArraySetWithVar | 3 | 2.1158 ns | 0.0724 ns | 0.1589 ns | 2.1493 ns | 2.18 | 0.16 | +| ArraySetWithField | 3 | 0.9698 ns | 0.0024 ns | 0.0020 ns | 0.9697 ns | 1.00 | 0.00 | +| SpanSet | 3 | 1.5822 ns | 0.0164 ns | 0.0153 ns | 1.5835 ns | 1.63 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 4 | 2.2945 ns | 0.0055 ns | 0.0049 ns | 2.2936 ns | 1.80 | 0.00 | +| ArraySetWithField | 4 | 1.2779 ns | 0.0038 ns | 0.0029 ns | 1.2779 ns | 1.00 | 0.00 | +| SpanSet | 4 | 2.0907 ns | 0.0125 ns | 0.0111 ns | 2.0963 ns | 1.64 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 5 | 2.7734 ns | 0.0494 ns | 0.0507 ns | 2.7562 ns | 1.60 | 0.03 | +| ArraySetWithField | 5 | 1.7355 ns | 0.0096 ns | 0.0080 ns | 1.7390 ns | 1.00 | 0.00 | +| SpanSet | 5 | 2.8583 ns | 0.0867 ns | 0.1586 ns | 2.7817 ns | 1.65 | 0.10 | +| | | | | | | | | +| ArraySetWithVar | 6 | 3.8121 ns | 0.0173 ns | 0.0154 ns | 3.8119 ns | 1.82 | 0.02 | +| ArraySetWithField | 6 | 2.0951 ns | 0.0165 ns | 0.0146 ns | 2.0946 ns | 1.00 | 0.00 | +| SpanSet | 6 | 3.3108 ns | 0.0354 ns | 0.0331 ns | 3.3259 ns | 1.58 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 7 | 3.4567 ns | 0.0165 ns | 0.0129 ns | 3.4596 ns | 1.41 | 0.01 | +| ArraySetWithField | 7 | 2.4506 ns | 0.0052 ns | 0.0046 ns | 2.4511 ns | 1.00 | 0.00 | +| SpanSet | 7 | 3.6886 ns | 0.0219 ns | 0.0183 ns | 3.6959 ns | 1.51 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 8 | 3.8263 ns | 0.0097 ns | 0.0086 ns | 3.8244 ns | 1.40 | 0.01 | +| ArraySetWithField | 8 | 2.7420 ns | 0.0113 ns | 0.0106 ns | 2.7401 ns | 1.00 | 0.00 | +| SpanSet | 8 | 4.3303 ns | 0.0419 ns | 0.0392 ns | 4.3372 ns | 1.58 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 9 | 4.0671 ns | 0.0144 ns | 0.0120 ns | 4.0669 ns | 1.32 | 0.00 | +| ArraySetWithField | 9 | 3.0772 ns | 0.0066 ns | 0.0055 ns | 3.0759 ns | 1.00 | 0.00 | +| SpanSet | 9 | 4.7950 ns | 0.0458 ns | 0.0429 ns | 4.7805 ns | 1.56 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 10 | 5.0931 ns | 0.0349 ns | 0.0326 ns | 5.1000 ns | 1.41 | 0.02 | +| ArraySetWithField | 10 | 3.6005 ns | 0.0558 ns | 0.0522 ns | 3.6177 ns | 1.00 | 0.00 | +| SpanSet | 10 | 5.3014 ns | 0.1238 ns | 0.1158 ns | 5.2656 ns | 1.47 | 0.04 | +| | | | | | | | | +| ArraySetWithVar | 11 | 4.6538 ns | 0.1019 ns | 0.0953 ns | 4.6584 ns | 1.27 | 0.03 | +| ArraySetWithField | 11 | 3.6546 ns | 0.0123 ns | 0.0115 ns | 3.6556 ns | 1.00 | 0.00 | +| SpanSet | 11 | 5.6726 ns | 0.0291 ns | 0.0272 ns | 5.6684 ns | 1.55 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 12 | 5.0027 ns | 0.0697 ns | 0.0652 ns | 4.9778 ns | 1.25 | 0.02 | +| ArraySetWithField | 12 | 4.0128 ns | 0.0249 ns | 0.0233 ns | 4.0165 ns | 1.00 | 0.00 | +| SpanSet | 12 | 6.0483 ns | 0.0539 ns | 0.0421 ns | 6.0564 ns | 1.51 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 13 | 5.1638 ns | 0.0386 ns | 0.0342 ns | 5.1679 ns | 1.23 | 0.01 | +| ArraySetWithField | 13 | 4.1868 ns | 0.0080 ns | 0.0071 ns | 4.1877 ns | 1.00 | 0.00 | +| SpanSet | 13 | 6.8180 ns | 0.1338 ns | 0.1252 ns | 6.8105 ns | 1.63 | 0.03 | +| | | | | | | | | +| ArraySetWithVar | 14 | 5.3945 ns | 0.0084 ns | 0.0065 ns | 5.3939 ns | 1.17 | 0.00 | +| ArraySetWithField | 14 | 4.6050 ns | 0.0163 ns | 0.0136 ns | 4.6079 ns | 1.00 | 0.00 | +| SpanSet | 14 | 7.4360 ns | 0.0431 ns | 0.0360 ns | 7.4488 ns | 1.61 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 15 | 6.0572 ns | 0.1460 ns | 0.2046 ns | 6.0832 ns | 1.25 | 0.04 | +| ArraySetWithField | 15 | 4.8496 ns | 0.0054 ns | 0.0042 ns | 4.8491 ns | 1.00 | 0.00 | +| SpanSet | 15 | 7.9418 ns | 0.0164 ns | 0.0137 ns | 7.9376 ns | 1.64 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 16 | 6.4385 ns | 0.1580 ns | 0.2412 ns | 6.3191 ns | 1.26 | 0.05 | +| ArraySetWithField | 16 | 5.1288 ns | 0.0365 ns | 0.0324 ns | 5.1361 ns | 1.00 | 0.00 | +| SpanSet | 16 | 8.8905 ns | 0.0499 ns | 0.0466 ns | 8.8896 ns | 1.73 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 17 | 6.6477 ns | 0.1621 ns | 0.1665 ns | 6.6454 ns | 1.23 | 0.04 | +| ArraySetWithField | 17 | 5.3947 ns | 0.1035 ns | 0.0864 ns | 5.3538 ns | 1.00 | 0.00 | +| SpanSet | 17 | 8.5048 ns | 0.0249 ns | 0.0221 ns | 8.5075 ns | 1.58 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 18 | 6.6377 ns | 0.1267 ns | 0.1244 ns | 6.5944 ns | 1.18 | 0.02 | +| ArraySetWithField | 18 | 5.6304 ns | 0.0105 ns | 0.0093 ns | 5.6274 ns | 1.00 | 0.00 | +| SpanSet | 18 | 8.9234 ns | 0.0360 ns | 0.0319 ns | 8.9253 ns | 1.58 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 19 | 7.0242 ns | 0.0154 ns | 0.0120 ns | 7.0265 ns | 1.16 | 0.00 | +| ArraySetWithField | 19 | 6.0558 ns | 0.0153 ns | 0.0136 ns | 6.0505 ns | 1.00 | 0.00 | +| SpanSet | 19 | 9.6918 ns | 0.0267 ns | 0.0249 ns | 9.6928 ns | 1.60 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 20 | 7.3951 ns | 0.1098 ns | 0.1027 ns | 7.3463 ns | 1.17 | 0.01 | +| ArraySetWithField | 20 | 6.3081 ns | 0.0147 ns | 0.0123 ns | 6.3062 ns | 1.00 | 0.00 | +| SpanSet | 20 | 10.3341 ns | 0.0067 ns | 0.0056 ns | 10.3315 ns | 1.64 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 30 | 9.7678 ns | 0.0492 ns | 0.0460 ns | 9.7524 ns | 1.06 | 0.01 | +| ArraySetWithField | 30 | 9.2401 ns | 0.0386 ns | 0.0342 ns | 9.2511 ns | 1.00 | 0.00 | +| SpanSet | 30 | 15.2144 ns | 0.0390 ns | 0.0345 ns | 15.2198 ns | 1.65 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 32 | 11.5139 ns | 0.2307 ns | 0.2158 ns | 11.3850 ns | 1.19 | 0.02 | +| ArraySetWithField | 32 | 9.7155 ns | 0.0127 ns | 0.0112 ns | 9.7123 ns | 1.00 | 0.00 | +| SpanSet | 32 | 16.0872 ns | 0.0204 ns | 0.0170 ns | 16.0871 ns | 1.66 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 40 | 12.2375 ns | 0.0264 ns | 0.0247 ns | 12.2253 ns | 1.01 | 0.00 | +| ArraySetWithField | 40 | 12.1236 ns | 0.0144 ns | 0.0134 ns | 12.1221 ns | 1.00 | 0.00 | +| SpanSet | 40 | 19.9416 ns | 0.0273 ns | 0.0255 ns | 19.9322 ns | 1.64 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 50 | 14.6316 ns | 0.0303 ns | 0.0268 ns | 14.6241 ns | 0.94 | 0.00 | +| ArraySetWithField | 50 | 15.5678 ns | 0.0862 ns | 0.0764 ns | 15.5547 ns | 1.00 | 0.00 | +| SpanSet | 50 | 24.7167 ns | 0.0153 ns | 0.0119 ns | 24.7160 ns | 1.59 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 60 | 17.0088 ns | 0.0186 ns | 0.0165 ns | 17.0064 ns | 0.92 | 0.01 | +| ArraySetWithField | 60 | 18.5370 ns | 0.1463 ns | 0.1368 ns | 18.5426 ns | 1.00 | 0.00 | +| SpanSet | 60 | 29.5779 ns | 0.1183 ns | 0.1107 ns | 29.6144 ns | 1.60 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 80 | 24.1961 ns | 0.1555 ns | 0.1455 ns | 24.1733 ns | 0.91 | 0.01 | +| ArraySetWithField | 80 | 26.4982 ns | 0.1534 ns | 0.1435 ns | 26.4651 ns | 1.00 | 0.00 | +| SpanSet | 80 | 40.4041 ns | 0.4015 ns | 0.3755 ns | 40.3401 ns | 1.52 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 90 | 24.2325 ns | 0.0650 ns | 0.0577 ns | 24.2251 ns | 0.72 | 0.01 | +| ArraySetWithField | 90 | 33.6785 ns | 0.2283 ns | 0.2136 ns | 33.7178 ns | 1.00 | 0.00 | +| SpanSet | 90 | 43.9471 ns | 0.1354 ns | 0.1057 ns | 43.9477 ns | 1.30 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 100 | 34.6069 ns | 0.3842 ns | 0.3593 ns | 34.7487 ns | 0.97 | 0.01 | +| ArraySetWithField | 100 | 35.5776 ns | 0.1210 ns | 0.1131 ns | 35.5837 ns | 1.00 | 0.00 | +| SpanSet | 100 | 53.7344 ns | 0.1538 ns | 0.1439 ns | 53.7185 ns | 1.51 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 128 | 41.4045 ns | 0.4361 ns | 0.4079 ns | 41.5116 ns | 0.95 | 0.01 | +| ArraySetWithField | 128 | 43.6934 ns | 0.1310 ns | 0.1162 ns | 43.6749 ns | 1.00 | 0.00 | +| SpanSet | 128 | 66.5672 ns | 0.1397 ns | 0.1238 ns | 66.5548 ns | 1.52 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 256 | 70.9427 ns | 0.4782 ns | 0.4473 ns | 71.0490 ns | 0.89 | 0.01 | +| ArraySetWithField | 256 | 80.0742 ns | 0.2967 ns | 0.2631 ns | 80.0270 ns | 1.00 | 0.00 | +| SpanSet | 256 | 127.5915 ns | 0.4899 ns | 0.4343 ns | 127.3657 ns | 1.59 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 512 | 132.1828 ns | 0.3919 ns | 0.3474 ns | 132.2670 ns | 0.86 | 0.00 | +| ArraySetWithField | 512 | 154.2140 ns | 0.3738 ns | 0.3497 ns | 154.2198 ns | 1.00 | 0.00 | +| SpanSet | 512 | 250.3647 ns | 0.5664 ns | 0.5021 ns | 250.0886 ns | 1.62 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 1024 | 256.1749 ns | 1.2610 ns | 1.1179 ns | 255.8400 ns | 0.84 | 0.00 | +| ArraySetWithField | 1024 | 303.9608 ns | 0.7641 ns | 0.7148 ns | 303.6921 ns | 1.00 | 0.00 | +| SpanSet | 1024 | 471.4042 ns | 0.9878 ns | 0.9240 ns | 471.5121 ns | 1.55 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 2048 | 501.2071 ns | 1.4541 ns | 1.2890 ns | 500.8381 ns | 0.83 | 0.00 | +| ArraySetWithField | 2048 | 600.8989 ns | 2.7570 ns | 2.4440 ns | 600.8161 ns | 1.00 | 0.00 | +| SpanSet | 2048 | 935.1442 ns | 1.5942 ns | 1.4912 ns | 934.6576 ns | 1.56 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 4096 | 996.5799 ns | 0.9021 ns | 0.7997 ns | 996.5021 ns | 0.83 | 0.00 | +| ArraySetWithField | 4096 | 1,201.1907 ns | 4.7698 ns | 4.4616 ns | 1,202.4870 ns | 1.00 | 0.00 | +| SpanSet | 4096 | 1,890.3773 ns | 6.1361 ns | 5.7397 ns | 1,890.7349 ns | 1.57 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 8192 | 1,987.9107 ns | 8.2600 ns | 6.8975 ns | 1,986.8454 ns | 0.83 | 0.00 | +| ArraySetWithField | 8192 | 2,399.1963 ns | 3.6931 ns | 2.8833 ns | 2,398.9801 ns | 1.00 | 0.00 | +| SpanSet | 8192 | 3,785.9897 ns | 21.6577 ns | 18.0852 ns | 3,782.0320 ns | 1.58 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 1000000 | 1,095,660.7117 ns | 21,714.0404 ns | 33,806.1449 ns | 1,083,141.5039 ns | 0.98 | 0.03 | +| ArraySetWithField | 1000000 | 1,111,976.3359 ns | 20,906.9048 ns | 27,910.1174 ns | 1,101,771.0938 ns | 1.00 | 0.00 | +| SpanSet | 1000000 | 1,114,171.4583 ns | 16,801.4894 ns | 15,716.1236 ns | 1,117,650.9766 ns | 1.00 | 0.04 | +| | | | | | | | | +| ArraySetWithVar | 2147483591 | 2,211,687,771.4286 ns | 16,208,703.4177 ns | 14,368,593.3923 ns | 2,215,022,850.0000 ns | 0.99 | 0.01 | +| ArraySetWithField | 2147483591 | 2,234,781,915.3846 ns | 14,747,844.4235 ns | 12,315,118.0269 ns | 2,232,597,000.0000 ns | 1.00 | 0.00 | +| SpanSet | 2147483591 | 2,256,799,913.3333 ns | 29,236,562.1000 ns | 27,347,898.3280 ns | 2,241,684,800.0000 ns | 1.01 | 0.02 | + +// * Warnings * +MultimodalDistribution + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> It seems that the distribution is bimodal (mValue = 3.78) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> It seems that the distribution is bimodal (mValue = 3.5) + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were detected (2.93 ns, 2.96 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (1.81 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (2.01 ns, 2.05 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was detected (3.19 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (2.22 ns..2.28 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (2.49 ns, 2.49 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (3.06 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (3.82 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed, 4 outliers were detected (2.77 ns, 2.79 ns..2.82 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 3 outliers were detected (3.57 ns, 3.57 ns, 3.62 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 4 outliers were removed (4.57 ns..4.77 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed, 5 outliers were detected (3.23 ns..3.23 ns, 3.26 ns, 3.27 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (4.84 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.37 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (3.72 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed, 4 outliers were detected (4.92 ns, 4.99 ns..5.39 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (3.98 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed, 4 outliers were detected (5.15 ns, 5.17 ns, 5.24 ns, 5.25 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.47 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (5.63 ns, 5.69 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (4.62 ns, 4.67 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (7.12 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (5.46 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 3 outliers were removed, 5 outliers were detected (7.48 ns, 7.48 ns, 7.69 ns..8.06 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (6.94 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (5.71 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed (6.93 ns..7.40 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed, 3 outliers were detected (6.07 ns, 6.14 ns, 6.14 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed, 5 outliers were detected (8.86 ns..8.92 ns, 8.99 ns, 9.00 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (6.37 ns..6.42 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (9.52 ns, 9.60 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (6.92 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (7.52 ns, 7.56 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (9.95 ns, 10.07 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed (8.68 ns..8.91 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (7.16 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (10.35 ns, 10.52 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 3 outliers were removed (8.69 ns..8.92 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (7.64 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (11.12 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (7.96 ns, 7.97 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (11.87 ns, 11.88 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 3 outliers were detected (10.65 ns, 10.72 ns, 10.81 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (16.84 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (11.28 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed, 3 outliers were detected (17.55 ns, 17.65 ns, 17.65 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (16.20 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (18.37 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 3 outliers were removed (26.29 ns..26.37 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (18.58 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (27.68 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (26.02 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 3 outliers were removed (45.90 ns..45.93 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was detected (35.18 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (36.80 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was detected (41.98 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 2 outliers were detected (44.93 ns, 45.65 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (69.12 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (82.86 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (131.98 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (133.69 ns, 136.28 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (257.71 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (262.80 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (507.27 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 2 outliers were detected (598.19 ns, 612.01 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (1.00 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (1.19 us) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (2.04 us, 2.08 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (2.42 us..2.44 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (3.85 us, 3.94 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 4 outliers were removed (1.22 ms..1.35 ms) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were detected (1.08 ms, 1.08 ms) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (2.18 s, 2.26 s) SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (2.28 s, 2.28 s) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Median : Value separating the higher half of all measurements (50th percentile) + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) + +************************************************************************************************************************************************* +************************************************************************************************************************************************* +************************************************************************************************************************************************* + +BenchmarkDotNet=v0.13.3, OS=ubuntu 20.04 +Intel Xeon Platinum 8272CL CPU 2.60GHz, 1 CPU, 4 logical and 2 physical cores +.NET SDK=7.0.203 + [Host] : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + DefaultJob : .NET 7.0.5 (7.0.523.17405), X64 RyuJIT AVX2 + + +| Method | TestObjectCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +|------------------ |---------------- |------------------------:|----------------------:|----------------------:|------------------------:|------:|--------:| +| ArraySetWithVar | 1 | 1.6005 ns | 0.0025 ns | 0.0022 ns | 1.6007 ns | 4.61 | 0.03 | +| ArraySetWithField | 1 | 0.3466 ns | 0.0027 ns | 0.0025 ns | 0.3478 ns | 1.00 | 0.00 | +| SpanSet | 1 | 0.7008 ns | 0.0007 ns | 0.0006 ns | 0.7008 ns | 2.02 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 2 | 1.9617 ns | 0.0028 ns | 0.0026 ns | 1.9621 ns | 2.97 | 0.01 | +| ArraySetWithField | 2 | 0.6612 ns | 0.0016 ns | 0.0013 ns | 0.6610 ns | 1.00 | 0.00 | +| SpanSet | 2 | 0.9224 ns | 0.0009 ns | 0.0009 ns | 0.9223 ns | 1.40 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 3 | 3.9180 ns | 0.0178 ns | 0.0166 ns | 3.9195 ns | 3.07 | 0.02 | +| ArraySetWithField | 3 | 1.2773 ns | 0.0067 ns | 0.0056 ns | 1.2749 ns | 1.00 | 0.00 | +| SpanSet | 3 | 1.6728 ns | 0.0065 ns | 0.0060 ns | 1.6733 ns | 1.31 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 4 | 3.0629 ns | 0.0035 ns | 0.0032 ns | 3.0636 ns | 1.66 | 0.00 | +| ArraySetWithField | 4 | 1.8461 ns | 0.0032 ns | 0.0030 ns | 1.8457 ns | 1.00 | 0.00 | +| SpanSet | 4 | 2.2457 ns | 0.0045 ns | 0.0039 ns | 2.2458 ns | 1.22 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 5 | 3.9794 ns | 0.0025 ns | 0.0022 ns | 3.9800 ns | 1.32 | 0.05 | +| ArraySetWithField | 5 | 3.0059 ns | 0.0939 ns | 0.1153 ns | 3.0882 ns | 1.00 | 0.00 | +| SpanSet | 5 | 3.1000 ns | 0.0028 ns | 0.0023 ns | 3.0997 ns | 1.03 | 0.04 | +| | | | | | | | | +| ArraySetWithVar | 6 | 3.5894 ns | 0.0089 ns | 0.0079 ns | 3.5899 ns | 1.49 | 0.02 | +| ArraySetWithField | 6 | 2.4166 ns | 0.0354 ns | 0.0331 ns | 2.4147 ns | 1.00 | 0.00 | +| SpanSet | 6 | 2.7727 ns | 0.0181 ns | 0.0161 ns | 2.7759 ns | 1.15 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 7 | 3.8705 ns | 0.0090 ns | 0.0084 ns | 3.8673 ns | 1.16 | 0.00 | +| ArraySetWithField | 7 | 3.3283 ns | 0.0074 ns | 0.0069 ns | 3.3268 ns | 1.00 | 0.00 | +| SpanSet | 7 | 3.1766 ns | 0.0102 ns | 0.0085 ns | 3.1786 ns | 0.95 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 8 | 4.1455 ns | 0.0030 ns | 0.0028 ns | 4.1448 ns | 1.30 | 0.00 | +| ArraySetWithField | 8 | 3.1821 ns | 0.0027 ns | 0.0024 ns | 3.1821 ns | 1.00 | 0.00 | +| SpanSet | 8 | 3.6841 ns | 0.0079 ns | 0.0074 ns | 3.6839 ns | 1.16 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 9 | 4.4378 ns | 0.0022 ns | 0.0019 ns | 4.4377 ns | 1.14 | 0.00 | +| ArraySetWithField | 9 | 3.8759 ns | 0.0029 ns | 0.0027 ns | 3.8764 ns | 1.00 | 0.00 | +| SpanSet | 9 | 4.1472 ns | 0.0158 ns | 0.0147 ns | 4.1470 ns | 1.07 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 10 | 4.7189 ns | 0.0038 ns | 0.0032 ns | 4.7184 ns | 1.15 | 0.00 | +| ArraySetWithField | 10 | 4.1148 ns | 0.0029 ns | 0.0024 ns | 4.1144 ns | 1.00 | 0.00 | +| SpanSet | 10 | 4.4050 ns | 0.0279 ns | 0.0233 ns | 4.3943 ns | 1.07 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 11 | 5.0961 ns | 0.0074 ns | 0.0062 ns | 5.0969 ns | 1.03 | 0.00 | +| ArraySetWithField | 11 | 4.9518 ns | 0.0044 ns | 0.0039 ns | 4.9520 ns | 1.00 | 0.00 | +| SpanSet | 11 | 4.9052 ns | 0.0329 ns | 0.0275 ns | 4.8949 ns | 0.99 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 12 | 5.2174 ns | 0.0074 ns | 0.0066 ns | 5.2167 ns | 0.92 | 0.00 | +| ArraySetWithField | 12 | 5.6778 ns | 0.0081 ns | 0.0076 ns | 5.6778 ns | 1.00 | 0.00 | +| SpanSet | 12 | 5.4086 ns | 0.0486 ns | 0.0455 ns | 5.4007 ns | 0.95 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 13 | 5.6888 ns | 0.0095 ns | 0.0084 ns | 5.6875 ns | 0.94 | 0.00 | +| ArraySetWithField | 13 | 6.0615 ns | 0.0056 ns | 0.0049 ns | 6.0600 ns | 1.00 | 0.00 | +| SpanSet | 13 | 5.7281 ns | 0.0331 ns | 0.0309 ns | 5.7292 ns | 0.94 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 14 | 5.8935 ns | 0.0120 ns | 0.0106 ns | 5.8901 ns | 0.93 | 0.00 | +| ArraySetWithField | 14 | 6.3079 ns | 0.0156 ns | 0.0146 ns | 6.3124 ns | 1.00 | 0.00 | +| SpanSet | 14 | 6.0179 ns | 0.0182 ns | 0.0170 ns | 6.0210 ns | 0.95 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 15 | 6.6929 ns | 0.0119 ns | 0.0112 ns | 6.6895 ns | 1.00 | 0.00 | +| ArraySetWithField | 15 | 6.6939 ns | 0.0056 ns | 0.0050 ns | 6.6924 ns | 1.00 | 0.00 | +| SpanSet | 15 | 6.5041 ns | 0.0192 ns | 0.0170 ns | 6.5039 ns | 0.97 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 16 | 6.9466 ns | 0.0123 ns | 0.0115 ns | 6.9451 ns | 0.99 | 0.00 | +| ArraySetWithField | 16 | 6.9918 ns | 0.0161 ns | 0.0126 ns | 6.9904 ns | 1.00 | 0.00 | +| SpanSet | 16 | 6.8335 ns | 0.0129 ns | 0.0115 ns | 6.8350 ns | 0.98 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 17 | 7.8803 ns | 0.1539 ns | 0.1364 ns | 7.8157 ns | 1.07 | 0.02 | +| ArraySetWithField | 17 | 7.3937 ns | 0.0072 ns | 0.0067 ns | 7.3923 ns | 1.00 | 0.00 | +| SpanSet | 17 | 7.3889 ns | 0.0295 ns | 0.0276 ns | 7.3834 ns | 1.00 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 18 | 8.0929 ns | 0.0084 ns | 0.0070 ns | 8.0907 ns | 1.04 | 0.00 | +| ArraySetWithField | 18 | 7.8115 ns | 0.0064 ns | 0.0057 ns | 7.8126 ns | 1.00 | 0.00 | +| SpanSet | 18 | 7.8917 ns | 0.1759 ns | 0.1646 ns | 7.8150 ns | 1.01 | 0.02 | +| | | | | | | | | +| ArraySetWithVar | 19 | 8.5985 ns | 0.0268 ns | 0.0237 ns | 8.5871 ns | 1.05 | 0.02 | +| ArraySetWithField | 19 | 8.2165 ns | 0.1507 ns | 0.1410 ns | 8.1337 ns | 1.00 | 0.00 | +| SpanSet | 19 | 8.4661 ns | 0.2031 ns | 0.2494 ns | 8.3056 ns | 1.04 | 0.03 | +| | | | | | | | | +| ArraySetWithVar | 20 | 8.8796 ns | 0.0080 ns | 0.0075 ns | 8.8776 ns | 1.03 | 0.01 | +| ArraySetWithField | 20 | 8.6620 ns | 0.0694 ns | 0.0615 ns | 8.6587 ns | 1.00 | 0.00 | +| SpanSet | 20 | 8.5786 ns | 0.0131 ns | 0.0123 ns | 8.5786 ns | 0.99 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 30 | 12.2340 ns | 0.0405 ns | 0.0359 ns | 12.2361 ns | 0.93 | 0.00 | +| ArraySetWithField | 30 | 13.0996 ns | 0.0613 ns | 0.0543 ns | 13.1167 ns | 1.00 | 0.00 | +| SpanSet | 30 | 12.9861 ns | 0.0075 ns | 0.0070 ns | 12.9860 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 32 | 13.0265 ns | 0.0452 ns | 0.0423 ns | 13.0312 ns | 0.81 | 0.01 | +| ArraySetWithField | 32 | 16.0182 ns | 0.1015 ns | 0.0949 ns | 15.9814 ns | 1.00 | 0.00 | +| SpanSet | 32 | 13.9318 ns | 0.0134 ns | 0.0126 ns | 13.9300 ns | 0.87 | 0.01 | +| | | | | | | | | +| ArraySetWithVar | 40 | 15.9772 ns | 0.0802 ns | 0.0750 ns | 15.9619 ns | 0.81 | 0.00 | +| ArraySetWithField | 40 | 19.8427 ns | 0.0528 ns | 0.0494 ns | 19.8337 ns | 1.00 | 0.00 | +| SpanSet | 40 | 18.5625 ns | 0.0147 ns | 0.0138 ns | 18.5616 ns | 0.94 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 50 | 19.0897 ns | 0.0472 ns | 0.0442 ns | 19.1012 ns | 0.80 | 0.00 | +| ArraySetWithField | 50 | 23.8520 ns | 0.0708 ns | 0.0662 ns | 23.8558 ns | 1.00 | 0.00 | +| SpanSet | 50 | 23.5046 ns | 0.0227 ns | 0.0201 ns | 23.4986 ns | 0.99 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 60 | 23.4799 ns | 0.1172 ns | 0.1096 ns | 23.4780 ns | 0.87 | 0.00 | +| ArraySetWithField | 60 | 26.8609 ns | 0.0311 ns | 0.0243 ns | 26.8626 ns | 1.00 | 0.00 | +| SpanSet | 60 | 29.1054 ns | 0.0204 ns | 0.0181 ns | 29.1026 ns | 1.08 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 80 | 50.2605 ns | 0.1258 ns | 0.1115 ns | 50.2455 ns | 1.07 | 0.00 | +| ArraySetWithField | 80 | 46.8658 ns | 0.0728 ns | 0.0645 ns | 46.8692 ns | 1.00 | 0.00 | +| SpanSet | 80 | 51.4484 ns | 0.0914 ns | 0.0855 ns | 51.4708 ns | 1.10 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 90 | 36.7051 ns | 0.1824 ns | 0.1706 ns | 36.7614 ns | 0.92 | 0.00 | +| ArraySetWithField | 90 | 39.6823 ns | 0.0930 ns | 0.0870 ns | 39.6612 ns | 1.00 | 0.00 | +| SpanSet | 90 | 43.4244 ns | 0.0195 ns | 0.0182 ns | 43.4271 ns | 1.09 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 100 | 51.3160 ns | 0.0568 ns | 0.0504 ns | 51.3058 ns | 1.00 | 0.00 | +| ArraySetWithField | 100 | 51.3570 ns | 0.0377 ns | 0.0353 ns | 51.3562 ns | 1.00 | 0.00 | +| SpanSet | 100 | 57.7251 ns | 0.0708 ns | 0.0662 ns | 57.7026 ns | 1.12 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 128 | 61.5201 ns | 0.0489 ns | 0.0434 ns | 61.5210 ns | 0.97 | 0.00 | +| ArraySetWithField | 128 | 63.5051 ns | 0.0859 ns | 0.0804 ns | 63.5097 ns | 1.00 | 0.00 | +| SpanSet | 128 | 71.3592 ns | 0.1269 ns | 0.1187 ns | 71.3527 ns | 1.12 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 256 | 105.8192 ns | 0.0383 ns | 0.0358 ns | 105.8335 ns | 0.88 | 0.00 | +| ArraySetWithField | 256 | 119.9394 ns | 0.0760 ns | 0.0711 ns | 119.9179 ns | 1.00 | 0.00 | +| SpanSet | 256 | 135.2443 ns | 0.1709 ns | 0.1599 ns | 135.2831 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 512 | 210.0185 ns | 0.1510 ns | 0.1339 ns | 209.9837 ns | 0.90 | 0.00 | +| ArraySetWithField | 512 | 233.1248 ns | 0.0516 ns | 0.0431 ns | 233.1158 ns | 1.00 | 0.00 | +| SpanSet | 512 | 263.0390 ns | 0.1591 ns | 0.1410 ns | 263.0316 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 1024 | 417.8774 ns | 0.2947 ns | 0.2613 ns | 417.9022 ns | 0.91 | 0.00 | +| ArraySetWithField | 1024 | 460.3398 ns | 0.1941 ns | 0.1516 ns | 460.3139 ns | 1.00 | 0.00 | +| SpanSet | 1024 | 518.9574 ns | 0.3053 ns | 0.2706 ns | 518.8852 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 2048 | 835.0463 ns | 0.5355 ns | 0.4747 ns | 835.1100 ns | 0.91 | 0.00 | +| ArraySetWithField | 2048 | 915.4347 ns | 0.6474 ns | 0.5406 ns | 915.4247 ns | 1.00 | 0.00 | +| SpanSet | 2048 | 1,031.4134 ns | 0.7092 ns | 0.6634 ns | 1,031.5475 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 4096 | 1,711.6639 ns | 0.6226 ns | 0.5824 ns | 1,711.7812 ns | 0.94 | 0.00 | +| ArraySetWithField | 4096 | 1,830.3076 ns | 0.4474 ns | 0.3966 ns | 1,830.2355 ns | 1.00 | 0.00 | +| SpanSet | 4096 | 2,061.5471 ns | 1.6995 ns | 1.5066 ns | 2,061.7563 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 8192 | 3,414.7095 ns | 1.2613 ns | 1.1798 ns | 3,414.6534 ns | 0.94 | 0.00 | +| ArraySetWithField | 8192 | 3,645.9164 ns | 1.4002 ns | 1.3097 ns | 3,645.9304 ns | 1.00 | 0.00 | +| SpanSet | 8192 | 4,107.2606 ns | 3.4648 ns | 3.2409 ns | 4,107.4933 ns | 1.13 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 1000000 | 420,702.1225 ns | 255.2887 ns | 226.3068 ns | 420,727.8545 ns | 0.95 | 0.00 | +| ArraySetWithField | 1000000 | 444,452.6199 ns | 402.2439 ns | 356.5787 ns | 444,416.0256 ns | 1.00 | 0.00 | +| SpanSet | 1000000 | 500,024.9671 ns | 1,037.9277 ns | 970.8782 ns | 500,189.8135 ns | 1.12 | 0.00 | +| | | | | | | | | +| ArraySetWithVar | 2147483591 | 238,492,391,914.8000 ns | 4,592,687,023.3708 ns | 4,296,002,291.1676 ns | 239,458,354,055.0000 ns | 0.97 | 0.03 | +| ArraySetWithField | 2147483591 | 244,774,507,284.3333 ns | 4,025,896,310.4222 ns | 3,765,825,906.6134 ns | 246,203,799,599.0000 ns | 1.00 | 0.00 | +| SpanSet | 2147483591 | 225,900,352,939.8750 ns | 4,408,307,941.2510 ns | 5,732,045,514.6553 ns | 224,187,746,770.5000 ns | 0.92 | 0.03 | + +// * Hints * +Outliers + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (3.48 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (2.57 ns, 2.57 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (2.53 ns, 2.53 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (3.17 ns, 3.22 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (4.13 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.87 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (4.97 ns, 4.98 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (5.48 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were detected (4.20 ns, 4.23 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (4.76 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (5.06 ns, 5.08 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (5.10 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (6.31 ns, 6.32 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (6.60 ns, 6.60 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (5.99 ns, 6.00 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (6.41 ns, 6.45 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed, 3 outliers were detected (6.95 ns, 6.98 ns, 6.98 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (6.83 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 2 outliers were removed (7.06 ns, 7.20 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (7.11 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (7.58 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (7.97 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (7.80 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (8.60 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (8.42 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (8.94 ns..9.13 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (8.74 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (10.15 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 2 outliers were removed (9.99 ns, 10.00 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (9.70 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (10.55 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (10.72 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (14.22 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed, 4 outliers were detected (14.86 ns..14.89 ns, 15.08 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (25.53 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (25.45 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (28.86 ns..28.99 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (31.04 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (52.93 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (48.99 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (53.44 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was detected (53.14 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (64.12 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (212.83 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (235.92 ns, 237.35 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed, 2 outliers were detected (265.18 ns, 265.83 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed (421.48 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 3 outliers were removed (463.76 ns..469.66 ns) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (522.77 ns) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (836.49 ns, 839.38 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 2 outliers were removed (919.44 ns, 941.01 ns) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (1.84 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (2.07 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was detected (4.10 us) + SpanVsArrayBenchmarks.ArraySetWithVar: Default -> 1 outlier was removed, 2 outliers were detected (420.17 us, 421.17 us) + SpanVsArrayBenchmarks.ArraySetWithField: Default -> 1 outlier was removed (445.30 us) + SpanVsArrayBenchmarks.SpanSet: Default -> 1 outlier was removed (243.93 s) + +// * Legends * + TestObjectCount : Value of the 'TestObjectCount' parameter + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + Median : Value separating the higher half of all measurements (50th percentile) + Ratio : Mean of the ratio distribution ([Current]/[Baseline]) + RatioSD : Standard deviation of the ratio distribution ([Current]/[Baseline]) + 1 ns : 1 Nanosecond (0.000000001 sec) diff --git a/Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs b/Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs new file mode 100644 index 0000000..546d108 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/TaskRunVsTaskFactoryStartNew.Benchmarks.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Threading.Tasks.Sources; +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Benchmarks.POC +{ + [MemoryDiagnoser] + public class TaskRunVsTaskFactoryStartNewBenchmarks + { + public int NumberOfTasks = 100; + private static readonly TaskFactory TaskPool = new(); + + [Benchmark] + public void OwnTaskFactory() + { + for (var i = 0; i < NumberOfTasks; i++) + { + _ = TaskPool.StartNew(() => {}); + } + } + + [Benchmark] + public void TaskRun() + { + for (var i = 0; i < NumberOfTasks; i++) + { + _ = Task.Run(() => { }); + } + } + + [Benchmark(Baseline = true)] + public void TaskFactoryStartNew() + { + for (var i = 0; i < NumberOfTasks; i++) + { + _ = Task.Factory.StartNew(() => { }); + } + } + + [Benchmark] + public void TaskConstructor() + { + for (var i = 0; i < NumberOfTasks; i++) + { + new Task(() => { }).Start(); + } + } + + [Benchmark] + public void TaskConstructorWithKnownScheduler() + { + TaskScheduler scheduler = TaskScheduler.Current; + for (var i = 0; i < NumberOfTasks; i++) + { + new Task(() => { }).Start(scheduler); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs new file mode 100644 index 0000000..b2b3e4a --- /dev/null +++ b/Recyclable.Collections.Benchmarks/POC/WhenParallelSearchBenchmarks.cs @@ -0,0 +1,346 @@ +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.ObjectPool; +using MoreLinq; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Benchmarks.POC +{ + public enum WhenParallelSearchBenchmarkType + { + IndexOfSequentially, + IndexOfParallel, + } + + [MemoryDiagnoser] + public class WhenParallelSearchBenchmarks : PocBenchmarkBase + { + #region ItemRange V2 + + private sealed class ItemRangeV2 + { + public int BlockIndex; + public long ItemsToSearchCount; + internal bool _returned; + + public ItemRangeV2() + { + } + + public ItemRangeV2(int blockIndex, long itemsToSearchCount) + { + BlockIndex = blockIndex; + ItemsToSearchCount = itemsToSearchCount; + } + + public void Dispose() + { + if (!_returned) + { + _returned = true; + ItemRangePoolV2.Shared.Return(this); + } + } + } + + #endregion + + #region ItemRangePoolV2 + + private static class ItemRangePoolV2 + { + private static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static ItemRangeV2 Create(int blockIndex, long itemsToSearchCount) + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + var itemRange = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + itemRange._returned = false; + itemRange.BlockIndex = blockIndex; + itemRange.ItemsToSearchCount = itemsToSearchCount; + return itemRange; + } + } + + #endregion + + //[Params(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 32)] + //public int NumberOfThreads { get; set; } = 3; + + //[Params(10_000, 20_000, 35_000, 50_000, 75_000, 100_000, 125_000, 150_000, 175_000, 200_000, 225_000, 250_000, 275_000, 300_000, 325_000, 350_000, 375_000, 400_000, 500_000)] + //[Params(320_000, 321_000, 322_000, 323_000, 324_000, 325_000, 326_000, 327_000, 328_000, 329_000, 330_000)] + //[Params(329_000, 512_000, 750_000, 1_000_000, 3_290_000)] + //public int Step { get; set; } = 329_000; // This was the fastest so far + + private RecyclableLongList? _testData; + + public object? ItemToFind { get; set; } + + [Params(WhenParallelSearchBenchmarkType.IndexOfParallel)] + public override WhenParallelSearchBenchmarkType BenchmarkType { get => base.BenchmarkType; set => base.BenchmarkType = value; } + + [Params(WhenParallelSearchBenchmarkType.IndexOfSequentially)] + public override WhenParallelSearchBenchmarkType BaselineBenchmarkType { get => base.BaselineBenchmarkType; set => base.BaselineBenchmarkType = value; } + + private long DoLongIndexOfSequentially() + { + // This won't work for TestObjectCount == 0 + int index; + int blockSize = _testData!.BlockSize; + int lastBlockWithData = _testData.LastBlockWithData; + var memoryBlocks = _testData.AsArray; + var itemToFind = ItemToFind; + int blockIndex = 0; + while (true) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (index >= 0) + { + return (blockIndex << _testData.BlockSizePow2BitShift) + index; + } + + if (blockIndex + 1 < lastBlockWithData) + { + blockIndex++; + } + else + { + break; + } + } + + index = lastBlockWithData == _testData.NextItemBlockIndex + ? Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, _testData.NextItemIndex) + : Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, blockSize); + + return index >= 0 ? (lastBlockWithData << _testData.BlockSizePow2BitShift) + index : -1; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + private static long DoLongIndexOfParallel(RecyclableLongList list, in long itemToFind, ItemRangeV2 searchInfo, ManualResetEventSlim itemFoundSignal) + { + int index; + int blockSize = list.BlockSize; + int blockIndex = searchInfo.BlockIndex; + long itemsToSearchCount = searchInfo.ItemsToSearchCount; + long[][] memoryBlocks = list.AsArray; + index = Array.IndexOf(memoryBlocks[searchInfo.BlockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return -1; + } + else if (index >= 0) + { + return (blockIndex << list.BlockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + if (itemsToSearchCount < 0) + { + return -1; + } + + int lastBlockWithData = blockIndex + (int)(itemsToSearchCount >> list.BlockSizePow2BitShift) + ((itemsToSearchCount & (list.BlockSize - 1)) > 0 ? 1 : 0); + blockIndex++; + while (true) + { + index = Array.IndexOf(memoryBlocks[blockIndex], itemToFind, 0, blockSize); + if (itemFoundSignal.IsSet) + { + return -1; + } + else if (index >= 0) + { + return ((long)blockIndex << list.BlockSizePow2BitShift) + index; + } + + itemsToSearchCount -= blockSize; + if (blockIndex + 1 < lastBlockWithData && itemsToSearchCount < 0) + { + blockIndex++; + } + else + { + break; + } + } + + index = itemsToSearchCount < 0 || itemFoundSignal.IsSet || lastBlockWithData > list.NextItemBlockIndex + ? -1 + : lastBlockWithData == list.NextItemBlockIndex + ? Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, checked((int)Math.Min(list.NextItemIndex, itemsToSearchCount))) + : Array.IndexOf(memoryBlocks[lastBlockWithData], itemToFind, 0, checked((int)Math.Min(blockSize, itemsToSearchCount))); + + return itemFoundSignal.IsSet || index < 0 ? -1 : ((long)lastBlockWithData << list.BlockSizePow2BitShift) + index; + } + + private long DoIndexOfParallel() + { + using var itemFoundSignal = new ManualResetEventSlim(false); + using var allDoneSignal = new Barrier(1); + //long step = Step;// TestObjectCount / NumberOfThreads; + int blockSize = _testData!.BlockSize; + //object[][] memoryBlocks = _testData.AsArray(); + //long itemsRemaining = testObjectCount; + var foundItemIndex = -1L; + var itemToFind = ItemToFind; + //ItemRangesIterator.Iterate(0, blockSize, BlockSizePow2BitShift, TestObjectCount, (long)(TestObjectCount * 0.329), (searchInfo) => + //{ + // _ = allDoneSignal.AddParticipant(); + // _ = Task.Factory.StartNew((state) => + // { + // try + // { + // var index = DoLongIndexOfParallel(_testData, itemToFind, searchInfo, itemFoundSignal); + // if (index >= 0) + // { + // itemFoundSignal.Set(); + // lock (itemFoundSignal) + // { + // foundItemIndex = index; + // } + // } + // } + // finally + // { + // lock (allDoneSignal) + // { + // allDoneSignal.RemoveParticipant(); + // } + // } + // }, state: searchInfo); + + // return !itemFoundSignal.IsSet; + //}); + + //while (true) + //{ + // var toGive = Math.Min(step, itemsRemaining); + // SearchInfo searchInfo = new( + // blockIndex: blockIndex, + // startingItemIndex: itemIndex, + // itemsToSearchCount: toGive, + // itemToFind: ItemToFind, + // itemFoundSignal: itemFoundSignal); + + // _ = Task.Factory.StartNew((state) => + // { + // try + // { + // var index = DoLongIndexOfParallel(_testData, (SearchInfo)state!); + // if (index >= 0) + // { + // itemFoundSignal.Set(); + // lock (itemFoundSignal) + // { + // foundItemIndex = index; + // } + // } + // } + // finally + // { + // lock (allDoneSignal) + // { + // allDoneSignal.RemoveParticipant(); + // } + // } + // }, state: searchInfo); + + // //itemIndex = checked((int)((itemIndex + toGive) >> BlockSizePow2BitShift)) - 1; + // while (toGive > 0) + // { + // if (itemIndex + toGive < blockSize) + // { + // itemIndex += (int)toGive; + // break; + // } + // else + // { + // blockIndex++; + // //itemIndex += blockSize; + // //if (itemIndex > blockSize) + // //{ + // // itemIndex + // //} + // //if (itemIndex + blockSize < ) + // //itemIndex = (int)(toGive - blockSize + itemIndex); + // toGive -= blockSize; + // } + + // //if (blockIndex == _testData.LastBlockWithData) + // //{ + // // itemIndex = checked((int)toGive); + // // break; + // //} + + // } + + // if (threadIndex + 1 < numberOfRanges && blockIndex <= _testData.LastBlockWithData) + // { + // threadIndex++; + // } + // else + // { + // break; + // } + //} + + allDoneSignal.SignalAndWait(); + throw new NotImplementedException(); + return foundItemIndex; + } + + protected override void PrepareData(T benchmarkType) + { + base.PrepareData(benchmarkType); + + _testData ??= Enumerable.Range(1, TestObjectCount).Cast().ToRecyclableLongList(BlockSize); + ItemToFind ??= _testData.Last(); + } + + protected override Action? GetTestMethod(WhenParallelSearchBenchmarkType benchmarkType) => BenchmarkType switch + { + WhenParallelSearchBenchmarkType.IndexOfSequentially => IndexOfSequentially, + WhenParallelSearchBenchmarkType.IndexOfParallel => IndexOfParallel, + _ => throw CreateUnknownBenchmarkTypeException(benchmarkType), + }; + + public override void Cleanup() + { + _testData = default; + base.Cleanup(); + } + + public void IndexOfSequentially() + { + long index = DoLongIndexOfSequentially(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + + public void IndexOfParallel() + { + var index = DoIndexOfParallel(); + if (index >= 0) + { + DoNothing(index); + } + else + { + throw new Exception("Item not found"); + } + } + } +} diff --git a/Recyclable.Collections.Benchmarks/Program.cs b/Recyclable.Collections.Benchmarks/Program.cs index e6ba27f..2ae0983 100644 --- a/Recyclable.Collections.Benchmarks/Program.cs +++ b/Recyclable.Collections.Benchmarks/Program.cs @@ -1,8 +1,11 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; using Recyclable.Collections.Benchmarks; +using Recyclable.Collections.Benchmarks.POC; -BenchmarkRunner.Run(); -//BenchmarkRunner.Run(); +public class Program +{ + static IConfig BenchmarkConfig { get; } = ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator | ConfigOptions.JoinSummary); static void RunAssemblyBenchmarks() { @@ -19,40 +22,59 @@ BenchmarkRunner.Run(); // **************** // *** Template *** // **************** - var benchmark = new RecyclableCollectionsBenchmarks(); - benchmark.Setup(); - benchmark.RecyclableLongList_IndexOf_BestAndWorstCases(); - benchmark.Cleanup(); + //var benchmark = new RecyclableCollectionsBenchmarks(); + //benchmark.Setup(); - //foreach (var _ in Enumerable.Range(1, 1000)) - //{ + static void RunAssemblyBenchmarks() + { + _ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); + } + + static void RunRecyclableCollectionsBenchmarks() + { + _ = BenchmarkRunner.Run(BenchmarkConfig); + } + + static void RunSelectedBenchmarks() + { + // **************** + // *** Template *** + // **************** + var benchmark = new RecyclableCollectionsBenchmarks(); + benchmark.Setup(); + benchmark.RecyclableLongList_IndexOf_BestAndWorstCases(); + benchmark.Cleanup(); + + //foreach (var _ in Enumerable.Range(1, 1000)) + //{ // benchmark.RecyclableLongList_Remove_FirstItems(); // benchmark.RecyclableLongList_RemoveAt_LastItems(); // benchmark.List_RemoveAt_LastItems(); - //} + //} - //benchmark.Cleanup(); - } + //benchmark.Cleanup(); + } - static void RunPocBenchmarks() - { - //_ = BenchmarkRunner.Run(BenchmarkConfig); - //_ = BenchmarkRunner.Run(BenchmarkConfig); - //_ = BenchmarkRunner.Run(BenchmarkConfig); - // _ = BenchmarkRunner.Run(BenchmarkConfig); - _ = BenchmarkRunner.Run(BenchmarkConfig); - //_ = BenchmarkRunner.Run(BenchmarkConfig); - //_ = BenchmarkRunner.Run(BenchmarkConfig); - //_ = BenchmarkRunner.Run(BenchmarkConfig); - // _ = BenchmarkRunner.Run(BenchmarkConfig); - // ArraySizeLimitPocBenchmarks>(BenchmarkConfig); - } + static void RunPocBenchmarks() + { + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + _ = BenchmarkRunner.Run(BenchmarkConfig); + // ArraySizeLimitPocBenchmarks>(BenchmarkConfig); + } - private static void Main(string[] args) - { - RunRecyclableCollectionsBenchmarks(); - // RunPocBenchmarks(); - // RunSelectedBenchmarks(); - // RunAssemblyBenchmarks(); + static void Main(string[] args) + { + RunRecyclableCollectionsBenchmarks(); + // RunPocBenchmarks(); + // RunSelectedBenchmarks(); + // RunAssemblyBenchmarks(); + } } } diff --git a/Recyclable.Collections.Benchmarks/Properties/launchSettings.json b/Recyclable.Collections.Benchmarks/Properties/launchSettings.json new file mode 100644 index 0000000..192f7ef --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "WSL": { + "commandName": "WSL2", + "distributionName": "" + }, + ".Net": { + "commandName": "Project", + "nativeDebugging": true + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj index 190fbfc..59e656d 100644 --- a/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj +++ b/Recyclable.Collections.Benchmarks/Recyclable.Collections.Benchmarks.csproj @@ -6,10 +6,12 @@ net6.0;net7.0 enable enable + AnyCPU;x64 - + + diff --git a/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs new file mode 100644 index 0000000..602cd79 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/RecyclableBenchmarkBase.cs @@ -0,0 +1,340 @@ +using BenchmarkDotNet.Attributes; +using Collections.Pooled; +using Recyclable.Collections.Benchmarks.Core; +using Recyclable.Collections.Benchmarks.POC; + +namespace Recyclable.Collections.Benchmarks +{ + public enum RecyclableCollectionsBenchmarkSource + { + Unknown, + Array, + List, + PooledList, + RecyclableList, + RecyclableLongList, + } + + [MemoryDiagnoser] + public class RecyclableBenchmarkBase : PocBenchmarkBase + { + protected long[]? _testObjects; + protected long[] TestObjects => _testObjects ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + + protected List? _testObjectsAsList; + protected List TestObjectsAsList => _testObjectsAsList ?? throw new NullReferenceException("Something went wront and the field is not initialized"); + + protected PooledList? _testObjectsAsPooledList; + public PooledList TestObjectsAsPooledList => _testObjectsAsPooledList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + protected RecyclableList? _testObjectsAsRecyclableList; + protected RecyclableList TestObjectsAsRecyclableList => _testObjectsAsRecyclableList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + protected RecyclableLongList? _testObjectsAsRecyclableLongList; + protected RecyclableLongList TestObjectsAsRecyclableLongList => _testObjectsAsRecyclableLongList ?? throw new NullReferenceException("Something is wrong and the field is not initialized"); + protected IEnumerable TestObjectsAsIEnumerable + { + get + { + for (long i = 0; i < TestObjectCount; i++) + { + yield return i; + } + } + } + + public override void Setup() + { + PrepareData(RecyclableCollectionsBenchmarkSource.Array); + base.Setup(); + } + + protected override void PrepareData(T benchmarkType) + { + Console.WriteLine($"******* PREPARING DATA FOR {benchmarkType} *******"); + switch (benchmarkType) + { + case RecyclableCollectionsBenchmarkSource.Unknown: + break; + + case RecyclableCollectionsBenchmarkSource.Array: + _testObjects ??= DataGenerator.EnumerateTestObjects(TestObjectCount); + break; + + case RecyclableCollectionsBenchmarkSource.List: + _testObjectsAsList ??= TestObjects.ToList(); + break; + + case RecyclableCollectionsBenchmarkSource.PooledList: + _testObjectsAsPooledList ??= new PooledList(TestObjects, ClearMode.Auto); + break; + + case RecyclableCollectionsBenchmarkSource.RecyclableList: + _testObjectsAsRecyclableList ??= new(TestObjects, initialCapacity: TestObjectCount); + break; + + case RecyclableCollectionsBenchmarkSource.RecyclableLongList: + _testObjectsAsRecyclableLongList ??= new(TestObjects, BlockSize, expectedItemsCount: TestObjectCount); + break; + + default: + throw CreateUnknownBenchmarkTypeException(benchmarkType); + } + + base.PrepareData(benchmarkType); + } + + public override void Cleanup() + { + Console.WriteLine("******* GLOBAL CLEANUP *******"); + // Enable this if item type is changed to reference type + //if (_testObjects != null) + //{ + // Array.Fill(_testObjects, 0); + //} + + _testObjects = default; + _testObjectsAsList = default; + _testObjectsAsPooledList?.Dispose(); + _testObjectsAsPooledList = default; + _testObjectsAsRecyclableList?.Dispose(); + _testObjectsAsRecyclableList = default; + _testObjectsAsRecyclableLongList?.Dispose(); + _testObjectsAsRecyclableLongList = default; + base.Cleanup(); + } + } + + //[MemoryDiagnoser] + //public class ListVsLinkedListBenchmarks + //{ + // [Benchmark] + // public void RecyclableList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // using var list = new RecyclableList(initialCapacity: dataCount); + // for (long i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (long i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // [Benchmark(Baseline = true)] + // public void RecyclableLongList() + // { + // var data = TestObjects; + // long dataCount = TestObjectCount; + // using var list = new RecyclableLongList(BlockSize, totalItemsCount: dataCount); + // for (long i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (long i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void List() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new List(capacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(data[i]); + // } + + // for (var i = 0; i < list.Count; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void LinkedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new LinkedList(); + // for (var i = 0; i < dataCount; i++) + // { + // _ = list.AddLast(data[i]); + // } + + // foreach (var node in list) + // { + // DoNothing(node); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableQueue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableQueue(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i]); + // } + + // while (list.LongCount > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void Queue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new Queue(capacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i]); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void PriorityQueue() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new PriorityQueue(initialCapacity: dataCount); + // for (var i = 0; i < dataCount; i++) + // { + // list.Enqueue(data[i], i); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Dequeue()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableSortedList_InUpdateMode() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableSortedList(BlockSize); + // list.BeginUpdate(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // list.EndUpdate(); + + // for (var i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableSortedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableSortedList(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // for (var i = 0; i < list.LongCount; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void SortedList() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new SortedList(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Add(i, data[i]); + // } + + // for (var i = 0; i < list.Count; i++) + // { + // DoNothing(list[i]); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void RecyclableStack() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new RecyclableStack(BlockSize); + // for (var i = 0; i < dataCount; i++) + // { + // list.Push(data[i]); + // } + + // while (list.LongCount > 0) + // { + // DoNothing(list.Pop()); + // } + + // list.Clear(); + // } + + // //[Benchmark] + // public void Stack() + // { + // var data = TestObjects; + // var dataCount = TestObjectCount; + // var list = new Stack(); + // for (var i = 0; i < dataCount; i++) + // { + // list.Push(data[i]); + // } + + // while (list.Count > 0) + // { + // DoNothing(list.Pop()); + // } + + // list.Clear(); + // } + //} +} diff --git a/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt new file mode 100644 index 0000000..9d06771 --- /dev/null +++ b/Recyclable.Collections.Benchmarks/Results/AllInOneBenchmarks.txt @@ -0,0 +1,122 @@ +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |-----------------------:|--------------------:|--------------------:|-----------------------:|-----------:|--------:|----------:|----------:|---------:|-----------:|------------:| + + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |-----------:|---------:|---------:|------:|--------:|--------:|--------:|--------:|----------:|------------:| +| Array_Create_WithCapacity | 2,447.9 us | 8.35 us | 7.40 us | 1.99 | 0.02 | 58.5938 | 58.5938 | 58.5938 | 8000061 B | 137,932.09 | +| List_Create_WithCapacity | 2,377.6 us | 43.03 us | 40.25 us | 1.93 | 0.03 | 54.6875 | 54.6875 | 54.6875 | 8000451 B | 137,938.81 | +| PooledList_Create_WithCapacity | 1,229.4 us | 10.70 us | 9.48 us | 1.00 | 0.00 | - | - | - | 58 B | 1.00 | +| RecyclableList_Create_WithCapacity | 1,233.9 us | 3.93 us | 3.68 us | 1.00 | 0.01 | - | - | - | 41 B | 0.71 | +| RecyclableLongList_Create_WithCapacity | 786.5 us | 2.32 us | 1.94 us | 0.64 | 0.01 | - | - | - | 425 B | 7.33 | + +| List_Create | 3.5523 ns | 0.0181 ns | 0.0141 ns | 3.5476 ns | 0.000 | 0.00 | 0.0153 | - | - | 32 B | 0.000 | +| PooledList_Create | 6.9236 ns | 0.0110 ns | 0.0098 ns | 6.9222 ns | 0.000 | 0.00 | 0.0268 | - | - | 56 B | 0.000 | +| RecyclableList_Create | 4.6451 ns | 0.0063 ns | 0.0053 ns | 4.6457 ns | 0.000 | 0.00 | 0.0191 | - | - | 40 B | 0.000 | +| RecyclableLongList_Create | 212.8164 ns | 11.9841 ns | 35.3355 ns | 213.2258 ns | 0.000 | 0.00 | 0.0381 | 0.0379 | - | 80 B | 0.000 | + + Array_GetItem | 239.7 us | 0.36 us | 0.32 us | 0.66 | 0.00 | - | NA | + List_GetItem | 363.5 us | 0.67 us | 0.56 us | 1.00 | 0.00 | - | NA | + PooledList_GetItem | 365.3 us | 0.90 us | 0.75 us | 1.00 | 0.00 | - | NA | + RecyclableList_GetItem | 239.2 us | 0.07 us | 0.06 us | 0.65 | 0.00 | - | NA | + RecyclableLongList_GetItem | 538.0 us | 1.01 us | 0.89 us | 1.47 | 0.00 | 1 B | NA | + RecyclableLongList_GetItem_AsSpan | 673.5 us | 4.85 us | 4.30 us | 1.84 | 0.01 | 1 B | NA | + RecyclableLongList_GetItem_AsArray | 239.4 us | 0.22 us | 0.20 us | 0.66 | 0.00 | - | NA | + RecyclableLongList_GetItem_AsForEach | 5,514.6 us | 27.28 us | 22.78 us | 15.10 | 0.06 | 109 B | NA | + RecyclableLongList_GetItem_AsWhile | 459.3 us | 0.38 us | 0.34 us | 1.26 | 0.00 | - | NA | + +| Array_SetItem | 2,576,086.8389 ns | 13,880.4584 ns | 11,590.8114 ns | 2,576,978.1250 ns | 2.194 | 0.01 | - | - | - | 2 B | 0.000 | +| List_SetItem | 3,361,745.7734 ns | 98,390.1277 ns | 290,105.5114 ns | 3,505,120.8984 ns | 2.771 | 0.29 | - | - | - | 2 B | 0.000 | +| PooledList_SetItem | 2,676,757.7524 ns | 1,228.8178 ns | 1,026.1185 ns | 2,676,991.4062 ns | 2.280 | 0.01 | - | - | - | 2 B | 0.000 | +| RecyclableList_SetItem | 2,547,059.6540 ns | 9,002.9953 ns | 7,980.9208 ns | 2,542,056.8359 ns | 2.170 | 0.01 | - | - | - | 2 B | 0.000 | +| RecyclableLongList_SetItem | 2,326,771.9866 ns | 3,186.0942 ns | 2,824.3895 ns | 2,326,636.7188 ns | 1.981 | 0.01 | - | - | - | 2 B | 0.000 | + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------------------------------------------ |----------:|----------:|----------:|------:|--------:|---------:|--------:|--------:|-----------:|------------:| +| List_Add | 11.002 ms | 0.2189 ms | 0.2996 ms | 1.36 | 0.04 | 140.6250 | 93.7500 | 93.7500 | 16778117 B | 236,311.51 | +| PooledList_Add | 8.117 ms | 0.0115 ms | 0.0102 ms | 1.00 | 0.00 | - | - | - | 71 B | 1.00 | +| RecyclableList_Add | 8.771 ms | 0.0169 ms | 0.0141 ms | 1.08 | 0.00 | - | - | - | 1233 B | 17.37 | +| RecyclableLongList_Add | 6.024 ms | 0.1101 ms | 0.1030 ms | 0.74 | 0.01 | - | - | - | 3317 B | 46.72 | + +| List_Add_WithCapacity | 3,724,682.0668 ns | 88,719.4866 ns | 260,198.8962 ns | 3,717,873.0469 ns | 3.326 | 0.09 | 50.7813 | 50.7813 | 50.7813 | 4000368 B | 1.000 | +| PooledList_Add_WithCapacity | 2,575,618.8337 ns | 5,277.9410 ns | 4,678.7572 ns | 2,575,805.2734 ns | 2.194 | 0.01 | - | - | - | 60 B | 0.000 | +| RecyclableList_Add_WithCapacity | 2,785,606.2500 ns | 4,595.4354 ns | 4,073.7338 ns | 2,785,292.7734 ns | 2.372 | 0.01 | - | - | - | 42 B | 0.000 | +| RecyclableLongList_Add_WithCapacity | 2,878,685.9701 ns | 44,974.7609 ns | 35,113.3350 ns | 2,873,102.9297 ns | 2.453 | 0.03 | - | - | - | 170 B | 0.000 | + +| List_AddRangeWhenSourceIsArray | 1,966,607.1581 ns | 39,075.4380 ns | 100,165.2861 ns | 1,958,457.4219 ns | 1.721 | 0.07 | 42.9688 | 42.9688 | 42.9688 | 4000364 B | 1.000 | +| PooledList_AddRangeWhenSourceIsArray | 972,973.4049 ns | 12,365.6121 ns | 11,566.8012 ns | 974,744.8242 ns | 0.829 | 0.01 | - | - | - | 57 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsArray | 1,098,651.7578 ns | 3,202.7829 ns | 2,995.8851 ns | 1,097,979.4922 ns | 0.936 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsArray | 1,023,799.0234 ns | 2,772.1984 ns | 2,457.4816 ns | 1,023,618.7500 ns | 0.872 | 0.00 | - | - | - | 169 B | 0.000 | + +| List_AddRangeWhenSourceIsList | 1,954,805.9462 ns | 37,631.3829 ns | 71,597.6170 ns | 1,947,531.6406 ns | 1.656 | 0.06 | 42.9688 | 42.9688 | 42.9688 | 4000363 B | 1.000 | +| PooledList_AddRangeWhenSourceIsList | 963,657.7116 ns | 5,594.8195 ns | 5,233.3976 ns | 965,055.4199 ns | 0.820 | 0.01 | - | - | - | 57 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsList | 1,114,697.8385 ns | 4,015.8164 ns | 3,756.3971 ns | 1,115,863.6719 ns | 0.950 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsList | 1,008,424.0374 ns | 2,179.7502 ns | 1,932.2918 ns | 1,008,779.8828 ns | 0.859 | 0.00 | - | - | - | 169 B | 0.000 | + +| List_AddRangeWhenSourceIsIList | 2,045,453.8895 ns | 40,653.4327 ns | 98,956.0738 ns | 2,040,041.7969 ns | 1.734 | 0.12 | 42.9688 | 42.9688 | 42.9688 | 4000366 B | 1.000 | +| PooledList_AddRangeWhenSourceIsIList | 977,402.4790 ns | 4,165.1933 ns | 3,478.1251 ns | 978,420.3125 ns | 0.832 | 0.00 | - | - | - | 58 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsIList | 1,109,563.8477 ns | 4,480.2733 ns | 4,190.8505 ns | 1,108,281.9336 ns | 0.945 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsIList | 2,632,797.0052 ns | 3,899.2563 ns | 3,044.2828 ns | 2,632,267.1875 ns | 2.243 | 0.01 | - | - | - | 170 B | 0.000 | + +| PooledList_AddRangeWhenSourceIsIEnumerable | 28,102,425.4630 ns | 556,294.1092 ns | 779,846.6478 ns | 28,009,543.7500 ns | 24.007 | 0.74 | 2187.5000 | 1375.0000 | 218.7500 | 12000526 B | 3.000 | +| List_AddRangeWhenSourceIsIEnumerable | 47,884,030.8442 ns | 955,391.3313 ns | 1,370,193.6989 ns | 48,152,172.7273 ns | 40.578 | 1.04 | 2272.7273 | 1363.6364 | 363.6364 | 20389288 B | 5.097 | +| RecyclableList_AddRangeWhenSourceIsIEnumerable | 27,102,237.2533 ns | 535,598.7947 ns | 595,316.6412 ns | 27,248,382.8125 ns | 23.009 | 0.61 | 2250.0000 | 1406.2500 | 250.0000 | 12000305 B | 3.000 | +| RecyclableLongList_AddRangeWhenSourceIsIEnumerable | 27,206,882.1023 ns | 517,997.7196 ns | 636,147.7188 ns | 27,262,153.1250 ns | 23.049 | 0.64 | 2218.7500 | 1468.7500 | 250.0000 | 12000361 B | 3.000 | + +| PooledList_AddRangeWhenSourceIsPooledList | 958,132.3033 ns | 4,444.5508 ns | 3,939.9785 ns | 957,355.9570 ns | 0.816 | 0.00 | - | - | - | 57 B | 0.000 | +| RecyclableList_AddRangeWhenSourceIsRecyclableList | 1,068,135.4492 ns | 15,036.9473 ns | 12,556.5320 ns | 1,063,337.2070 ns | 0.910 | 0.01 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_AddRangeWhenSourceIsRecyclableList | 1,057,253.7249 ns | 2,697.5224 ns | 2,391.2834 ns | 1,057,475.9766 ns | 0.901 | 0.00 | - | - | - | 169 B | 0.000 | + +| List_AddRange_WithCapacity | 2,022,247.3039 ns | 44,489.4467 ns | 129,071.9520 ns | 2,030,418.9453 ns | 1.767 | 0.06 | 39.0625 | 39.0625 | 39.0625 | 4000318 B | 1.000 | +| PooledList_AddRange_WithCapacity | 965,635.9570 ns | 11,740.2154 ns | 10,981.8048 ns | 968,593.2617 ns | 0.822 | 0.01 | - | - | - | 57 B | 0.000 | +| RecyclableList_AddRange_WithCapacity | 1,044,438.5517 ns | 3,047.5353 ns | 2,544.8300 ns | 1,045,038.8672 ns | 0.890 | 0.00 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_AddRange_WithCapacity | 1,028,953.5156 ns | 17,735.8499 ns | 14,810.2379 ns | 1,022,104.4922 ns | 0.876 | 0.01 | - | - | - | 169 B | 0.000 | + +| Array_Count | 0.0087 ns | 0.0110 ns | 0.0103 ns | 0.0024 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| List_Count | 1.2190 ns | 0.0046 ns | 0.0040 ns | 1.2173 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| PooledList_Count | 0.0037 ns | 0.0015 ns | 0.0012 ns | 0.0040 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableList_Count | 1.2096 ns | 0.0009 ns | 0.0008 ns | 1.2092 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableLongList_Count | 0.0103 ns | 0.0008 ns | 0.0007 ns | 0.0102 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | + +| Array_LongCount | 0.0021 ns | 0.0005 ns | 0.0004 ns | 0.0022 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | +| RecyclableLongList_LongCount | 0.0030 ns | 0.0004 ns | 0.0003 ns | 0.0031 ns | 0.000 | 0.00 | - | - | - | - | 0.000 | + +| List_Contains_FirstItems | 1,855,720,661.5385 ns | 16,632,292.4881 ns | 13,888,717.5079 ns | 1,850,307,400.0000 ns | 1,580.480 | 10.24 | - | - | - | 600 B | 0.000 | +| PooledList_Contains_FirstItems | 1,854,542,814.2857 ns | 3,151,361.3032 ns | 2,793,599.7119 ns | 1,855,147,650.0000 ns | 1,579.345 | 5.84 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_Contains_FirstItems | 1,842,715,553.8462 ns | 5,837,438.3043 ns | 4,874,525.3630 ns | 1,840,917,300.0000 ns | 1,569.420 | 6.23 | - | - | - | 600 B | 0.000 | + +| List_Contains_LastItems | 56,934,350,853.3333 ns | 257,718,254.2389 ns | 241,069,814.9149 ns | 56,884,611,800.0000 ns | 48,516.143 | 270.39 | - | - | - | 600 B | 0.000 | +| PooledList_Contains_LastItems | 57,262,565,633.3333 ns | 295,685,051.7358 ns | 276,583,980.8497 ns | 57,246,796,900.0000 ns | 48,801.131 | 237.64 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_Contains_LastItems | 57,019,747,564.2857 ns | 264,241,719.1753 ns | 234,243,401.3552 ns | 57,059,345,900.0000 ns | 48,552.402 | 223.09 | - | - | - | 600 B | 0.000 | + +| Array_IndexOf_FirstItems | 1,874,810,260.0000 ns | 25,774,115.5271 ns | 24,109,123.6555 ns | 1,867,388,700.0000 ns | 1,597.521 | 22.54 | - | - | - | 600 B | 0.000 | +| List_IndexOf_FirstItems | 1,864,704,725.0000 ns | 10,982,470.8154 ns | 8,574,390.8164 ns | 1,866,253,200.0000 ns | 1,588.702 | 10.94 | - | - | - | 600 B | 0.000 | +| PooledList_IndexOf_FirstItems | 1,840,856,800.0000 ns | 4,046,589.8784 ns | 3,159,311.2037 ns | 1,839,944,050.0000 ns | 1,568.374 | 6.17 | - | - | - | 600 B | 0.000 | +| RecyclableList_IndexOf_FirstItems | 1,832,934,950.0000 ns | 1,729,308.0131 ns | 1,350,129.9476 ns | 1,832,475,300.0000 ns | 1,561.625 | 5.52 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_IndexOf_FirstItems | 1,844,986,792.3077 ns | 3,209,050.1952 ns | 2,679,702.2516 ns | 1,843,943,600.0000 ns | 1,571.359 | 6.49 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_LongIndexOf_FirstItems | 1,878,933,500.0000 ns | 17,118,131.6171 ns | 16,012,310.9355 ns | 1,870,009,600.0000 ns | 1,601.616 | 14.82 | - | - | - | 600 B | 0.000 | + +| List_IndexOf_LastItems | 57,303,575,140.0000 ns | 297,612,744.3548 ns | 278,387,145.7216 ns | 57,391,576,000.0000 ns | 48,792.865 | 303.56 | - | - | - | 600 B | 0.000 | +| PooledList_IndexOf_LastItems | 57,032,399,106.6667 ns | 281,241,865.3817 ns | 263,073,815.3730 ns | 56,895,992,200.0000 ns | 48,566.622 | 322.88 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_IndexOf_LastItems | 57,308,279,713.3333 ns | 272,718,905.6589 ns | 255,101,433.5602 ns | 57,297,645,400.0000 ns | 48,767.562 | 239.96 | - | - | - | 600 B | 0.000 | +| RecyclableLongList_LongIndexOf_LastItems | 57,198,323,460.0000 ns | 282,971,632.5816 ns | 264,691,840.6850 ns | 57,198,418,300.0000 ns | 48,669.107 | 304.11 | - | - | - | 600 B | 0.000 | + +| List_Remove_FirstItems | 5,023,030,012.5000 ns | 82,615,052.4052 ns | 107,422,903.9562 ns | 5,000,417,000.0000 ns | 4,295.171 | 116.37 | - | - | - | 4000656 B | 1.000 | +| PooledList_Remove_FirstItems | 4,750,601,721.4286 ns | 17,989,258.8487 ns | 15,947,009.4038 ns | 4,747,622,550.0000 ns | 4,043.997 | 21.26 | - | - | - | 992 B | 0.000 | +| RecyclableList_Remove_FirstItems | 4,950,634,900.0000 ns | 33,761,094.2570 ns | 29,928,330.6849 ns | 4,936,519,050.0000 ns | 4,218.075 | 23.90 | - | - | - | 640 B | 0.000 | +| RecyclableLongList_Remove_FirstItems | 5,020,383,250.0000 ns | 100,209,734.5497 ns | 115,401,679.3276 ns | 5,029,163,750.0000 ns | 4,306.772 | 86.95 | - | - | - | 952 B | 0.000 | + +| List_Remove_LastItems | 56,593,869,333.3333 ns | 258,693,958.6041 ns | 241,982,489.3835 ns | 56,513,091,800.0000 ns | 48,189.227 | 272.02 | - | - | - | 4000656 B | 1.000 | +| PooledList_Remove_LastItems | 56,465,443,542.8571 ns | 99,443,506.1766 ns | 88,154,078.0245 ns | 56,458,364,050.0000 ns | 48,092.866 | 216.09 | - | - | - | 992 B | 0.000 | +| RecyclableList_Remove_LastItems | 57,432,405,146.6667 ns | 264,062,625.8226 ns | 247,004,344.0307 ns | 57,409,508,000.0000 ns | 48,952.707 | 234.83 | - | - | - | 640 B | 0.000 | +| RecyclableLongList_Remove_LastItems | 57,077,678,728.5714 ns | 210,992,675.8689 ns | 187,039,511.4398 ns | 57,038,290,150.0000 ns | 48,614.503 | 242.79 | - | - | - | 952 B | 0.000 | + +| List_RemoveAt_FirstItems | 4,517,669,893.3333 ns | 77,360,576.7460 ns | 72,363,131.4861 ns | 4,546,302,700.0000 ns | 3,850.590 | 62.34 | - | - | - | 4000656 B | 1.000 | +| PooledList_RemoveAt_FirstItems | 4,439,417,700.0000 ns | 40,915,112.8948 ns | 38,272,021.9874 ns | 4,418,366,400.0000 ns | 3,779.751 | 38.92 | - | - | - | 992 B | 0.000 | +| RecyclableList_RemoveAt_FirstItems | 4,558,102,892.8571 ns | 25,076,973.8515 ns | 22,230,084.1404 ns | 4,559,174,800.0000 ns | 3,882.107 | 16.61 | - | - | - | 640 B | 0.000 | +| RecyclableLongList_RemoveAt_FirstItems | 4,617,033,766.6667 ns | 85,061,197.3076 ns | 79,566,296.7373 ns | 4,632,994,800.0000 ns | 3,928.768 | 72.17 | - | - | - | 952 B | 0.000 | + +| List_RemoveAt_LastItems | 2,005,211.3281 ns | 40,078.0351 ns | 84,538.1787 ns | 2,010,579.1016 ns | 1.725 | 0.08 | 42.9688 | 42.9688 | 42.9688 | 4000306 B | 1.000 | +| PooledList_RemoveAt_LastItems | 1,047,436.1654 ns | 5,655.2027 ns | 5,289.8801 ns | 1,044,690.9180 ns | 0.892 | 0.01 | - | - | - | 58 B | 0.000 | +| RecyclableList_RemoveAt_LastItems | 1,256,380.8969 ns | 9,142.3285 ns | 7,634.2584 ns | 1,254,354.9805 ns | 1.070 | 0.01 | - | - | - | 41 B | 0.000 | +| RecyclableLongList_RemoveAt_LastItems | 1,621,193.5826 ns | 20,030.6198 ns | 17,756.6227 ns | 1,620,789.8438 ns | 1.379 | 0.02 | - | - | - | 353 B | 0.000 | diff --git a/Recyclable.Collections.sln b/Recyclable.Collections.sln index 12edb6c..1af4cb0 100644 --- a/Recyclable.Collections.sln +++ b/Recyclable.Collections.sln @@ -7,38 +7,40 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.Collections", "R EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.CollectionsTests", "Recyclable.CollectionsTests\Recyclable.CollectionsTests.csproj", "{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Recyclable.Collections.Benchmarks", "Recyclable.Collections.Benchmarks\Recyclable.Collections.Benchmarks.csproj", "{CDC0CF78-FC11-43E3-9059-3261DA3639D1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceCodeGenerator.Net", "SourceCodeGenerator.Net\SourceCodeGenerator.Net.csproj", "{30C187CF-D469-48FC-8D35-6EB031535668}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.Collections.Benchmarks", "Recyclable.Collections.Benchmarks\Recyclable.Collections.Benchmarks.csproj", "{CDC0CF78-FC11-43E3-9059-3261DA3639D1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|x64.ActiveCfg = Debug|x64 + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Debug|x64.Build.0 = Debug|x64 {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|Any CPU.ActiveCfg = Release|Any CPU {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|Any CPU.Build.0 = Release|Any CPU + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|x64.ActiveCfg = Release|x64 + {7EC0F96D-FB4C-418A-9172-612CC5BCE705}.Release|x64.Build.0 = Release|x64 {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|x64.ActiveCfg = Debug|x64 + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|x64.Build.0 = Debug|x64 {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.Build.0 = Release|Any CPU + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|x64.ActiveCfg = Release|x64 + {2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|x64.Build.0 = Release|x64 {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|x64.ActiveCfg = Debug|x64 + {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|x64.Build.0 = Debug|x64 {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|Any CPU.ActiveCfg = Release|Any CPU {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|Any CPU.Build.0 = Release|Any CPU {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|x64.ActiveCfg = Release|x64 {CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|x64.Build.0 = Release|x64 - {30C187CF-D469-48FC-8D35-6EB031535668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Debug|x64.ActiveCfg = Debug|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Debug|x64.Build.0 = Debug|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Release|Any CPU.Build.0 = Release|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Release|x64.ActiveCfg = Release|Any CPU - {30C187CF-D469-48FC-8D35-6EB031535668}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Recyclable.Collections/ExceptionExtensions.cs b/Recyclable.Collections/ExceptionExtensions.cs new file mode 100644 index 0000000..e0a4f10 --- /dev/null +++ b/Recyclable.Collections/ExceptionExtensions.cs @@ -0,0 +1,12 @@ +using System.Runtime.ExceptionServices; + +namespace Recyclable.Collections +{ + internal static class ExceptionExtensions + { + public static void CaptureAndRethrow(this Exception ex) + { + ExceptionDispatchInfo.Capture(ex).Throw(); + } + } +} diff --git a/Recyclable.Collections/GlobalSuppressions.cs b/Recyclable.Collections/GlobalSuppressions.cs new file mode 100644 index 0000000..1f77122 --- /dev/null +++ b/Recyclable.Collections/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Style", "IDE0032:Use auto property", Justification = "", Scope = "member", Target = "~F:Recyclable.Collections.RecyclableList`1._count")] diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs index b209a0b..866a598 100644 --- a/Recyclable.Collections/ListExtensions.cs +++ b/Recyclable.Collections/ListExtensions.cs @@ -6,6 +6,79 @@ namespace Recyclable.Collections // It was only beautified for .Net 6.0+. internal static class ListExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + { + Span arrayMemory = destinationArray.AsSpan(); + arrayMemory = arrayMemory[destinationArrayIndex..]; + int startingArrayIndex = (int)(startingIndex / blockSize); + startingIndex %= blockSize; + if (arrays.Count > 0) + { + ReadOnlySpan sourceMemory = arrays[0].AsSpan(); + var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); + if (arrays.Count == 1) + { + maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); + } + + sourceMemory = sourceMemory[(int)startingIndex..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + arrayMemory = arrayMemory[maxToCopy..]; + } + + for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++) + { + ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); + var maxToCopy = Math.Min(blockSize, arrayMemory.Length); + sourceMemory = sourceMemory[..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + arrayMemory = arrayMemory[maxToCopy..]; + } + + if (arrays.Count > 1 && arrayMemory.Length > 0) + { + ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); + var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); + sourceMemory = sourceMemory[..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) + { + long currentCount = 0; + for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) + { + var array = arrays[arrayIdx]; + currentCount += Math.Min(chunkSize, totalCount); + switch (currentCount < totalCount) + { + case true: + for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + + case false: + var partialCount = (int)(totalCount % chunkSize); + int maxCount = partialCount > 0 ? partialCount : chunkSize; + for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + } + } + } + public static void InsertionSort(this IList values, int startIndex, int endIndex, IComparer comparer) { var left = startIndex; @@ -29,6 +102,26 @@ namespace Recyclable.Collections { values.InsertionSort(Comparer.Default); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long LongIndexOf(this RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) + { + for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + { + var array = arrays[arrayIdx]; + Span arrayMemory = array.AsSpan(); + for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) + { + if (comparer.Equals(arrayMemory[memoryIdx], item)) + { + return (arrayIdx * (long)blockSize) + memoryIdx; + } + } + } + + return -1; + } + public static void QuickSort(this IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) { var range = (startIndex, endIndex); diff --git a/Recyclable.Collections/Parallel/BarrierSlimmer.cs b/Recyclable.Collections/Parallel/BarrierSlimmer.cs new file mode 100644 index 0000000..4a859b6 --- /dev/null +++ b/Recyclable.Collections/Parallel/BarrierSlimmer.cs @@ -0,0 +1,117 @@ +using Microsoft.Extensions.ObjectPool; +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections.Parallel +{ + public sealed class BarrierSlimmer : IDisposable + { + private static readonly ObjectPool _defaultPool = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + private ObjectPool? _assignedPool; + private readonly SpinLock _lock = new(false); + + public bool HasParticipants; + public int Participants; + + [MethodImpl(MethodImplOptions.NoInlining)] + public void AddParticipant() + { + bool isLocked = false; + _lock.Enter(ref isLocked); + + Participants++; + if (Participants == 1) + { + HasParticipants = true; + } + + if (isLocked) + { + _lock.Exit(false); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public void RemoveParticipant() + { + if (!HasParticipants) + { + ThrowNoMoreParticipants(); + } + + bool isLocked = false; + _lock.Enter(ref isLocked); + + Participants--; + if (Participants == 0) + { + HasParticipants = false; + } + + if (isLocked) + { + _lock.Exit(false); + } + } + + private static void ThrowNoMoreParticipants() + { + throw new InvalidOperationException("There are no more participants. The operation is not allowed"); + } + + public BarrierSlimmer() + { + _lock = new(false); + } + + public BarrierSlimmer(int participants) + { + _lock = new(false); + Participants = participants; + HasParticipants = participants > 0; + } + + public static BarrierSlimmer Create(int participants) + { + var result = _defaultPool.Get(); + result.Participants = participants; + result.HasParticipants = participants > 0; + return result; + } + + public void Dispose() + { + var assignedPool = _assignedPool; + _assignedPool = null; + assignedPool?.Return(this); + + GC.SuppressFinalize(this); + } + + public void SignalAndWait() + { + RemoveParticipant(); + + //if (!HasParticipants) + //{ + // return; + //} + + //if (!HasParticipants) + //{ + // return; + //} + + //if (!HasParticipants) + //{ + // return; + //} + + // SpinWait.SpinUntil(() => HasParticipants); + var waitSpinner = new SpinWait(); + while (HasParticipants) + { + waitSpinner.SpinOnce(0); + } + } + } +} diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs new file mode 100644 index 0000000..ddcde32 --- /dev/null +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContext.cs @@ -0,0 +1,111 @@ +namespace Recyclable.Collections.Parallel +{ + public sealed class IndexOfSynchronizationContext + { + private SpinWait _participantsSpinWait = new(); + private SpinLockSlimmer _participantsSpinLock = new(); + private SpinLockSlimmer _indexOfSpinLock = new(); + + internal bool _isItemFound; + internal int _participants; + internal bool _returned; + + public void AddParticipant() + { + _participantsSpinLock.Enter(); + _participants++; + _participantsSpinLock.Exit(); + } + + public void AddParticipants(int newParticipants) + { + _participantsSpinLock.Enter(); + _participants += newParticipants; + _participantsSpinLock.Exit(); + } + + public void Lock() + { + _indexOfSpinLock.Enter(); + } + + public void RemoveParticipants(int participantsToRemove) + { + if (_participants < participantsToRemove) + { + ThrowNotEnoughParticipants(); + } + + _participantsSpinLock.Enter(); + _participants -= participantsToRemove; + _participantsSpinLock.Exit(); + } + + public void RemoveParticipant() + { + if (_participants == 0) + { + ThrowNoMoreParticipants(); + } + + _participantsSpinLock.Enter(); + _participants--; + _participantsSpinLock.Exit(); + } + + public void SignalAndWait() + { + if (_participants == 0) + { + ThrowNoMoreParticipants(); + } + + _participantsSpinLock.Enter(); + _participants--; + _participantsSpinLock.Exit(); + + while (_participants > 0) + { + _participantsSpinWait.SpinOnce(); + } + } + + public void Unlock() + { + _indexOfSpinLock.Exit(); + } + + private static void ThrowNotEnoughParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are not enough participants."); + private static void ThrowNoMoreParticipants() => throw new InvalidOperationException("Participant cannot be removed, because there are no more participants."); + + public long FoundBlockIndex; + public long FoundItemIndex; + public Exception? Exception; + public int Participants => _participants; + + public IndexOfSynchronizationContext() + { + } + + public IndexOfSynchronizationContext(int participantCount) + { + _participants = participantCount; + } + + public void SetItemFound() + { + _isItemFound = true; + } + + public bool IsItemFound => _isItemFound; + + public void Dispose() + { + if (!_returned) + { + _returned = true; + IndexOfSynchronizationContextPool.Shared.Return(this); + } + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs new file mode 100644 index 0000000..935b71d --- /dev/null +++ b/Recyclable.Collections/Parallel/IndexOfSynchronizationContextPool.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Parallel +{ + public static class IndexOfSynchronizationContextPool + { + private static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static IndexOfSynchronizationContext GetWithOneParticipant() + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + var context = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + if (context._isItemFound) + { + context._isItemFound = false; + } + + switch (context._participants) + { + case > 1: + context.RemoveParticipants(context._participants - 1); + break; + + case 0: + context.AddParticipant(); + break; + } + + if (context.Exception != null) + { + context.Exception = null; + } + + if (context.FoundItemIndex >= 0) + { + context.FoundItemIndex = RecyclableDefaults.ItemNotFoundIndexLong; + } + + context._returned = false; + return context; + } + } +} \ No newline at end of file diff --git a/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs new file mode 100644 index 0000000..8d53a8d --- /dev/null +++ b/Recyclable.Collections/Parallel/ManualResetEventSlimmer.cs @@ -0,0 +1,38 @@ +namespace Recyclable.Collections.Parallel +{ + public sealed class ManualResetEventSlimmer + { + internal bool _isSet; + public bool IsSet => _isSet; + + internal bool _returned; + + public ManualResetEventSlimmer() + { + } + + public ManualResetEventSlimmer(bool initialState) + { + _isSet = initialState; + } + + public void Set() + { + _isSet = true; + } + + public void Reset() + { + _isSet = false; + } + + public void Dispose() + { + if (!_returned) + { + _returned = true; + ManualResetEventSlimmerPool.Shared.Return(this); + } + } + } +} diff --git a/Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs b/Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs new file mode 100644 index 0000000..5cee73e --- /dev/null +++ b/Recyclable.Collections/Parallel/ManualResetEventSlimmerPool.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.ObjectPool; + +namespace Recyclable.Collections.Parallel +{ + public static class ManualResetEventSlimmerPool + { + public static readonly SpinLock _updateLock = new(false); + public static readonly ObjectPool Shared = new DefaultObjectPool(new DefaultPooledObjectPolicy()); + + public static ManualResetEventSlimmer Create(bool initialState) + { + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + ManualResetEventSlimmer result = Shared.Get(); + if (lockTaken) + { + _updateLock.Exit(false); + } + + result._returned = false; + if (result._isSet != initialState) + { + result._isSet = initialState; + } + + return result; + } + } +} diff --git a/Recyclable.Collections/Parallel/SpinLockSlimmer.cs b/Recyclable.Collections/Parallel/SpinLockSlimmer.cs new file mode 100644 index 0000000..a603b25 --- /dev/null +++ b/Recyclable.Collections/Parallel/SpinLockSlimmer.cs @@ -0,0 +1,26 @@ +namespace Recyclable.Collections.Parallel +{ + public struct SpinLockSlimmer + { + private volatile int _locked; + + public void Enter() + { + if (Interlocked.CompareExchange(ref _locked, 1, 0) == 0) + { + return; + } + + SpinWait waiter = new(); + while (Interlocked.CompareExchange(ref _locked, 1, 0) == 1) + { + waiter.SpinOnce(); + } + } + + public void Exit() + { + _locked = 0; + } + } +} diff --git a/Recyclable.Collections/Pools/OneSizeArrayPool.cs b/Recyclable.Collections/Pools/OneSizeArrayPool.cs new file mode 100644 index 0000000..11859c5 --- /dev/null +++ b/Recyclable.Collections/Pools/OneSizeArrayPool.cs @@ -0,0 +1,86 @@ +using System.Buffers; + +namespace Recyclable.Collections.Pools +{ + public class OneSizeArrayPool : ArrayPool + { + private readonly int _blockSize; + private readonly int _minBlockCount; + private int _blockCount; + private MemoryBucket? _currentBucket; + private readonly SpinLock _updateLock = new(false); + + public OneSizeArrayPool(int blockSize, int minBlockCount) + { + _blockSize = blockSize; + _minBlockCount = minBlockCount; + } + + public override T[] Rent(int minimumLength) + { + if (minimumLength != _blockSize) + { + ThrowWrongRentedArraySizeException(minimumLength, _blockSize); + } + + MemoryBucket? bucket; + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + if (_currentBucket != null) + { + _blockCount--; + bucket = _currentBucket; + _currentBucket = _currentBucket.NextBucket; + } + else + { + bucket = null; + } + + if (lockTaken) + { + _updateLock.Exit(false); + } + + return bucket != null ? bucket.Memory : new T[_blockSize]; + } + + public override void Return(T[] array, bool clearArray = false) + { + if (array.Length != _blockSize) + { + ThrowWrongReturnedArraySizeException(array.Length, _blockSize); + } + + MemoryBucket bucket = new(array); + if (clearArray) + { + Array.Clear(array, 0, array.Length); + } + + bool lockTaken = false; + _updateLock.Enter(ref lockTaken); + if (_blockCount < _minBlockCount) + { + bucket.NextBucket = _currentBucket; + _currentBucket = bucket; + _blockCount++; + } + + if (lockTaken) + { + _updateLock.Exit(false); + } + } + + private static void ThrowWrongReturnedArraySizeException(int returnedLength, int blockSize) + { + throw new InvalidOperationException($"The array size {returnedLength} doesn't match the size {blockSize} of the pool buffer and cannot be returned to this pool."); + } + + private static void ThrowWrongRentedArraySizeException(int requestedSize, int blockSize) + { + throw new InvalidOperationException($"The requested size {requestedSize} doesn't match the size {blockSize} of the pool buffer and cannot be taken from this pool."); + } + } +} diff --git a/Recyclable.Collections/Pools/RecyclableArrayPool.cs b/Recyclable.Collections/Pools/RecyclableArrayPool.cs new file mode 100644 index 0000000..3e44463 --- /dev/null +++ b/Recyclable.Collections/Pools/RecyclableArrayPool.cs @@ -0,0 +1,35 @@ +using System.Buffers; +using System.Collections.Concurrent; + +namespace Recyclable.Collections.Pools +{ + public static class RecyclableArrayPool + { + public static readonly OneSizeArrayPool Null = new(0, 0); + + private static readonly ConcurrentDictionary> _pools = new(); + + public static ArrayPool Shared(int blockSize) + { + //lock (_lock) + { + if (!_pools.TryGetValue(blockSize, out ArrayPool? pool)) + { + pool = new OneSizeArrayPool(blockSize, 100); + pool = _pools.TryAdd(blockSize, pool) ? pool : _pools[blockSize]; + } + + return pool; + } + + //_lock.Wait(); + //try + //{ + //} + //finally + //{ + // _lock.Release(); + //} + } + } +} diff --git a/Recyclable.Collections/Recyclable.Collections.csproj b/Recyclable.Collections/Recyclable.Collections.csproj index 99a142c..33db06d 100644 --- a/Recyclable.Collections/Recyclable.Collections.csproj +++ b/Recyclable.Collections/Recyclable.Collections.csproj @@ -31,4 +31,9 @@ PreserveNewest + + + + + diff --git a/Recyclable.Collections/RecyclableDefaults.cs b/Recyclable.Collections/RecyclableDefaults.cs index e726503..1fa29b9 100644 --- a/Recyclable.Collections/RecyclableDefaults.cs +++ b/Recyclable.Collections/RecyclableDefaults.cs @@ -1,7 +1,13 @@ namespace Recyclable.Collections { - public class RecyclableDefaults + public static class RecyclableDefaults { - public const int BlockSize = 10_240; + public const int BlockSize = 16_384; + public const int Capacity = 1; + public const long MinItemsCountForParallelization = 850_000; + public const int MinPooledArrayLength = 128; + public const int MaxPooledBlockSize = 2_147_483_591; + public const long ItemNotFoundIndexLong = -1L; + public const int ItemNotFoundIndex = -1; } } diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 3389241..19937d3 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -1,154 +1,493 @@ -using System.Collections; +using System.Buffers; +using System.Collections; using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableList : IDisposable, IList + public class RecyclableList : IList, IDisposable { - private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; + private static readonly ArrayPool _arrayPool = ArrayPool.Create(); - private readonly List _arrays = new(); - private bool _disposedValue; + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; - public RecyclableList(int blockSize = RecyclableDefaults.BlockSize) + protected T[] _memoryBlock; + + private static void ThrowArgumentOutOfRangeException(in string message) { - BlockSize = blockSize; + throw new ArgumentOutOfRangeException(message, (Exception?)null); } - public RecyclableList(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static T[] Resize(in T[]? source, int newSize) { - BlockSize = blockSize; - foreach (var item in source) + ArrayPool arrayPool = _arrayPool; + T[] newMemoryBlock = newSize >= RecyclableDefaults.MinPooledArrayLength + ? arrayPool.Rent(newSize) + : new T[newSize]; + + if (source?.Length > 0) { - Add(item); + Array.Copy(source!, 0, newMemoryBlock, 0, source!.Length); + //var sourceSpan = new Memory(source); + //var newArraySpan = new Memory(newMemoryBlock); + + //sourceSpan.CopyTo(newArraySpan); + + if (source!.Length >= RecyclableDefaults.MinPooledArrayLength) + { + arrayPool.Return(source!, NeedsClearing); + } + } + + return newMemoryBlock; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static int EnsureCapacity(RecyclableList list, int requestedCapacity) + { + int newCapacity; + switch (list._capacity > 0) + { + case true: + newCapacity = list._capacity; + while (newCapacity < requestedCapacity) + { + newCapacity <<= 1; + } + + break; + + case false: + newCapacity = requestedCapacity; + break; + } + + list._memoryBlock = Resize(list._memoryBlock, newCapacity); + newCapacity = list._memoryBlock.Length; + list._capacity = newCapacity; + return newCapacity; + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList() +#pragma warning restore CS8618 + { + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(int initialCapacity) +#pragma warning restore CS8618 + { + if (initialCapacity > 0) + { + _memoryBlock = Resize(_memoryBlock, initialCapacity); + _capacity = _memoryBlock.Length; } } - public int BlockSize { get; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetItem(List arrays, in int blockSize, in long index, in T value) - => arrays[(int)(index / blockSize)][index % blockSize] = value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T GetItem(List arrays, in int blockSize, in long index) - => arrays[(int)(index / blockSize)][index % blockSize]; - - public T this[long index] +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(RecyclableList source) +#pragma warning restore CS8618 { - get => GetItem(_arrays, BlockSize, index); - set => SetItem(_arrays, BlockSize, index, value); + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(in T[] source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(List source) +#pragma warning restore CS8618 + { + AddRange(source); + } + +#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added + public RecyclableList(IList source) +#pragma warning restore CS8618 + { + AddRange(source); + } + + public RecyclableList(IEnumerable source, int initialCapacity = RecyclableDefaults.Capacity) + { + _memoryBlock = Resize(_memoryBlock, initialCapacity); + _capacity = _memoryBlock.Length; + AddRange(source); } public T this[int index] { - get => GetItem(_arrays, BlockSize, index); - set => SetItem(_arrays, BlockSize, index, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _memoryBlock[index]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _memoryBlock[index] = value; + } + + protected int _capacity; + public int Capacity + { + get => _capacity; + set + { + if (_capacity != value) + { + _memoryBlock = Resize(_memoryBlock, value); + _capacity = _memoryBlock.Length; + } + } + } + + protected int _count; + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _count = value; } - public long Capacity { get; set; } - public int Count => (int)LongCount; - public long LongCount { get; set; } public bool IsReadOnly { get; } = false; - public int BlockCount => _arrays.Count; [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(T item) { - if (LongCount == Capacity) + int requiredCapacity = _count + 1; + if (_capacity < requiredCapacity) { - T[] newArray = BlockSize.RentArrayFromPool(); - _arrays.Add(newArray); - Capacity += BlockSize; + _ = EnsureCapacity(this, requiredCapacity); } - this[LongCount] = item; - LongCount++; + _memoryBlock[_count++] = item; } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + int sourceItemsCount = items.Length; + int targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); + Span itemsSpan = new(items); + itemsSpan.CopyTo(targetSpan); + _count = targetCapacity; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + var sourceItemsCount = items.Count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + items.CopyTo(_memoryBlock, _count); + _count = targetCapacity; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + var sourceItemsCount = items.Count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + items.CopyTo(_memoryBlock, _count); + _count = targetCapacity; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + var sourceItemsCount = items._count; + var targetCapacity = _count + sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + var targetSpan = new Span(_memoryBlock, _count, sourceItemsCount); + Span itemsSpan = new(items._memoryBlock, 0, sourceItemsCount); + itemsSpan.CopyTo(targetSpan); + _count = targetCapacity; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(RecyclableLongList items) + { + if (items.LongCount == 0) + { + return; + } + + var sourceItemsCount = items.LongCount; + if (sourceItemsCount > int.MaxValue) + { + ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableList)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); + } + + if (_count + sourceItemsCount > int.MaxValue) + { + ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableList)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableLongList)}, instead"); + } + + int targetCapacity = _count + (int)sourceItemsCount; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + Span targetSpan = new Span(_memoryBlock)[_count..], + itemsSpan; + + int blockIndex, + sourceBlockSize = items.BlockSize, + lastBlockIndex = (int)(sourceItemsCount / sourceBlockSize) - 1; + + for (blockIndex = 0; blockIndex <= lastBlockIndex; blockIndex++) + { + itemsSpan = new(items.AsArray[blockIndex], 0, sourceBlockSize); + itemsSpan.CopyTo(targetSpan); + targetSpan = targetSpan[sourceBlockSize..]; + } + + if (blockIndex == 0) + { + itemsSpan = new(items.AsArray[blockIndex], 0, (int)sourceItemsCount); + itemsSpan.CopyTo(targetSpan); + } + else if (blockIndex <= items.LastBlockWithData) + { + itemsSpan = new(items.AsArray[blockIndex], 0, items.NextItemIndex); + itemsSpan.CopyTo(targetSpan); + } + + _count = targetCapacity; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) + { + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + int targetItemIdx = _count; + Span memorySpan; + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + _ = EnsureCapacity(this, targetItemIdx + requiredAdditionalCapacity); + memorySpan = new(_memoryBlock); + foreach (var item in source) + { + memorySpan[targetItemIdx++] = item; + } + + _count = targetItemIdx; + return; + } + + int i; + using var enumerator = source.GetEnumerator(); + + int capacity = _capacity; + memorySpan = new(_memoryBlock); + if (enumerator.MoveNext()) + { + int available = capacity - targetItemIdx; + do + { + if (targetItemIdx + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memorySpan = new(_memoryBlock); + available = capacity - targetItemIdx; + } + + for (i = 0; i < available; i++) + { + memorySpan[targetItemIdx++] = enumerator.Current; + if (!enumerator.MoveNext()) + { + break; + } + } + } + while (i >= available); + } + + _count = targetItemIdx; + } + + public T[] AsArray => _memoryBlock; + + [MethodImpl(MethodImplOptions.NoInlining)] public void Clear() { - try + if (NeedsClearing) { - // Remove in reversed order for performance savings - while (_arrays.Count > 0) - { - try - { - RemoveBlock(_arrays.Count - 1); - } - catch (Exception) - { - // We want to try returning as many arrays, as possible, before - // the list is cleared. - } - } - } - finally - { - LongCount = 0; - _arrays.Clear(); + Array.Clear(_memoryBlock, 0, _count); } + + _count = 0; } - public bool Contains(T item) => _arrays.Contains(item); - - public void CopyTo(T[] array, int arrayIndex) => _arrays.CopyTo(0, BlockSize, (int)LongCount % BlockSize, array, arrayIndex); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerator GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator(); + public bool Contains(T item) => _count > 0 && Array.IndexOf(_memoryBlock, item, 0, _count) >= 0; - public int IndexOf(T item) => (int)_arrays.LongIndexOf(BlockSize, item, _equalityComparer); + [MethodImpl(MethodImplOptions.NoInlining)] + public void CopyTo(T[] array, int arrayIndex) => Array.Copy(_memoryBlock, 0, array, arrayIndex, _count); - public void Insert(int index, T item) => throw new NotSupportedException(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public long LongIndexOf(T item) => _arrays.LongIndexOf(BlockSize, item, _equalityComparer); - - public bool Remove(T item) => throw new NotSupportedException(); - - public void RemoveAt(int index) => throw new NotSupportedException(); - - IEnumerator IEnumerable.GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator(); - - protected virtual void Dispose(bool disposing) + protected static IEnumerable Enumerate(RecyclableList list) { - if (!_disposedValue) + int count = list._count; + var memory = list._memoryBlock; + for (var i = 0; i < count; i++) { - if (disposing) - { - Clear(); - } - - _disposedValue = true; + yield return memory[i]; } } - // override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~RecyclableList() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(T itemToFind) => _count > 0 ? Array.IndexOf(_memoryBlock, itemToFind, 0, _count) : RecyclableDefaults.ItemNotFoundIndex; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Insert(int index, T item) + { + int oldCount = _count; + int requestedCapacity = oldCount + 1; + if (_capacity < requestedCapacity) + { + _ = EnsureCapacity(this, requestedCapacity); + } + + if (oldCount > 0) + { + var sourceSpan = new Span(_memoryBlock, index, oldCount); + var targetSpan = new Span(_memoryBlock, index + 1, oldCount); + sourceSpan.CopyTo(targetSpan); + } + + _memoryBlock[index] = item; + _count++; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T itemToRemove) + { + var index = IndexOf(itemToRemove); + if (index >= 0) + { + _count--; + if (index < _count) + { + Array.Copy(_memoryBlock, index + 1, _memoryBlock, index, _count - index); + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlock[_count] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _count) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range"); + } + + _count--; + if (index < _count) + { + Array.Copy(_memoryBlock, index + 1, _memoryBlock, index, _count - index); + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlock[_count] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IEnumerator GetEnumerator() => Enumerate(this).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IEnumerator IEnumerable.GetEnumerator() => Enumerate(this).GetEnumerator(); public void Dispose() { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - public void RemoveBlock(int index) - { - try + if (_capacity > 0) { - _arrays[index].ReturnToPool(); - _arrays.RemoveAt(index); - } - finally - { - Capacity -= BlockSize; - LongCount -= BlockSize; + Clear(); + _capacity = 0; + _memoryBlock.ReturnToPool(_arrayPool, NeedsClearing); + GC.SuppressFinalize(this); } } } diff --git a/Recyclable.Collections/RecyclableListExtensions.cs b/Recyclable.Collections/RecyclableListExtensions.cs deleted file mode 100644 index 3a4e145..0000000 --- a/Recyclable.Collections/RecyclableListExtensions.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace Recyclable.Collections -{ - public static class RecyclableListExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyTo(this List arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) - { - Span arrayMemory = destinationArray.AsSpan(); - arrayMemory = arrayMemory[destinationArrayIndex..]; - int startingArrayIndex = (int)(startingIndex / blockSize); - startingIndex %= blockSize; - if (arrays.Count > 0) - { - ReadOnlySpan sourceMemory = arrays[0].AsSpan(); - var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); - if (arrays.Count == 1) - { - maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); - } - - sourceMemory = sourceMemory[(int)startingIndex..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - arrayMemory = arrayMemory[maxToCopy..]; - } - - for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++) - { - ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); - var maxToCopy = Math.Min(blockSize, arrayMemory.Length); - sourceMemory = sourceMemory[..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - arrayMemory = arrayMemory[maxToCopy..]; - } - - if (arrays.Count > 1 && arrayMemory.Length > 0) - { - ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); - var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); - sourceMemory = sourceMemory[..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this List arrays, T item) => arrays.Any(x => x.Contains(item)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this List arrays, int chunkSize, long totalCount) - { - long currentCount = 0; - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) - { - var array = arrays[arrayIdx]; - currentCount += chunkSize; - switch (currentCount < totalCount) - { - case true: - for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - - case false: - var partialCount = (int)(totalCount % chunkSize); - int maxCount = partialCount > 0 ? partialCount : chunkSize; - for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T LimitTo(this T value, T maxValue, IComparer comparer) - => comparer.Compare(value, maxValue) > 0 ? maxValue : value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this List arrays, int blockSize, T item, IEqualityComparer comparer) - { - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) - { - var array = arrays[arrayIdx]; - Span arrayMemory = array.AsSpan(); - for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) - { - if (comparer.Equals(arrayMemory[memoryIdx], item)) - { - return (arrayIdx * (long)blockSize) + memoryIdx; - } - } - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int ToArrayIndex(this long index, int blockSize) => (int)(index / blockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long ToItemIndex(this long index, int blockSize) => index % blockSize; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IEnumerable values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize) => ArrayPool.Shared.Rent(minSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array) => ArrayPool.Shared.Return(array); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void SetItem(this List arrays, in int blockSize, in long index, in T value) - => arrays[(int)(index / blockSize)][index % blockSize] = value; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T GetItem(this List arrays, in int blockSize, in long index) - => arrays[(int)(index / blockSize)][index % blockSize]; - - } -} diff --git a/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs new file mode 100644 index 0000000..21826af --- /dev/null +++ b/Recyclable.Collections/RecyclableLongList.IndexOfHelpers.cs @@ -0,0 +1,351 @@ +// #define MAX_BLOCKS_FOR_TASK_2 +// #define MAX_BLOCKS_FOR_TASK_3 +#define MAX_BLOCKS_FOR_TASK_4 +// #define MAX_BLOCKS_FOR_TASK_5 +// #define MAX_BLOCKS_FOR_TASK_6 +// #define MAX_BLOCKS_FOR_TASK_7 +// #define MAX_BLOCKS_FOR_TASK_8 + +using System.Runtime.CompilerServices; +using Recyclable.Collections.Parallel; + +namespace Recyclable.Collections +{ + public partial class RecyclableLongList + { + public static class IndexOfHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static void IndexOfInConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + { + try + { + if (context._isItemFound && blockIndex >= context.FoundBlockIndex) + { + return; + } + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + index); + return; + } + else if (context._isItemFound) + { + return; + } + +#if MAX_BLOCKS_FOR_TASK_2 || MAX_BLOCKS_FOR_TASK_3 || MAX_BLOCKS_FOR_TASK_4 || MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 1], itemToFind, 0, list._blockSize); + if (index >= 0) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 1, ((long)(blockIndex + 1) << list._blockSizePow2BitShift) + index); + return; + } + else if (context._isItemFound) + { + return; + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_3 || MAX_BLOCKS_FOR_TASK_4 || MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 2], itemToFind, 0, list._blockSize); + if (index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 2, ((long)(blockIndex + 2) << list._blockSizePow2BitShift) + index); + return; + } + else if (context._isItemFound) + { + return; + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_4 || MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 3], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 3, ((long)(blockIndex + 3) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_5 || MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 4], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 4, ((long)(blockIndex + 4) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_6 || MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 5], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 5, ((long)(blockIndex + 5) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_7 || MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 6], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 6, ((long)(blockIndex + 6) << list._blockSizePow2BitShift) + index); + } + +#endif + +#if MAX_BLOCKS_FOR_TASK_8 + + index = Array.IndexOf(list._memoryBlocks[blockIndex + 7], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0 && (!context._isItemFound || index < context.FoundItemIndex)) + { + ExchangeFoundItemIndexIfLower(context, blockIndex + 7, ((long)(blockIndex + 7) << list._blockSizePow2BitShift) + index); + } + +#endif + } + catch (Exception e) + { + e.Data.Add("list", list); + e.Data.Add("itemToFind", itemToFind); + e.Data.Add("blockIndex", blockIndex); + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.RemoveParticipant(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ExchangeFoundItemIndexIfLower(IndexOfSynchronizationContext context, int blockIndex, long newItemIndex) + { + context.Lock(); + + if (newItemIndex < context.FoundItemIndex || context.FoundItemIndex == RecyclableDefaults.ItemNotFoundIndexLong) + { + context.FoundBlockIndex = blockIndex; + context.FoundItemIndex = newItemIndex; + } + + context._isItemFound = true; + context.Unlock(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static void IndexOfIn1Block(IndexOfSynchronizationContext context, RecyclableLongList list, int blockIndex, T itemToFind) + { + try + { + if (context._isItemFound) + { + return; + } + + long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize); + if (!context._isItemFound && index >= 0) + { + ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + index); + } + } + catch (Exception e) + { + _ = Interlocked.Exchange(ref context.Exception, e); + } + finally + { + context.RemoveParticipant(); + } + } + +#if MAX_BLOCKS_FOR_TASK_2 + private const int BlocksForEachTask = 2; +#endif +#if MAX_BLOCKS_FOR_TASK_3 + private const int BlocksForEachTask = 3; +#endif +#if MAX_BLOCKS_FOR_TASK_4 + private const int BlocksForEachTask = 4; +#endif +#if MAX_BLOCKS_FOR_TASK_5 + private const int BlocksForEachTask = 5; +#endif +#if MAX_BLOCKS_FOR_TASK_6 + private const int BlocksForEachTask = 6; +#endif +#if MAX_BLOCKS_FOR_TASK_7 + private const int BlocksForEachTask = 7; +#endif +#if MAX_BLOCKS_FOR_TASK_8 + private const int BlocksForEachTask = 8; +#endif + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long IndexOfParallel(RecyclableLongList list, T item) + { + // & WAS SLOWER + //long itemsCount = Array.IndexOf(list._memoryBlocks[0], item, 0, (int)Math.Min(list._blockSize, list._longCount)); + //if (itemsCount >= 0) + //{ + // return itemsCount; + //} + + //using var context = new ParallelSynchronizationContext(1); + IndexOfSynchronizationContext context = IndexOfSynchronizationContextPool.GetWithOneParticipant(); + // (long)(_longCount * 0.329) => most efficient step, based on benchmarks + //Iterate(this, context, item); + + // & WAS SLOWER without + int blockIndex = 2; + // & WAS SLOWER + //long itemsCount = list._longCount - list._blockSize; + // & WAS SLOWER + // int blockSize = _blockSize; + // & WAS SLOWER + // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + //long blockSize = list._blockSize << 1; + int lastBlockWithData = list._lastBlockWithData - BlocksForEachTask; + while (blockIndex < lastBlockWithData) + { + // At this point itemIndex is limited to blockSize range - int. + + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + // Task.Factory.StartNew(() => + context.AddParticipant(); + var startingBlock = blockIndex; + _ = Task.Run(() => IndexOfInConsecutiveBlocks(context, list, startingBlock, item)); + + // & WAS SLOWER + // ScheduleIndexOfTask(context, list, blockIndex, item); + if (context._isItemFound) + { + break; + } + + blockIndex += BlocksForEachTask; + } + + if (!context._isItemFound) + { + while (blockIndex < list._lastBlockWithData) + { + // & WAS SLOWER + // _ = ThreadPool.QueueUserWorkItem((_) => + // Task.Factory.StartNew(() => + var startingBlock = blockIndex; + context.AddParticipant(); + _ = Task.Run(() => IndexOfIn1Block(context, list, startingBlock, item)); + + // & WAS SLOWER + // ScheduleIndexOfTaskSingle(context, list, blockIndex, item); + if (context._isItemFound) + { + break; + } + + blockIndex++; + } + } + + if (!context._isItemFound) + { + int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize); + if (!context._isItemFound && itemIndex >= 0) + { + ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex); + } + } + + context.SignalAndWait(); + if (context.Exception == null) + { + try + { + return context.FoundItemIndex; + } + finally + { + context.Dispose(); + } + } + + try + { + context.Exception.CaptureAndRethrow(); + } + finally + { + context.Dispose(); + } + + return RecyclableDefaults.ItemNotFoundIndexLong; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + public static long IndexOfSequential(RecyclableLongList list, in T item) //, in T[][] memoryBlocks, int lastBlockIndex, int blockSize, int nextItemIndex) + { + int itemIndex; + // & WAS SLOWER + //int lastBlockWithData = list._lastBlockWithData; + Span memoryBlocksSpan = new(list._memoryBlocks, 0, list._lastBlockWithData + 1); + int blockIndex; + for (blockIndex = 1; blockIndex < list._lastBlockWithData; blockIndex += 4) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)blockIndex << list._blockSizePow2BitShift); + } + + if (blockIndex + 1 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 1], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 1)<< list._blockSizePow2BitShift); + } + + if (blockIndex + 2 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 2], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 2)<< list._blockSizePow2BitShift); + } + + if (blockIndex + 3 < list._lastBlockWithData) + { + itemIndex = Array.IndexOf(memoryBlocksSpan[blockIndex + 3], item, 0, list._blockSize); + if (itemIndex >= 0) + { + return itemIndex + ((long)(blockIndex + 3)<< list._blockSizePow2BitShift); + } + } + } + } + } + + itemIndex = list._nextItemIndex != 0 + ? Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._nextItemIndex) + : Array.IndexOf(memoryBlocksSpan[list._lastBlockWithData], item, 0, list._blockSize); + + return itemIndex >= 0 ? itemIndex + ((long)list._lastBlockWithData << list._blockSizePow2BitShift) : RecyclableDefaults.ItemNotFoundIndexLong; + } + + public static int Min(int a, int b, int c) => a < b ? a < c ? a : c : b < c ? b : c; + } + } +} diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs new file mode 100644 index 0000000..e1ac32c --- /dev/null +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -0,0 +1,1094 @@ +using System.Buffers; +using System.Collections; +using System.Numerics; +using System.Runtime.CompilerServices; +using Recyclable.Collections.Pools; + +[assembly: InternalsVisibleTo("Recyclable.Collections.Benchmarks")] +[assembly: InternalsVisibleTo("Recyclable.CollectionsTests")] + +namespace Recyclable.Collections +{ + public partial class RecyclableLongList : IDisposable, IList + { + private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); + private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); + private static readonly T[][] _emptyMemoryBlocksArray = new T[0][]; + private static readonly T[] _emptyBlockArray = new T[0]; + + internal int _blockSize; + internal byte _blockSizePow2BitShift; + internal int _blockSizeMinus1; + private int _nextItemBlockIndex; + internal int _nextItemIndex; + private int _reservedBlockCount; + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + protected ArrayPool _memoryBlocksPool; + protected ArrayPool _blockArrayPool; + internal T[][] _memoryBlocks; + + private long _capacity; + public long Capacity + { + get => _capacity; + protected set => _capacity = value; + } + + public int Count => checked((int)_longCount); + public bool IsReadOnly { get; } + private int _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; + public int LastBlockWithData => _lastBlockWithData; + + internal long _longCount; + + public long LongCount + { + get => _longCount; + set => _longCount = value; + } + + public int ReservedBlockCount => _reservedBlockCount; + public int BlockSize => _blockSize; + public int BlockSizeMinus1 => _blockSizeMinus1; + public byte BlockSizePow2BitShift => _blockSizePow2BitShift; + public int NextItemBlockIndex => _nextItemBlockIndex; + public int NextItemIndex => _nextItemIndex; + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void CopyItems(RecyclableLongList list, long index) + { + T[][] memoryBlocks = list._memoryBlocks; + int nextItemIndex = list._nextItemIndex; + int blockSize = list._blockSize; + int sourceBlockIndex = (int)((index + 1) >> list._blockSizePow2BitShift); + int sourceItemIndex = (int)((index + 1) & list._blockSizeMinus1); + + int targetBlockIndex = (int)(index >> list._blockSizePow2BitShift); + int targetItemIndex = (int)(index & list._blockSizeMinus1); + + int lastTakenBlockIndex = list._lastBlockWithData; + while (sourceBlockIndex < lastTakenBlockIndex || (sourceBlockIndex == lastTakenBlockIndex && (sourceItemIndex < nextItemIndex || sourceBlockIndex != list._nextItemBlockIndex))) + { + int toCopy = sourceBlockIndex < lastTakenBlockIndex || nextItemIndex == 0 + ? blockSize - (sourceItemIndex >= targetItemIndex ? sourceItemIndex : targetItemIndex) + : Math.Min(nextItemIndex, blockSize - targetItemIndex); + + Array.Copy(memoryBlocks[sourceBlockIndex], sourceItemIndex, memoryBlocks[targetBlockIndex], targetItemIndex, toCopy); + + // We didn't have enough room in the target array block. There are still items in the source array block to copy. + if (sourceItemIndex + toCopy < blockSize) + { + sourceItemIndex += toCopy; + targetBlockIndex++; + targetItemIndex = 0; + } + // We copied all the source items in the current array block. But have we filled the target? + else + { + sourceItemIndex = 0; + sourceBlockIndex++; + if (targetItemIndex + toCopy < blockSize) + { + targetItemIndex += toCopy; + } + else + { + targetBlockIndex++; + targetItemIndex = 0; + } + } + } + } + + private static void ThrowArgumentOutOfRangeException(in string message) => throw new ArgumentOutOfRangeException("index", message); + + protected void AddRangeEnumerated(IEnumerable source, int growByCount) + { + long i, copied = _longCount; + int blockSize = _blockSize, targetItemIdx = _nextItemIndex, targetBlockIdx = _nextItemBlockIndex; + + using IEnumerator enumerator = source.GetEnumerator(); + if (!enumerator.MoveNext()) + { + return; + } + + long capacity = _capacity; + long available = capacity - copied; + + Span memoryBlocksSpan = new(_memoryBlocks, 0, ReservedBlockCount); + Span blockArraySpan; + var memoryBlocksCount = memoryBlocksSpan.Length; + while (true) + { + if (copied + growByCount > capacity) + { + capacity = EnsureCapacity(this, capacity + growByCount); + memoryBlocksCount = _reservedBlockCount; + if (blockSize != _blockSize) + { + blockSize = _blockSize; + } + + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + available = capacity - copied; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + for (i = 1; i <= available; i++) + { + blockArraySpan[targetItemIdx++] = enumerator.Current; + + if (!enumerator.MoveNext()) + { + _lastBlockWithData = targetBlockIdx; + _longCount += copied + i - _longCount; + if (targetItemIdx < blockSize) + { + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + } + else + { + _nextItemBlockIndex = targetBlockIdx + 1; + _nextItemIndex = 0; + } + + return; + } + + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + copied += available; + } + } + + protected void AddRangeWithKnownCount(IEnumerable source, int requiredAdditionalCapacity) + { + long copied = _longCount; + int blockSize = _blockSize; + int targetItemIdx = _nextItemIndex; + int targetBlockIdx = _nextItemBlockIndex; + + Span memoryBlocksSpan; + Span blockArraySpan; + long requiredCapacity = copied + requiredAdditionalCapacity; + if (_capacity < requiredCapacity) + { + _ = EnsureCapacity(this, requiredCapacity); + blockSize = _blockSize; + } + + var memoryBlocksCount = ReservedBlockCount; + memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlocksCount); + blockArraySpan = new(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + foreach (var item in source) + { + blockArraySpan[targetItemIdx++] = item; + if (targetItemIdx == blockSize) + { + targetBlockIdx++; + targetItemIdx = 0; + if (targetBlockIdx == memoryBlocksCount) + { + break; + } + + blockArraySpan = new Span(memoryBlocksSpan[targetBlockIdx], 0, blockSize); + } + } + + _longCount = requiredCapacity; + _nextItemBlockIndex = targetBlockIdx; + _nextItemIndex = targetItemIdx; + _lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1); + } + + /// + /// Creates a set of new memory buffers, if needed, to allow storing at minimum no. of items. + /// + /// that needs to be resized. + /// Minimal requested block size. It MUST be rounded to the power of 2, see remarks. + /// Pre-calculated bit shifting value for left & right shift operations against. + /// The minimum no. of items MUST be able to store after . + /// + /// For performance reasons, MUST a power of 2. This simplifies a lot block & item + /// index calculations, i.e. makes them logical operations on bits. + /// + /// This method checks for integral overflow. + /// + /// + /// This method doesn't support downsizing the memory block. As such it doesn't release excessive blocks. This is to for additional performance + /// gain - 1+ operation less for each call. If you need this functionality, you need to implement it on a higher level. + /// + /// The maximum no. of items can store. + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static long Resize(RecyclableLongList list, int minBlockSize, byte minBlockSizePow2Shift, long newCapacity) + { + ArrayPool blockArrayPool = list._blockArrayPool; + int sourceBlockCount = list._reservedBlockCount; + int requiredBlockCount = checked((int)(newCapacity >> minBlockSizePow2Shift) + ((newCapacity & (minBlockSize - 1)) > 0 ? 1 : 0)); + int blockIndex; + + T[][] newMemoryBlocks; + if (requiredBlockCount > (list._memoryBlocks?.Length ?? 0)) + { + // Allocate new memory block for all arrays + newMemoryBlocks = requiredBlockCount >= RecyclableDefaults.MinPooledArrayLength ? list._memoryBlocksPool.Rent(requiredBlockCount) : new T[requiredBlockCount][]; + + // Copy arrays from the old memory block for all arrays + if (sourceBlockCount > 0) + { + Array.Copy(list._memoryBlocks!, newMemoryBlocks, sourceBlockCount); + // We can now return the old memory block for all arrays itself + if (sourceBlockCount >= RecyclableDefaults.MinPooledArrayLength) + { + list._memoryBlocksPool.Return(list._memoryBlocks!, true); + } + } + + list._memoryBlocks = newMemoryBlocks; + } + else + { + newMemoryBlocks = list._memoryBlocks!; + } + + // Allocate arrays for any new blocks + if (requiredBlockCount > sourceBlockCount) + { + if (minBlockSize >= RecyclableDefaults.MinPooledArrayLength) + { + if (sourceBlockCount == 0) + { + minBlockSize = GetEstimatedBlockSize(minBlockSize, blockArrayPool); + } + + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = blockArrayPool.Rent(minBlockSize); + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + else + { + blockIndex = sourceBlockCount; + while (true) + { + newMemoryBlocks[blockIndex] = new T[minBlockSize]; + if (blockIndex + 1 < requiredBlockCount) + { + blockIndex++; + } + else + { + break; + } + } + } + } + + list._reservedBlockCount = requiredBlockCount; + return (long)requiredBlockCount << minBlockSizePow2Shift; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static int GetEstimatedBlockSize(int minBlockSize, ArrayPool blockArrayPool) + { + var array = blockArrayPool.Rent(minBlockSize); + minBlockSize = array.Length; + blockArrayPool.Return(array); + return minBlockSize; + } + + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.NoOptimization)] + // & WAS SLOWER + // [MethodImpl(MethodImplOptions.NoInlining)] + //private static void Iterate(RecyclableLongList list, in ParallelSynchronizationContext context, in T itemToFind) + //{ + // // TODO: Switch to use _blockSize instead of list._longCountIndeexOfStep for simplifications & better performance. + + // // & WAS SLOWER without + // int blockIndex = 0; + + // long itemsCount = list._longCount; + // // & WAS SLOWER + // // int blockSize = _blockSize; + // // & WAS SLOWER + // // while ((blockIndex << list._blockSizePow2BitShift) + itemIndex > step) + // while (itemsCount > list._blockSize) + // { + // // At this point itemIndex is limited to blockSize range - int. + // if (!ScheduleIndexOfTask(context, list, blockIndex, list._blockSize, itemToFind)) + // { + // break; + // } + + // itemsCount -= list._blockSize; + // blockIndex++; + // } + + // _ = ScheduleIndexOfTask(context, list, blockIndex, (int)itemsCount, itemToFind); + //} + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected static void SetupBlockArrayPooling(RecyclableLongList list, int blockSize, ArrayPool? blockArrayPool = null) + { + list._blockSize = blockSize; + list._blockSizePow2BitShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize)); + list._blockArrayPool = blockSize >= RecyclableDefaults.MinPooledArrayLength + ? blockArrayPool ?? RecyclableArrayPool.Shared(blockSize) + : RecyclableArrayPool.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + protected static long EnsureCapacity(RecyclableLongList list, long requestedCapacity) + { + long newCapacity = list._capacity > 0 + ? checked((long)BitOperations.RoundUpToPowerOf2((ulong)requestedCapacity)) + : requestedCapacity; + + int blockSize = list._blockSize; + newCapacity = Resize(list, blockSize, list._blockSizePow2BitShift, newCapacity); + if (blockSize != list._memoryBlocks[0].Length && newCapacity > 0) + { + SetupBlockArrayPooling(list, list._memoryBlocks[0].Length); + list._blockSizeMinus1 = list._blockSize - 1; + } + + list._capacity = newCapacity; + return newCapacity; + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (_blockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + } + +#pragma warning disable CS8618 // It's set by SetupBlockArrayPooling + public RecyclableLongList(IEnumerable source, int minBlockSize = RecyclableDefaults.BlockSize, long? expectedItemsCount = default, ArrayPool? memoryBlocksPool = default, ArrayPool? blockArrayPool = default) +#pragma warning restore CS8618 + { + _memoryBlocksPool = memoryBlocksPool ?? _defaultMemoryBlocksPool; + if (expectedItemsCount > 0) + { + minBlockSize = checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)); + SetupBlockArrayPooling(this, minBlockSize, blockArrayPool); + _capacity = Resize(this, minBlockSize, _blockSizePow2BitShift, expectedItemsCount.Value); + if (minBlockSize != _memoryBlocks![0].Length && _capacity > 0) + { + SetupBlockArrayPooling(this, _memoryBlocks[0].Length, blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + } + else + { + _blockSizeMinus1 = minBlockSize - 1; + } + } + else + { + SetupBlockArrayPooling(this, checked((int)BitOperations.RoundUpToPowerOf2((uint)minBlockSize)), blockArrayPool); + _blockSizeMinus1 = _blockSize - 1; + _memoryBlocks = _emptyMemoryBlocksArray; + } + + AddRange(source); + } + + public T this[long index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[checked((int)(index >> _blockSizePow2BitShift))])[checked((int)(index & _blockSizeMinus1))] = value; + } + + public T this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + set => new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; + } + + public T[][] AsArray { get => _memoryBlocks; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(T item) + { + if (_capacity < _longCount + 1) + { + _ = EnsureCapacity(this, _longCount + 1); + } + + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; + if (_nextItemIndex == _blockSize) + { + if (_nextItemBlockIndex > _lastBlockWithData) + { + _lastBlockWithData++; + } + + _nextItemBlockIndex++; + _nextItemIndex = 0; + } + else if (_nextItemIndex == 1 && _nextItemBlockIndex > _lastBlockWithData) + { + _lastBlockWithData++; + } + + _longCount++; + } + + public void AddRange(in T[] items) + { + if (items.LongLength == 0) + { + return; + } + + long targetCapacity = _longCount + items.LongLength; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (itemsSpan.Length >= targetBlockArraySpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + if (itemsSpan.Length == 0) + { + break; + } + + targetBlockIndex++; + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + if (itemsSpan.Length > 0) + { + itemsSpan.CopyTo(targetBlockArraySpan); + _nextItemBlockIndex = targetBlockIndex; + } + else + { + _nextItemBlockIndex = targetBlockIndex + 1; + } + + _nextItemIndex = itemsSpan.Length; + _longCount = targetCapacity; + _lastBlockWithData = targetBlockIndex; + } + + public void AddRange(RecyclableList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + Span itemsSpan = new(items.AsArray, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + _nextItemIndex = itemsSpan.Length; + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); + } + + public void AddRange(RecyclableLongList items) + { + if (items.LongCount == 0) + { + return; + } + + long itemsCount = items.LongCount, + targetCapacity = _longCount + itemsCount; + + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + sourceBlockIndex = 0, + targetBlockIndex = _nextItemBlockIndex, + toCopy; + + Span sourceMemoryBlocksSpan = new(items._memoryBlocks, 0, items._reservedBlockCount); + Span itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + Span targetMemoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + Span targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + long copiedCount = 0; + while (copiedCount < itemsCount) + { + toCopy = checked((int)Math.Min(itemsCount - copiedCount, itemsSpan.Length)); + if (targetBlockArraySpan.Length < toCopy) + { + toCopy = targetBlockArraySpan.Length; + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + itemsSpan = itemsSpan[toCopy..]; + copiedCount += toCopy; + } + else + { + itemsSpan[..toCopy].CopyTo(targetBlockArraySpan); + targetBlockArraySpan = targetBlockArraySpan[toCopy..]; + itemsSpan = itemsSpan[toCopy..]; + if (itemsSpan.IsEmpty && sourceBlockIndex + 1 < sourceMemoryBlocksSpan.Length) + { + sourceBlockIndex++; + itemsSpan = new(sourceMemoryBlocksSpan[sourceBlockIndex], 0, items._blockSize); + } + + copiedCount += toCopy; + if (targetBlockArraySpan.IsEmpty && copiedCount < itemsCount) + { + targetBlockIndex++; + targetBlockArraySpan = new(targetMemoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + } + } + + _lastBlockWithData = targetBlockIndex; + if ((targetCapacity & _blockSizeMinus1) == 0) + { + _nextItemIndex = 0; + _nextItemBlockIndex = targetBlockIndex + 1; + } + else + { + _nextItemIndex = checked((int)(targetCapacity & _blockSizeMinus1)); + _nextItemBlockIndex = targetBlockIndex; + } + + _longCount = targetCapacity; + } + + public void AddRange(List items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + itemsCount = items.Count, + copied = Math.Min(blockSize - _nextItemIndex, itemsCount); + + Span memoryBlocksSpan = new(_memoryBlocks, 0, _reservedBlockCount); + items.CopyTo(0, memoryBlocksSpan[targetBlockIndex], _nextItemIndex, copied); + if (_nextItemIndex + copied == blockSize) + { + targetBlockIndex++; + _nextItemIndex = 0; + } + else + { + _nextItemIndex += copied; + } + + while (blockSize <= itemsCount - copied) + { + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex++], 0, blockSize); + copied += blockSize; + } + + if ((itemsCount - copied < blockSize) && (itemsCount - copied != 0)) + { + _nextItemIndex = itemsCount - copied; + items.CopyTo(copied, memoryBlocksSpan[targetBlockIndex], 0, _nextItemIndex); + } + + _nextItemBlockIndex = targetBlockIndex; + _longCount = targetCapacity; + _lastBlockWithData = targetBlockIndex - (_nextItemIndex > 0 ? 0 : 1); + } + + public void AddRange(IList items) + { + if (items.Count == 0) + { + return; + } + + long targetCapacity = _longCount + items.Count; + if (_capacity < targetCapacity) + { + _ = EnsureCapacity(this, targetCapacity); + } + + int blockSize = _blockSize, + targetBlockIndex = _nextItemBlockIndex, + memoryBlockCount = _reservedBlockCount; + + // We're better off to temporarily copy it to a fixed array, + // than copying them item by item. We may run into OOMs, though. + T[] itemsBuffer = items.Count >= RecyclableDefaults.MinPooledArrayLength ? _defaultBlockArrayPool.Rent(items.Count) : new T[items.Count]; + try + { + items.CopyTo(itemsBuffer, 0); + + Span itemsSpan = new(itemsBuffer, 0, items.Count); + Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); + Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); + while (targetBlockArraySpan.Length <= itemsSpan.Length) + { + itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); + itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; + targetBlockIndex++; + if (targetBlockIndex == memoryBlockCount) + { + break; + } + + targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); + } + + itemsSpan.CopyTo(targetBlockArraySpan); + _longCount = targetCapacity; + _nextItemBlockIndex = targetBlockIndex; + _nextItemIndex = itemsSpan.Length; + _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); + } + finally + { + if (items.Count >= RecyclableDefaults.MinPooledArrayLength) + { + _defaultBlockArrayPool.Return(itemsBuffer, NeedsClearing); + } + } + } + + public void AddRange(IEnumerable source, int growByCount = RecyclableDefaults.BlockSize) + { + if (source is RecyclableLongList sourceRecyclableLongList) + { + AddRange(sourceRecyclableLongList); + return; + } + + if (source is RecyclableList sourceRecyclableList) + { + AddRange(sourceRecyclableList); + return; + } + + if (source is T[] sourceArray) + { + AddRange(sourceArray); + return; + } + + if (source is List sourceList) + { + AddRange(sourceList); + return; + } + + if (source is IList sourceIList) + { + AddRange(sourceIList); + return; + } + + if (source.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) + { + AddRangeWithKnownCount(source, requiredAdditionalCapacity); + return; + } + + AddRangeEnumerated(source, growByCount); + } + + public void Clear() + { + if (_longCount == 0) + { + return; + } + + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + T[][] memoryBlocks = _memoryBlocks; + int memoryBlocksCount = _reservedBlockCount; + ArrayPool blockArrayPool = _blockArrayPool; + int toRemoveIdx = 0; + while (toRemoveIdx < memoryBlocksCount) + { + blockArrayPool.Return(memoryBlocks[toRemoveIdx++], NeedsClearing); + } + } + + Array.Fill(_memoryBlocks, _emptyBlockArray, 0, _reservedBlockCount); + _capacity = 0; + _reservedBlockCount = 0; + _nextItemBlockIndex = 0; + _nextItemIndex = 0; + _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; + _longCount = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(T item) + { + if (_longCount == 0) + { + return false; + } + + if (_nextItemBlockIndex == 0 || (_nextItemBlockIndex == 1 && _nextItemIndex == 0)) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, checked((int)_longCount)) >= 0; + } + + Span memoryBlocksSpan = new(_memoryBlocks); + int lastBlockIndex = _lastBlockWithData; + for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) + { + if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) + { + return true; + } + } + + return Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex > 0 ? _nextItemIndex : _blockSize) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + + public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int IndexOf(T item) + { + if (_lastBlockWithData == 0) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + } + else if (_longCount == 0) + { + return RecyclableDefaults.ItemNotFoundIndex; + } + + int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + if (itemIndex >= 0) + { + return itemIndex; + } + else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) + { + return (int)IndexOfHelpers.IndexOfSequential(this, item); + } + else + { + // & WAS SLOWER + // long toSearch = _longCount - _blockSize; + if (_longCount > _blockSize) + { + itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); + return itemIndex >= 0 ? _blockSize + itemIndex : (int)IndexOfHelpers.IndexOfParallel(this, item); + } + else + { + return RecyclableDefaults.ItemNotFoundIndex; + } + } + } + + public void Insert(int index, T item) => throw new NotSupportedException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public long LongIndexOf(T item) + { + if (_lastBlockWithData == 0) + { + return Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + } + else if (_longCount == 0) + { + return RecyclableDefaults.ItemNotFoundIndex; + } + + int itemIndex = Array.IndexOf(_memoryBlocks[0], item, 0, _longCount < _blockSize ? _nextItemIndex : _blockSize); + if (itemIndex >= 0) + { + return itemIndex; + } + else if (_longCount < RecyclableDefaults.MinItemsCountForParallelization) + { + return IndexOfHelpers.IndexOfSequential(this, item); + } + else + { + // & WAS SLOWER + // long toSearch = _longCount - _blockSize; + if (_longCount > _blockSize) + { + itemIndex = Array.IndexOf(_memoryBlocks[1], item, 0, (int)Math.Min(_blockSize, _longCount - _blockSize)); + return itemIndex >= 0 ? _blockSize + itemIndex : IndexOfHelpers.IndexOfParallel(this, item); + } + else + { + return RecyclableDefaults.ItemNotFoundIndex; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool Remove(T item) + { + var index = LongIndexOf(item); + if (index >= 0) + { + _longCount--; + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + + return true; + } + + return false; + } + + public void RemoveBlock(int index) + { + if (_blockSize >= RecyclableDefaults.MinPooledArrayLength) + { + _blockArrayPool.Return(_memoryBlocks[index], NeedsClearing); + _memoryBlocks[index] = _emptyBlockArray; + } + else + { + _memoryBlocks[index] = _emptyBlockArray; + } + + _capacity -= _blockSize; + if (_nextItemBlockIndex == index) + { + _nextItemIndex = 0; + } + else if (_nextItemBlockIndex > index) + { + _nextItemBlockIndex--; + } + + _lastBlockWithData--; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(int index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + + if (index < _longCount) + { + CopyItems(this, index); + } + + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void RemoveAt(long index) + { + if (index >= _longCount || index < 0) + { + ThrowArgumentOutOfRangeException($"Argument \"{nameof(index)}\" = {index} is out of range. Expected value between 0 and {_longCount - 1}"); + } + + _longCount--; + + if (index < _longCount) + { + CopyItems(this, index); + } + + if (_nextItemIndex > 0) + { + _nextItemIndex--; + } + else + { + _nextItemIndex = _blockSizeMinus1; + _nextItemBlockIndex--; + } + + if (_nextItemIndex == 0) + { + _lastBlockWithData--; + } + + if (NeedsClearing) + { +#pragma warning disable CS8601 // In real use cases we'll never access it + _memoryBlocks[_nextItemBlockIndex][_nextItemIndex] = default; +#pragma warning restore CS8601 + } + } + + IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + + ~RecyclableLongList() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + if (_capacity > 0) + { + Clear(); + if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) + { + _memoryBlocksPool.Return(_memoryBlocks, NeedsClearing); + } + + GC.SuppressFinalize(this); + } + } + } +} diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 46b69eb..070ebcd 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -4,28 +4,61 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableQueue : IRecyclableOwner, IList, IDisposable + internal class RecyclableQueue : IRecyclableOwner, IList, IDisposable { + private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly IEqualityComparer _equalityComparer = EqualityComparer.Default; - private static readonly IComparer _comparer = Comparer.Default; private bool _disposedValue; + private RecyclableList Memory { get; } - protected List Arrays { get; } - protected long GetAbsoluteIndex(long index) => index + RemovedCount; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T Dequeue(RecyclableQueue queue) + { + if (queue.LongCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(queue), "There are no items in the queue"); + } + + var toRemove = queue[0]; + queue.RemovedCount++; + queue.LongCount--; + if (queue.Capacity - queue.LongCount == queue.BlockSize) + { + queue.RemoveBlock(0); + queue.RemovedCount -= queue.BlockSize; + } + + return toRemove; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryDequeue(RecyclableQueue queue, out T? item) + { + if (queue.LongCount > 0) + { + item = queue.Dequeue(); + return true; + } + + item = default; + return false; + } + + protected static readonly bool NeedsClearing = !typeof(T).IsValueType; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected long GetRelativeIndex(long index) => index - RemovedCount; + protected long RemovedCount { get; set; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T[] RentArrayFromPool(int minSize) => ArrayPool.Shared.Rent(minSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(T[] array) => ArrayPool.Shared.Return(array); - public RecyclableQueue(IEnumerable source, int blockSize = RecyclableDefaults.BlockSize) { BlockSize = blockSize; - Arrays = new(); + Memory = new(1); foreach (var item in source) { Enqueue(item); @@ -35,7 +68,7 @@ namespace Recyclable.Collections public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize) { BlockSize = blockSize; - Arrays = new(); + Memory = new(1); } public int BlockSize { get; protected set; } @@ -43,31 +76,47 @@ namespace Recyclable.Collections public int Count { - get => (int)LongCount.LimitTo(int.MaxValue, _comparer); + get => (int)LongCount; set => LongCount = value; } public long LongCount { get; set; } - public bool IsReadOnly { get; } = false; + public bool IsReadOnly { get; } + + public T this[int index] + { + get => GetItem(index); + set => SetItem(index, value); + } - public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; } public T this[long index] { - get - { - long absoluteIndex = GetAbsoluteIndex(index); - int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize); - long itemIndex = absoluteIndex.ToItemIndex(BlockSize); - return Arrays[arrayIndex][itemIndex]; - } + get => GetItem(index); + set => SetItem(index, value); + } - set - { - long absoluteIndex = GetAbsoluteIndex(index); - int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize); - long itemIndex = absoluteIndex.ToItemIndex(BlockSize); - Arrays[arrayIndex][itemIndex] = value; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ToArrayIndex(long index, int blockSize) => (int)(index / blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long ToItemIndex(long index, int blockSize) => index % blockSize; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetItem(long index, T value) + { + long absoluteIndex = GetAbsoluteIndex(index, RemovedCount); + int arrayIndex = ToArrayIndex(absoluteIndex, BlockSize); + long itemIndex = ToItemIndex(absoluteIndex, BlockSize); + Memory[arrayIndex][itemIndex] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T GetItem(long index) + { + long absoluteIndex = GetAbsoluteIndex(index, RemovedCount); + int arrayIndex = ToArrayIndex(absoluteIndex, BlockSize); + long itemIndex = ToItemIndex(absoluteIndex, BlockSize); + return Memory[arrayIndex][itemIndex]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -75,33 +124,22 @@ namespace Recyclable.Collections { if (LongCount == Capacity) { - T[] newArray = BlockSize.RentArrayFromPool(); - Arrays.Add(newArray); - Capacity += BlockSize; + AddBlock(); } - var newIndex = LongCount; - // We don't want this[newIndex] set to raise the update event, because - // the count would be wrong. We need to increase it, first. - this[newIndex] = item; + SetItem(LongCount, item); LongCount++; } - - public T Dequeue() + public void AddBlock() { - var toRemove = this[0]; - RemovedCount++; - LongCount--; - if (Capacity - LongCount == BlockSize) - { - RemoveBlock(0); - RemovedCount -= BlockSize; - } - - return toRemove; + T[] newArray = BlockSize.RentArrayFromPool(_arrayPool); + Memory.Add(newArray); + Capacity += BlockSize; } + public T Dequeue() => Dequeue(this); + public void Add(T item) => Enqueue(item); public void Clear() @@ -109,11 +147,11 @@ namespace Recyclable.Collections try { // Remove in reversed order for performance savings - while (Arrays.Count > 0) + while (Memory.Count > 0) { try { - RemoveBlock(Arrays.Count - 1); + RemoveBlock(Memory.Count - 1); } catch (Exception) { @@ -126,62 +164,43 @@ namespace Recyclable.Collections { LongCount = 0; RemovedCount = 0; - Arrays.Clear(); + Memory.Clear(); } } - public bool Contains(T item) => Arrays.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => Arrays.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public IEnumerable Enumerate() => Arrays.Enumerate(BlockSize, LongCount); - public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public int IndexOf(T item) => (int)LongIndexOf(item).LimitTo(int.MaxValue, _comparer); - public long LongIndexOf(T item) => GetRelativeIndex(Arrays.LongIndexOf(BlockSize, item, _equalityComparer)); + public bool Contains(T item) => Memory.Contains(item); + public void CopyTo(T[] array, int arrayIndex) => Memory.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex); + public IEnumerable Enumerate() => Memory.Enumerate(BlockSize, LongCount); + public IEnumerator GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator(); + public int IndexOf(T item) => (int)LongIndexOf(item); + public long LongIndexOf(T item) => GetRelativeIndex(Memory.LongIndexOf(BlockSize, item, _equalityComparer)); public void Insert(int index, T item) => throw new NotSupportedException(); public void RemoveAt(int index) => throw new NotSupportedException(); - - public bool TryDequeue(out T? item) - { - if (LongCount > 0) - { - item = Dequeue(); - return true; - } - - item = default; - return false; - } - - public bool Remove(T item) => throw new NotSupportedException(); + public bool TryDequeue(out T? item) => TryDequeue(this, out item); + public bool Remove(T item) => TryDequeue(out var _); public void RemoveBlock(int index) { try { - ReturnToPool(Arrays[index]); - Arrays.RemoveAt(index); + Memory[index].ReturnToPool(_arrayPool, NeedsClearing); + Memory.RemoveAt(index); } finally { Capacity -= BlockSize; - LongCount -= BlockSize; + //LongCount -= BlockSize; } } - protected void Dispose(bool disposing) + public void Dispose() { if (!_disposedValue) { Clear(); _disposedValue = true; } - } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); GC.SuppressFinalize(this); } } diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index ee807e2..d1d9ede 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -1,6 +1,6 @@ namespace Recyclable.Collections { - public class RecyclableSortedList : RecyclableList<(TKey Key, TValue Value)> + internal class RecyclableSortedList : RecyclableLongList<(TKey Key, TValue Value)> where TKey : notnull { private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer(); @@ -27,7 +27,7 @@ QuickSort(); } - public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) + public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) : base(blockSize) { } @@ -38,7 +38,7 @@ public void BeginUpdate() => _isUpdating++; - public void Clear() + public new void Clear() { BeginUpdate(); try @@ -73,7 +73,7 @@ finally { EndUpdate(false); - } + } } } } \ No newline at end of file diff --git a/Recyclable.Collections/RecyclableStack.cs b/Recyclable.Collections/RecyclableStack.cs index c8af95c..b885708 100644 --- a/Recyclable.Collections/RecyclableStack.cs +++ b/Recyclable.Collections/RecyclableStack.cs @@ -2,12 +2,12 @@ namespace Recyclable.Collections { - public class RecyclableStack : IList, IDisposable + internal class RecyclableStack : IList, IDisposable { private bool _disposedValue; private readonly int _blockSize; - protected RecyclableList List { get; } + protected RecyclableLongList List { get; } public int Count => List.Count; public long LongCount { @@ -32,7 +32,7 @@ namespace Recyclable.Collections List = new(list, blockSize); } - public RecyclableStack(RecyclableList list, int blockSize = RecyclableDefaults.BlockSize) + public RecyclableStack(RecyclableLongList list, int blockSize = RecyclableDefaults.BlockSize) { _blockSize = blockSize; List = new(list, blockSize); @@ -47,9 +47,9 @@ namespace Recyclable.Collections { var toRemove = List[LongCount - 1]; LongCount--; - if (List.Capacity - LongCount == _blockSize) + if ((List.Capacity * _blockSize) - LongCount == _blockSize) { - List.RemoveBlock(List.BlockCount - 1); + List.RemoveBlock(List.ReservedBlockCount - 1); } return toRemove; diff --git a/Recyclable.CollectionsTests/CustomIList.cs b/Recyclable.CollectionsTests/CustomIList.cs new file mode 100644 index 0000000..6d191b8 --- /dev/null +++ b/Recyclable.CollectionsTests/CustomIList.cs @@ -0,0 +1,33 @@ +using System.Collections; + +namespace Recyclable.CollectionsTests +{ + public class CustomIList : IList + { + protected List _list; + + public CustomIList() + { + _list = new List(); + } + + public CustomIList(IEnumerable items) + { + _list = new List(items); + } + + public T this[int index] { get => _list[index]; set => _list[index] = value; } + public int Count => _list.Count; + public bool IsReadOnly => false; + public void Add(T item) => _list.Add(item); + public void Clear() => _list.Clear(); + public bool Contains(T item) => _list.Contains(item); + public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => _list.GetEnumerator(); + public int IndexOf(T item) => _list.IndexOf(item); + public void Insert(int index, T item) => _list.Insert(index, item); + public bool Remove(T item) => _list.Remove(item); + public void RemoveAt(int index) => _list.RemoveAt(index); + IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator(); + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/EnumerableWithoutCount.cs b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs new file mode 100644 index 0000000..34df3e9 --- /dev/null +++ b/Recyclable.CollectionsTests/EnumerableWithoutCount.cs @@ -0,0 +1,17 @@ +using System.Collections; + +namespace Recyclable.CollectionsTests +{ + public class EnumerableWithoutCount : IEnumerable + { + private readonly IEnumerable _testData; + + public EnumerableWithoutCount(IEnumerable testData) + { + _testData = testData; + } + + public IEnumerator GetEnumerator() => _testData.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _testData.GetEnumerator(); + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/MathUtilsTests.cs b/Recyclable.CollectionsTests/MathUtilsTests.cs new file mode 100644 index 0000000..dd8e625 --- /dev/null +++ b/Recyclable.CollectionsTests/MathUtilsTests.cs @@ -0,0 +1,85 @@ +using FluentAssertions; +using Recyclable.Collections; + +namespace Recyclable.CollectionsTests +{ + public class MathUtilsTests + { + [Theory] + [InlineData(int.MaxValue / 1024, int.MaxValue / 10 / 1024)] + public void DivMod(long testNumber, int blockSize) + { + for (var testCase = testNumber; testCase >= 0; testCase--) + { + // Prepare + var expectedResult = testCase / blockSize; + var expectedRemainder = testCase % blockSize; + + // Act + var actualResult = MathUtils.DivMod(testCase, blockSize, out long actualRemainder); + + // Validate + _ = actualResult.Should().Be(expectedResult); + _ = actualRemainder.Should().Be(expectedRemainder); + } + } + + [Theory] + [InlineData(0, false)] + [InlineData(1, true)] + [InlineData(2, true)] + [InlineData(3, false)] + [InlineData(4, true)] + [InlineData(5, false)] + [InlineData(8, true)] + [InlineData(16, true)] + [InlineData(20, false)] + [InlineData(32, true)] + [InlineData(64, true)] + [InlineData(128, true)] + [InlineData(256, true)] + [InlineData(512, true)] + [InlineData(1024, true)] + [InlineData(2048, true)] + [InlineData(4096, true)] + [InlineData(8192, true)] + [InlineData(16384, true)] + [InlineData(32768, true)] + [InlineData(65536, true)] + [InlineData(131072, true)] + [InlineData(262144, true)] + [InlineData(524288, true)] + [InlineData(1048576, true)] + [InlineData(2097152, true)] + [InlineData(4194304, true)] + [InlineData(8388608, true)] + [InlineData(16777216, true)] + [InlineData(33554432, true)] + [InlineData(67108864, true)] + [InlineData(134217728, true)] + [InlineData(268435456, true)] + [InlineData(536870912, true)] + [InlineData(1073741824, true)] + [InlineData(2147483648, true)] + [InlineData(2147483649, false)] + [InlineData(4294967296, true)] + public void IsPow2(long testNumber, bool expected) + { + _ = MathUtils.IsPow2(testNumber).Should().Be(expected); + } + + [Theory] + [InlineData(-2, -1)] + [InlineData(-1, -1)] + [InlineData(0, -1)] + [InlineData(1, 0)] + [InlineData(2, 1)] + [InlineData(3, -1)] + [InlineData(4, 2)] + [InlineData(8, 3)] + public void GetPow2Shift(long testNumber, int expected) + { + _ = MathUtils.GetPow2Shift(testNumber).Should().Be(expected); + } + } +} diff --git a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj index 12982dd..215afa8 100644 --- a/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj +++ b/Recyclable.CollectionsTests/Recyclable.CollectionsTests.csproj @@ -6,14 +6,20 @@ enable false + + AnyCPU;x64 - - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index 88ab0cb..fb77c5a 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -3,189 +3,549 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass()] public class RecyclableListTests { - private const int _totalObjectCount = 5_000; - private static RecyclableList NewRecyclableList => CreateReversedRecyclableList(_testData); - private static readonly IEnumerable _testData = Enumerable.Range(1, _totalObjectCount); - private static RecyclableList CreateReversedRecyclableList(IEnumerable source) => new(source); - - [TestMethod] - public void AddShouldAddItems() + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = new RecyclableList(); + using var list = new RecyclableList(); // Act - const int itemsCount = 5; - foreach (var index in Enumerable.Range(1, itemsCount)) + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + list.AddRange(testDataRecyclableLongList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + { + // Prepare + using var list = new RecyclableList(); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + list.AddRange(testDataRecyclableLongList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + _ = list.Capacity.Should().Be(0); + _ = list.Count.Should().Be(0); + _ = list.Should().BeEmpty(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + list.AddRange(testDataList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + list.AddRange(testDataRecyclableLongList); + list.AddRange(testDataRecyclableLongList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + var expectedData = testData.Concat(testData).ToArray(); + + if (itemsCount > 0) + { + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount * 2); + } + + _ = list.Count.Should().Be(expectedData.Length); + _ = list.Should().ContainInConsecutiveOrder(expectedData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + testData = testData.Concat(testData).ToArray(); + + // Act + using var list = new RecyclableList(); + foreach (var index in testData) { list.Add(index); } // Validate - _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); - for (var index = 0; index < itemsCount; index++) - { - var actual = list[index]; - _ = actual.Should().Be(index + 1); - } + _ = list.Count.Should().Be((int)(itemsCount * 2)); + _ = list.Should().ContainInConsecutiveOrder(testData); } - [TestMethod] - public void AddShouldAcceptDuplicates() + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; + using var list = new RecyclableList(); // Act - using var list = new RecyclableList(); - foreach (var index in testNumbers) + foreach (var item in testData) + { + list.Add(item); + _ = list.Count.Should().Be((int)item); + } + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount) + { + // Act + using var list = new RecyclableList(); + list.AddRange(testData); + foreach (var index in testData) { list.Add(index); } // Validate - _ = list.LongCount.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); + _ = list.Count.Should().Be((int)(itemsCount * 2)); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); } - [TestMethod] - public void ConstructorShouldAcceptDuplicates() + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount) { // Prepare - int[] testNumbers = new[] { 1, 2, 2, 3 }; + using var list = new RecyclableList(testData); + _ = list.Count.Should().Be((int)itemsCount); - // Act - using var list = new RecyclableList(testNumbers); + // Act + list.Clear(); + list.Clear(); - // Validate - _ = list.LongCount.Should().Be(testNumbers.Length); - _ = list.Should().BeEquivalentTo(testNumbers); + // Validate + _ = list.Count.Should().Be(0); + _ = list.Should().BeEmpty(); } - [TestMethod] - public void InitializeSortClearShouldSucceed() + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { - var expectedList = _testData; + // Prepare + using var list = new RecyclableList(testData); + list.Dispose(); // Act - using RecyclableList list = NewRecyclableList; + list.Dispose(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount) + { + // Act + using var list = new RecyclableList(testData.Concat(testData), initialCapacity: (int)itemsCount * 2); + + // Validate + _ = list.Count.Should().Be((int)itemsCount * 2); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount) + { + // Act + using var list = new RecyclableList(testData); + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount) + { + // Act + using var list = new RecyclableList(testData); + + // Validate + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount) + { + if (itemsCount == 0) + { + return; + } + + // Prepare + using var list = new RecyclableList(testData); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.Contains(item).Should().BeTrue(); + } + } + + [Fact] + public void ContainsShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableList(); + + // Validate + _ = list.Contains(0).Should().BeFalse(); + _ = list.Contains(1).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount) + { + if (itemsCount == 0) + { + return; + } + + // Prepare + using var list = new RecyclableList(testData); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.Contains(-item).Should().BeFalse(); + } + + // Validate + _ = list.Contains(0).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(testData); + long[] copiedItems = new long[itemsCount]; + + // Act + list.CopyTo(copiedItems, 0); + + // Validate + _ = copiedItems.Length.Should().Be((int)itemsCount); + _ = copiedItems.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(testData); + + // Act + var yieldedItems = new List((int)itemsCount); + yieldedItems.AddRange(list); + + // Validate + _ = yieldedItems.Count.Should().Be((int)itemsCount); + _ = yieldedItems.Should().ContainInConsecutiveOrder(testData); + } + + [Fact] + public void IndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableList(); + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + _ = list.IndexOf(1).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + // Prepare + using var list = new RecyclableList(testData); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.IndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(testData); + + // Act & Validate + foreach (var index in testData) + { + var actual = list.IndexOf(index); + _ = actual.Should().Be((int)(index - 1)); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount) + { + if (itemsCount == 0) + { + return; + } + + // Act + using RecyclableList list = new(testData); list.QuickSort(); // Validate - _ = list.LongCount.Should().Be(_totalObjectCount); - _ = list.Should().Equal(expectedList); - _ = expectedList.Should().AllSatisfy(expected => list.Contains(expected)); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().Equal(testData); // Act list.Clear(); // Validate - _ = list.LongCount.Should().Be(0); - } - - [TestMethod] - public void CopyToShouldCopyAllItems() - { - // Prepare - using var list = NewRecyclableList; - - // Act - var actual = new int[list.LongCount]; - list.CopyTo(actual, 0); - _ = actual.Should().BeEquivalentTo(_testData); - } - - [TestMethod] - public void GetEnumeratorShouldYieldAllItems() - { - // Prepare - using var list = NewRecyclableList; + _ = list.Count.Should().Be(0); + _ = list.Should().BeEmpty(); // Act - var actual = new List(); - var enumerator = list.GetEnumerator(); - while (enumerator.MoveNext()) + list.AddRange(testData); + + // Validate + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + + // Act + foreach (var item in testData.Reverse()) { - actual.Add(enumerator.Current); + _ = list.Remove(item).Should().BeTrue(); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(); + + // Act + foreach (var item in testData) + { + list.Insert(0, item); + _ = list.Count.Should().Be((int)item); } // Validate - _ = actual.Should().BeEquivalentTo(_testData); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.Count.Should().Be((int)itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData.Reverse()); } - [TestMethod] - public void IndexOfShouldFindTheIndexes() + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount) { // Prepare - using var list = NewRecyclableList; + using var list = new RecyclableList(testData); // Act & Validate - foreach(var index in _testData) + for (var deleted = 1; deleted <= itemsCount; deleted++) { - var actual = list.IndexOf(index); - _ = actual.Should().Be(index - 1); + // Act + list.RemoveAt(0); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(list.Count - 1); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) + { + // Prepare + using var list = new RecyclableList(testData); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + _ = list.Remove(deleted).Should().BeTrue(); + + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); } } - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void InsertShouldRaiseNotSupportedException() + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount) { - // Prepare - using var list = NewRecyclableList; + // Act & Validate + using var list = new RecyclableList(testData); + long deleted = 0; + foreach (var item in testData.Reverse()) + { + // Act + _ = list.Remove(item).Should().BeTrue(); + deleted++; - // Act - list.Insert(0, -1); - } - - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void RemoveShouldRaiseNotSupportedException() - { - // Prepare - using var list = NewRecyclableList; - - // Act - bool removed = list.Remove(_testData.First()); - _ = removed.Should().BeFalse(); - } - - [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void RemoveAtShouldRaiseNotSupportedException() - { - // Prepare - using var list = NewRecyclableList; - - // Act - list.RemoveAt(0); - } - - [TestMethod] - public void DisposeShouldSucceed() - { - // Prepare - using var list = NewRecyclableList; - - // Act - list.Dispose(); - - // Validate - _ = list.LongCount.Should().Be(0L); - } - - [TestMethod] - public void ConsecutiveDisposeShouldSucceed() - { - // Prepare - using var list = NewRecyclableList; - list.Dispose(); - - // Act - list.Dispose(); + // Validate + _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); + } } } } \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableLongListTestData.cs b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs new file mode 100644 index 0000000..bffd458 --- /dev/null +++ b/Recyclable.CollectionsTests/RecyclableLongListTestData.cs @@ -0,0 +1,93 @@ +using Recyclable.Collections; + +namespace Recyclable.CollectionsTests +{ + public static class RecyclableLongListTestData + { + private static IEnumerable DoCreateTestData(long itemsCount) + { + for (long index = 1; index <= itemsCount; index++) + { + yield return index; + } + } + + public static IEnumerable CreateTestData(long itemsCount) => DoCreateTestData(itemsCount).ToArray(); + + public static readonly IEnumerable ItemsCountVariants = new long[] + { + 0, 1, 2, 3, 10, 16, 32, 64, 128, 256, 333, 512, 1_024, 8_192, 16_384, + 100_000 + }; + + public static readonly IEnumerable BlockSizeVariants = new int[] + { + 1, 2, 4, 8, 10, 16, 32, 64, 128, 256, 512, 1_024, 2_048, 4_096, + 8_192, 16_384 + }; + + private static IEnumerable GetTargetDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { itemsCount, targetBlockSize }; + } + } + } + + public static IEnumerable TargetDataVariants => GetTargetDataVariants().ToArray(); + + private static IEnumerable CreateSourceDataVariants() + { + foreach (var itemsCount in ItemsCountVariants) + { + yield return new object[] { $"int[{itemsCount}]", CreateTestData(itemsCount).ToArray(), itemsCount }; + yield return new object[] { $"List(itemsCount: {itemsCount}", CreateTestData(itemsCount).ToList(), itemsCount }; + yield return new object[] { $"RecyclableList(itemsCount: {itemsCount})", CreateTestData(itemsCount).ToRecyclableList(), itemsCount }; + foreach (var sourceBlockSize in BlockSizeVariants) + { + yield return new object[] + { + $"RecyclableLongList(itemsCount: {itemsCount}, minBlockSize: {sourceBlockSize})", + CreateTestData(itemsCount).ToRecyclableLongList(sourceBlockSize), + itemsCount + }; + } + + yield return new object[] { $"IList(itemsCount: {itemsCount})", new CustomIList(CreateTestData(itemsCount)), itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) with non-enumerated count", CreateTestData(itemsCount), itemsCount }; + yield return new object[] { $"IEnumerable(itemsCount: {itemsCount}) without non-enumerated count", new EnumerableWithoutCount(CreateTestData(itemsCount)), itemsCount }; + } + } + + public static IEnumerable SourceDataVariants => CreateSourceDataVariants().ToArray(); + + private static IEnumerable CreateSourceEmptyDataVariants() => new[] + { + new object[] { "int[]", Array.Empty() }, + new object[] { "List", Array.Empty().ToList() }, + new object[] { "RecyclableList", Array.Empty().ToRecyclableList() }, + new object[] { "RecyclableLongList", Array.Empty().ToRecyclableLongList() }, + new object[] { "IList", new CustomIList(Array.Empty()) }, + new object[] { "IEnumerable with non-enumerated count", Array.Empty().AsEnumerable() }, + new object[] { "IEnumerable without non-enumerated count", new EnumerableWithoutCount(Array.Empty()) } + }; + + public static IEnumerable EmptySourceDataVariants => CreateSourceEmptyDataVariants().ToArray(); + + private static IEnumerable CreateSourceTargetDataVariants() + { + foreach (var testData in SourceDataVariants) + { + foreach (var targetBlockSize in BlockSizeVariants) + { + yield return new object[] { testData[0], testData[1], testData[2], targetBlockSize }; + } + } + } + + public static IEnumerable SourceTargetDataVariants => CreateSourceTargetDataVariants().ToArray(); + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs new file mode 100644 index 0000000..3f2f53c --- /dev/null +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -0,0 +1,662 @@ +using FluentAssertions; +using Recyclable.Collections; + +namespace Recyclable.CollectionsTests +{ + public class RecyclableLongListTests + { + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + list.AddRange(testDataRecyclableLongList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + int expectedItemsCount = testData.Count(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize != 0 ? 0 : 1)); + _ = list.LongCount.Should().Be(expectedItemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable testData) + { + // Prepare + using var list = new RecyclableLongList(); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + list.AddRange(testDataRecyclableLongList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + _ = list.Capacity.Should().Be(0); + _ = list.LastBlockWithData.Should().Be(-1); + _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); + + // Act + if (testData is long[] testDataArray) + { + list.AddRange(testDataArray); + list.AddRange(testDataArray); + } + else if (testData is List testDataList) + { + list.AddRange(testDataList); + list.AddRange(testDataList); + } + else if (testData is RecyclableList testDataRecyclableList) + { + list.AddRange(testDataRecyclableList); + list.AddRange(testDataRecyclableList); + } + else if (testData is RecyclableLongList testDataRecyclableLongList) + { + list.AddRange(testDataRecyclableLongList); + list.AddRange(testDataRecyclableLongList); + } + else if (testData is IList testDataIList) + { + list.AddRange(testDataIList); + list.AddRange(testDataIList); + } + else if (testData is IEnumerable testDataIEnumerable) + { + list.AddRange(testDataIEnumerable); + list.AddRange(testDataIEnumerable); + } + else + { + throw new InvalidCastException("Unknown type of test data"); + } + + // Validate + var expectedData = testData.Concat(testData).ToArray(); + + if (itemsCount > 0) + { + _ = list.Capacity.Should().BeGreaterThanOrEqualTo(targetBlockSize); + } + + _ = list.LongCount.Should().Be(expectedData.Length); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); + _ = list.Should().ContainInConsecutiveOrder(expectedData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + testData = testData.Concat(testData).ToArray(); + + // Act + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); + foreach (var index in testData) + { + list.Add(index); + } + + // Validate + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void AddShouldAddItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); + + // Act + foreach (var index in testData) + { + list.Add(index); + } + + // Validate + _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize == 0 ? 1 : 0)); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void GetByIndexShouldReturnCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + foreach (var item in testData) + { + _ = list[item - 1].Should().Be(item); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + //[InlineData("Debugging case", new long[1] { 1 }, 1, 2)] + public void AddShouldAddItemWhenAfterAddRange(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Act + using var list = new RecyclableLongList(minBlockSize: targetBlockSize); + list.AddRange(testData); + foreach (var index in testData) + { + list.Add(index); + } + + // Validate + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); + _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ClearShouldRemoveAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + _ = list.LongCount.Should().Be(itemsCount); + + // Act + list.Clear(); + list.Clear(); + + // Validate + _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConsecutiveDisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + list.Dispose(); + + // Act + list.Dispose(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + testData = testData.Concat(testData).ToArray(); + + // Act + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Validate + _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.Should().Equal(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Act + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Validate + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only"); + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Act + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ContainsShouldFindAllItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.Contains(item).Should().BeTrue($"we searched for {item}"); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.TargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ContainsShouldNotFindAnythingWhenListEmpty(long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(minBlockSize: targetBlockSize, expectedItemsCount: itemsCount); + + // Validate + _ = list.Contains(0).Should().BeFalse(); + _ = list.Contains(1).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void ContainsShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.Contains(-item).Should().BeFalse(); + } + + // Validate - this case is special, because this is the default item value + _ = list.Contains(0).Should().BeFalse(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + //[InlineData("Debuging case", new[] { 1L, 2, }, 2, 2)] + public void CopyToShouldCopyAllItemsInTheCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act + var actual = new long[itemsCount]; + list.CopyTo(actual, 0); + _ = actual.Should().ContainInConsecutiveOrder(testData); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void DisposeShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act + list.Dispose(); + + // Validate + _ = list.LongCount.Should().Be(0L); + _ = list.Should().BeEmpty(); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void EnumerateShouldYieldAllItemsInCorrectOrder(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act + var actual = new List(); + using var enumerator = list.GetEnumerator(); + while (enumerator.MoveNext()) + { + actual.Add(enumerator.Current); + } + + // Validate + _ = actual.Should().ContainInConsecutiveOrder(testData); + } + + [Fact] + public void IndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableLongList(); + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + _ = list.IndexOf(1).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void IndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + if (itemsCount == 0) + { + // There is nothing to search for when our lists are empty + return; + } + + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.IndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.IndexOf(0).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void IndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + foreach (var item in testData) + { + var actual = list.IndexOf(item); + _ = actual.Should().Be((int)item - 1); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void InitializeSortClearAddRangeRemoveShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + if (itemsCount == 0) + { + return; + } + + // Act + using RecyclableLongList list = new(testData, minBlockSize: targetBlockSize); + list.QuickSort(); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().Equal(testData); + + // Act + list.Clear(); + + // Validate + _ = list.LongCount.Should().Be(0); + _ = list.Should().BeEmpty(); + + // Act + list.AddRange(testData); + + // Validate + _ = list.LongCount.Should().Be(itemsCount); + _ = list.Should().ContainInConsecutiveOrder(testData); + + // Act + foreach (var item in testData.Reverse()) + { + _ = list.Remove(item).Should().BeTrue(); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void InsertAtTheBeginningShouldMoveItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act + _ = Assert.Throws(() => list.Insert(0, -1)); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void InsertShouldRaiseNotSupportedException(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act + _ = Assert.Throws(() => list.Insert(0, -1)); + } + + [Fact] + public void LongIndexOfShouldNotFindAnythingWhenListEmpty() + { + // Prepare + using var list = new RecyclableLongList(); + + // Validate + _ = list.LongIndexOf(0).Should().Be(-1); + _ = list.LongIndexOf(1).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void LongIndexOfShouldNotFindNonExistingItems(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + if (itemsCount == 0) + { + return; + } + + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + _ = testData.Any().Should().BeTrue("we need items on the list that we can look for"); + + // Act + foreach (var item in testData) + { + // Validate + _ = list.LongIndexOf(-item).Should().Be(-1); + } + + // Validate + _ = list.LongIndexOf(0).Should().Be(-1); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void LongIndexOfShouldReturnCorrectIndexes(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + foreach (var item in testData) + { + // Act + var index = list.LongIndexOf(item); + + // Validate + _ = index.Should().Be(item - 1); + } + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveAtFromTheBeginningShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(0); + + // 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().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveAtFromTheEndShouldSucceed(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + list.RemoveAt(list.Count - 1); + + // 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().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveFromTheBeginningShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + + // Act & Validate + for (var deleted = 1; deleted <= itemsCount; deleted++) + { + // Act + _ = list.Remove(deleted).Should().BeTrue(); + + // 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().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); + } + + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceTargetDataVariants), MemberType = typeof(RecyclableLongListTestData))] + public void RemoveFromTheEndShouldRemoveTheCorrectItem(string testCase, IEnumerable testData, long itemsCount, int targetBlockSize) + { + // Prepare + using var list = new RecyclableLongList(testData, minBlockSize: targetBlockSize); + var expectedData = 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)]; + _ = 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); + } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); + } + } +} \ No newline at end of file diff --git a/Recyclable.CollectionsTests/RecyclableQueueTests.cs b/Recyclable.CollectionsTests/RecyclableQueueTests.cs index 29e3924..18fe0a8 100644 --- a/Recyclable.CollectionsTests/RecyclableQueueTests.cs +++ b/Recyclable.CollectionsTests/RecyclableQueueTests.cs @@ -3,12 +3,11 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass] public class RecyclableQueueTests { private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" }; - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenCreated() { // Act @@ -17,9 +16,10 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Should().HaveCount(_testData.Length) .And.ContainInConsecutiveOrder(_testData); + _ = list.LongCount.Should().Be(_testData.Length); } - [TestMethod] + [Fact] public void ShouldBeEmptyWhenNotInitialized() { // Act @@ -27,9 +27,10 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Should().BeEmpty(); + _ = list.LongCount.Should().Be(0); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenInitialized() { // Act @@ -48,7 +49,7 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyAfterClear() { // Prepare @@ -61,7 +62,7 @@ namespace Recyclable.CollectionsTests _ = list.Should().BeEmpty(); } - [TestMethod] + [Fact] public void DequeueShouldReturnItemsInTheSameOrder() { // Prepare @@ -73,18 +74,18 @@ namespace Recyclable.CollectionsTests list.Enqueue(_testData[itemIdx]); } - var dequeuedItems = new RecyclableList(); + var dequeuedItems = new RecyclableLongList(); while (list.LongCount > 0) { dequeuedItems.Add(list.Dequeue()); } // Validate - _ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData); + _ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData) + .And.BeEquivalentTo(_testData); } - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Fact] public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound() { // Prepare @@ -96,25 +97,27 @@ namespace Recyclable.CollectionsTests _ = list.Dequeue(); } - _ = list.Dequeue(); + _ = Assert.Throws(() => list.Dequeue()); + _ = list.LongCount.Should().Be(0); } - [TestMethod] + [Fact] public void GetEnumeratorShouldYieldAllItemsInTheSameOrder() { // Prepare using var list = new RecyclableQueue(_testData); // Act - var actual = new RecyclableList(); - var enumerator = list.GetEnumerator(); + var actual = new RecyclableLongList(); + using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { actual.Add(enumerator.Current); } // Validate - _ = actual.Should().ContainInConsecutiveOrder(_testData); + _ = actual.Should().ContainInConsecutiveOrder(_testData) + .And.BeEquivalentTo(_testData); } } } diff --git a/Recyclable.CollectionsTests/RecyclableSortedListTests.cs b/Recyclable.CollectionsTests/RecyclableSortedListTests.cs index 82aecb9..6f72aa2 100644 --- a/Recyclable.CollectionsTests/RecyclableSortedListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableSortedListTests.cs @@ -3,12 +3,11 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass()] public class RecyclableSortedListTests { private static readonly (int, string)[] _testData = new[] { (5, "a"), (4, "d"), (3, "c"), (2, "b"), (1, "a") }; - [TestMethod] + [Fact] public void ShouldBeSortedWhenCreated() { // Act @@ -20,7 +19,7 @@ namespace Recyclable.CollectionsTests .And.BeInAscendingOrder((item) => item.Key); } - [TestMethod] + [Fact] public void ShouldBeEmptyWhenNotInitialized() { // Act @@ -30,7 +29,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeEmpty(); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenInitialized() { // Act @@ -48,7 +47,7 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(_testData); } - [TestMethod] + [Fact] public void ShouldBeSortedWhenUpdateEnds() { // Prepare @@ -71,7 +70,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeInAscendingOrder(x => x.Key); } - [TestMethod] + [Fact] public void ShouldBeEmptyAfterClear() { // Prepare diff --git a/Recyclable.CollectionsTests/RecyclableStackTests.cs b/Recyclable.CollectionsTests/RecyclableStackTests.cs index 0946615..dffdd52 100644 --- a/Recyclable.CollectionsTests/RecyclableStackTests.cs +++ b/Recyclable.CollectionsTests/RecyclableStackTests.cs @@ -3,20 +3,19 @@ using Recyclable.Collections; namespace Recyclable.CollectionsTests { - [TestClass] public class RecyclableStackTests { private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" }; - [TestMethod] + [Fact] public void GetEnumeratorShouldYieldAllItemsInReversedOrder() { // Prepare using var list = new RecyclableStack(_testData); // Act - var actual = new RecyclableList(); - var enumerator = list.GetEnumerator(); + var actual = new RecyclableLongList(); + using var enumerator = list.GetEnumerator(); while (enumerator.MoveNext()) { actual.Add(enumerator.Current); @@ -26,7 +25,7 @@ namespace Recyclable.CollectionsTests _ = actual.Should().ContainInConsecutiveOrder(_testData.Reverse()); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenCreated() { // Act @@ -37,7 +36,7 @@ namespace Recyclable.CollectionsTests .And.ContainInConsecutiveOrder(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyWhenNotInitialized() { // Act @@ -47,7 +46,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeEmpty(); } - [TestMethod] + [Fact] public void ShouldNotBeSortedWhenInitialized() { // Act @@ -66,7 +65,7 @@ namespace Recyclable.CollectionsTests .And.BeEquivalentTo(_testData); } - [TestMethod] + [Fact] public void ShouldBeEmptyAfterClear() { // Prepare @@ -79,7 +78,7 @@ namespace Recyclable.CollectionsTests _ = sortedList.Should().BeEmpty(); } - [TestMethod] + [Fact] public void PopShouldReturnItemsInReversedOrder() { // Prepare @@ -91,7 +90,7 @@ namespace Recyclable.CollectionsTests list.Push(_testData[itemIdx]); } - var dequeuedItems = new RecyclableList(); + var dequeuedItems = new RecyclableLongList(); while (list.LongCount > 0) { dequeuedItems.Add(list.Pop()); @@ -101,8 +100,7 @@ namespace Recyclable.CollectionsTests _ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData.Reverse()); } - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] + [Fact] public void PopShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound() { // Prepare @@ -114,7 +112,7 @@ namespace Recyclable.CollectionsTests _ = list.Dequeue(); } - _ = list.Dequeue(); + _ = Assert.Throws(() => list.Dequeue()); } } } diff --git a/Recyclable.CollectionsTests/Usings.cs b/Recyclable.CollectionsTests/Usings.cs index ab67c7e..8c927eb 100644 --- a/Recyclable.CollectionsTests/Usings.cs +++ b/Recyclable.CollectionsTests/Usings.cs @@ -1 +1 @@ -global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file +global using Xunit; \ No newline at end of file