RecyclableLongList.IndexOf stability & performance improvements.

This commit is contained in:
mlemanczyk committed 2023-06-04 17:21:44 +02:00
1 parent e3ca5b4f87
commit fb689e84d6
4 files changed
+298 -81

No files matched your search

+139 -1
View File
@@ -145,7 +145,145 @@ This is the direct equivalent of `List<T>` class, except that the arrays are tak
* 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.
* 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` is not guaranteed to return the index of the first item found. In most cases it will, but there may be scenarios when the index of subsequent items on the list is found. That may happen when the no. of items equals to or exceeds 786_432. In that case `IndexOf` will parallelize the search using tasks.
* ⚠️ `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<T>` 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
@@ -2,11 +2,9 @@ namespace Recyclable.Collections.Parallel
{
public sealed class IndexOfSynchronizationContext
{
private readonly SpinWait _participantsSpinWait = new();
private readonly SpinLock _participantsSpinLock = new(false);
private readonly SpinLock _indexOfSpinLock = new(false);
private bool _indexOfLockTaken;
private SpinWait _participantsSpinWait = new();
private SpinLockSlimmer _participantsSpinLock = new();
private SpinLockSlimmer _indexOfSpinLock = new();
internal bool _isItemFound;
internal int _participants;
@@ -14,29 +12,21 @@ namespace Recyclable.Collections.Parallel
public void AddParticipant()
{
bool lockTaken = false;
_participantsSpinLock.Enter(ref lockTaken);
_participantsSpinLock.Enter();
_participants++;
if (lockTaken)
{
_participantsSpinLock.Exit(false);
}
_participantsSpinLock.Exit();
}
public void AddParticipants(int newParticipants)
{
bool lockTaken = false;
_participantsSpinLock.Enter(ref lockTaken);
_participantsSpinLock.Enter();
_participants += newParticipants;
if (lockTaken)
{
_participantsSpinLock.Exit(false);
}
_participantsSpinLock.Exit();
}
public void Lock()
{
_indexOfSpinLock.Enter(ref _indexOfLockTaken);
_indexOfSpinLock.Enter();
}
public void RemoveParticipants(int participantsToRemove)
@@ -46,13 +36,9 @@ namespace Recyclable.Collections.Parallel
ThrowNotEnoughParticipants();
}
bool lockTaken = false;
_participantsSpinLock.Enter(ref lockTaken);
_participantsSpinLock.Enter();
_participants -= participantsToRemove;
if (lockTaken)
{
_participantsSpinLock.Exit(false);
}
_participantsSpinLock.Exit();
}
public void RemoveParticipant()
@@ -62,13 +48,9 @@ namespace Recyclable.Collections.Parallel
ThrowNoMoreParticipants();
}
bool lockTaken = false;
_participantsSpinLock.Enter(ref lockTaken);
_participantsSpinLock.Enter();
_participants--;
if (lockTaken)
{
_participantsSpinLock.Exit(false);
}
_participantsSpinLock.Exit();
}
public void SignalAndWait()
@@ -78,13 +60,9 @@ namespace Recyclable.Collections.Parallel
ThrowNoMoreParticipants();
}
bool lockTaken = false;
_participantsSpinLock.Enter(ref lockTaken);
_participantsSpinLock.Enter();
_participants--;
if (lockTaken)
{
_participantsSpinLock.Exit(false);
}
_participantsSpinLock.Exit();
while (_participants > 0)
{
@@ -94,15 +72,13 @@ namespace Recyclable.Collections.Parallel
public void Unlock()
{
if (_indexOfLockTaken)
{
_indexOfSpinLock.Exit(false);
}
_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;
@@ -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;
}
}
}
@@ -1,4 +1,12 @@
using System.Runtime.CompilerServices;
// #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
@@ -8,65 +16,105 @@ namespace Recyclable.Collections
public static class IndexOfHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void IndexOfIn4ConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList<T> list, int blockIndex, T itemToFind)
private static void IndexOfInConsecutiveBlocks(IndexOfSynchronizationContext context, RecyclableLongList<T> list, int blockIndex, T itemToFind)
{
try
{
if (context._isItemFound)
if (context._isItemFound && blockIndex >= context.FoundBlockIndex)
{
return;
}
long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize);
if (context._isItemFound)
if (index >= 0 && (!context._isItemFound || index < context.FoundItemIndex))
{
ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + index);
return;
}
else if (context._isItemFound)
{
return;
}
else if (index >= 0)
{
context._isItemFound = true;
context.Lock();
context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + index;
context.Unlock();
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 (context._isItemFound)
if (index >= 0)
{
ExchangeFoundItemIndexIfLower(context, blockIndex + 1, ((long)(blockIndex + 1) << list._blockSizePow2BitShift) + index);
return;
}
else if (index >= 0)
else if (context._isItemFound)
{
context._isItemFound = true;
context.Lock();
context.FoundItemIndex = ((long)(blockIndex + 1) << list._blockSizePow2BitShift) + index;
context.Unlock();
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 (context._isItemFound)
if (index >= 0 && (!context._isItemFound || index < context.FoundItemIndex))
{
ExchangeFoundItemIndexIfLower(context, blockIndex + 2, ((long)(blockIndex + 2) << list._blockSizePow2BitShift) + index);
return;
}
else if (context._isItemFound)
{
return;
}
else if (index >= 0)
{
context._isItemFound = true;
context.Lock();
context.FoundItemIndex = ((long)(blockIndex + 2) << list._blockSizePow2BitShift) + index;
context.Unlock();
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)
{
context._isItemFound = true;
context.Lock();
context.FoundItemIndex = ((long)(blockIndex + 3) << list._blockSizePow2BitShift) + index;
context.Unlock();
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)
{
@@ -81,6 +129,21 @@ namespace Recyclable.Collections
}
}
[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<T> list, int blockIndex, T itemToFind)
{
@@ -94,10 +157,7 @@ namespace Recyclable.Collections
long index = Array.IndexOf(list._memoryBlocks[blockIndex], itemToFind, 0, list._blockSize);
if (!context._isItemFound && index >= 0)
{
context._isItemFound = true;
context.Lock();
context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + index;
context.Unlock();
ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + index);
}
}
catch (Exception e)
@@ -110,7 +170,27 @@ namespace Recyclable.Collections
}
}
#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<T> list, T item)
@@ -146,7 +226,7 @@ namespace Recyclable.Collections
// Task.Factory.StartNew(() =>
context.AddParticipant();
var startingBlock = blockIndex;
_ = Task.Run(() => IndexOfIn4ConsecutiveBlocks(context, list, startingBlock, item));
_ = Task.Run(() => IndexOfInConsecutiveBlocks(context, list, startingBlock, item));
// & WAS SLOWER
// ScheduleIndexOfTask(context, list, blockIndex, item);
@@ -185,10 +265,7 @@ namespace Recyclable.Collections
int itemIndex = Array.IndexOf(list._memoryBlocks[list._lastBlockWithData], item, 0, list._nextItemIndex > 0 ? list._nextItemIndex : list._blockSize);
if (!context._isItemFound && itemIndex >= 0)
{
context._isItemFound = true;
context.Lock();
context.FoundItemIndex = ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex;
context.Unlock();
ExchangeFoundItemIndexIfLower(context, blockIndex, ((long)blockIndex << list._blockSizePow2BitShift) + itemIndex);
}
}