Skip to content

Commit

Permalink
DotNext.Threading 5.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sakno committed May 21, 2024
1 parent 021f439 commit d0a2508
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 27 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Release Notes
====

# 05-21-2024
<a href="https://www.nuget.org/packages/dotnext.threading/5.4.0">DotNext.Metaprogramming 5.4.0</a>
* Smallish performance improvements of `IndexPool` instance methods
* Added ability to instantiate empty `IndexPool`

# 05-15-2024
<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.3.1">DotNext.Metaprogramming 5.3.1</a>
* Fixed [234](https://github.com/dotnet/dotNext/issues/234)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ All these things are implemented in 100% managed code on top of existing .NET AP
* [NuGet Packages](https://www.nuget.org/profiles/rvsakno)

# What's new
Release Date: 05-15-2024
Release Date: 05-21-2024

<a href="https://www.nuget.org/packages/dotnext.metaprogramming/5.3.1">DotNext.Metaprogramming 5.3.1</a>
* Fixed [234](https://github.com/dotnet/dotNext/issues/234)
* Updated dependencies
<a href="https://www.nuget.org/packages/dotnext.threading/5.4.0">DotNext.Metaprogramming 5.4.0</a>
* Smallish performance improvements of `IndexPool` instance methods
* Added ability to instantiate empty `IndexPool`

Changelog for previous versions located [here](./CHANGELOG.md).

Expand Down
14 changes: 14 additions & 0 deletions src/DotNext.Tests/Collections/Concurrent/IndexPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ public static void EmptyPool()
Empty(pool);
}

[Fact]
public static void EmptyPool2()
{
var pool = new IndexPool() { IsEmpty = true };
False(pool.TryPeek(out _));
False(pool.TryTake(out _));
DoesNotContain(10, pool);
Empty(pool);

pool.Reset();
NotEmpty(pool);
Contains(10, pool);
}

[Fact]
public static void TakeAll()
{
Expand Down
58 changes: 36 additions & 22 deletions src/DotNext.Threading/Collections/Concurrent/IndexPool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace DotNext.Collections.Concurrent;
/// </remarks>
[EditorBrowsable(EditorBrowsableState.Advanced)]
[StructLayout(LayoutKind.Auto)]
public struct IndexPool : ISupplier<int>, IConsumer<int>, IReadOnlyCollection<int>
public struct IndexPool : ISupplier<int>, IConsumer<int>, IReadOnlyCollection<int>, IResettable
{
private readonly int maxValue;
private ulong bitmask;
Expand All @@ -39,13 +39,22 @@ public IndexPool()
/// </exception>
public IndexPool(int maxValue)
{
if (maxValue < 0 || maxValue > MaxValue)
if ((uint)maxValue > (uint)MaxValue)
throw new ArgumentOutOfRangeException(nameof(maxValue));

bitmask = ulong.MaxValue;
this.maxValue = maxValue;
}

/// <summary>
/// Gets or sets a value indicating that the pool is empty.
/// </summary>
public bool IsEmpty
{
readonly get => Count is 0;
init => bitmask = value ? 0UL : ulong.MaxValue;
}

/// <summary>
/// Gets the maximum number that can be returned by the pool.
/// </summary>
Expand Down Expand Up @@ -109,28 +118,28 @@ public int Take()
}

/// <summary>
/// Takes all available indicies, atomically.
/// Takes all available indices, atomically.
/// </summary>
/// <param name="indicies">
/// The buffer to be modified with the indicies taken from the pool.
/// <param name="indices">
/// The buffer to be modified with the indices taken from the pool.
/// The size of the buffer should not be less than <see cref="Capacity"/>.
/// </param>
/// <returns>The number of indicies written to the buffer.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="indicies"/> is too small to place indicies.</exception>
/// <returns>The number of indices written to the buffer.</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="indices"/> is too small to place indices.</exception>
/// <seealso cref="Return(ReadOnlySpan{int})"/>
public int Take(Span<int> indicies)
public int Take(Span<int> indices)
{
if (indicies.Length < Capacity)
throw new ArgumentOutOfRangeException(nameof(indicies));
if (indices.Length < Capacity)
throw new ArgumentOutOfRangeException(nameof(indices));

var oldValue = Interlocked.Exchange(ref bitmask, 0UL);
var bufferOffset = 0;

for (int bitPosition = 0; bitPosition < Capacity; bitPosition++)
for (var bitPosition = 0; bitPosition < Capacity; bitPosition++)
{
if (Contains(oldValue, bitPosition))
{
indicies[bufferOffset++] = bitPosition;
indices[bufferOffset++] = bitPosition;
}
}

Expand All @@ -148,7 +157,7 @@ public int Take(Span<int> indicies)
/// value specified for this pool.</exception>
public void Return(int value)
{
if (value < 0 || value > maxValue)
if ((uint)value > (uint)maxValue)
ThrowArgumentOutOfRangeException();

Interlocked.Or(ref bitmask, 1UL << value);
Expand All @@ -160,21 +169,26 @@ static void ThrowArgumentOutOfRangeException()
}

/// <summary>
/// Returns multiple indicies, atomically.
/// Returns multiple indices, atomically.
/// </summary>
/// <param name="indicies">The buffer of indicies to return back to the pool.</param>
public void Return(ReadOnlySpan<int> indicies)
/// <param name="indices">The buffer of indices to return back to the pool.</param>
public void Return(ReadOnlySpan<int> indices)
{
var newValue = 0UL;

foreach (var index in indicies)
foreach (var index in indices)
{
newValue |= 1UL << index;
}

Interlocked.Or(ref bitmask, newValue);
}

/// <summary>
/// Returns all values to the pool.
/// </summary>
public void Reset() => Volatile.Write(ref bitmask, ulong.MaxValue);

/// <inheritdoc/>
void IConsumer<int>.Invoke(int value) => Return(value);

Expand All @@ -184,20 +198,20 @@ public void Return(ReadOnlySpan<int> indicies)
/// <param name="value">The value to check.</param>
/// <returns><see langword="true"/> if <paramref name="value"/> is available for rent; otherwise, <see langword="false"/>.</returns>
public readonly bool Contains(int value)
=> value >= 0 && value <= maxValue && Contains(Volatile.Read(in bitmask), value);
=> (uint)value <= (uint)maxValue && Contains(Volatile.Read(in bitmask), value);

private static bool Contains(ulong bitmask, int index)
=> (bitmask & (1UL << index)) is not 0UL;

/// <summary>
/// Gets the number of available indicies.
/// Gets the number of available indices.
/// </summary>
public readonly int Count => Math.Min(BitOperations.PopCount(bitmask), maxValue + 1);

/// <summary>
/// Gets an enumerator over available indicies in the pool.
/// Gets an enumerator over available indices in the pool.
/// </summary>
/// <returns>The enumerator over available indicies in this pool.</returns>
/// <returns>The enumerator over available indices in this pool.</returns>
public readonly Enumerator GetEnumerator() => new(Volatile.Read(in bitmask), maxValue);

/// <inheritdoc/>
Expand All @@ -207,7 +221,7 @@ private static bool Contains(ulong bitmask, int index)
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator().AsClassicEnumerator();

/// <summary>
/// Represents an enumerator over available indicies in the pool.
/// Represents an enumerator over available indices in the pool.
/// </summary>
[StructLayout(LayoutKind.Auto)]
public struct Enumerator
Expand Down
2 changes: 1 addition & 1 deletion src/DotNext.Threading/DotNext.Threading.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ImplicitUsings>true</ImplicitUsings>
<IsTrimmable>true</IsTrimmable>
<Features>nullablePublicOnly</Features>
<VersionPrefix>5.3.1</VersionPrefix>
<VersionPrefix>5.4.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<Authors>.NET Foundation and Contributors</Authors>
<Product>.NEXT Family of Libraries</Product>
Expand Down

0 comments on commit d0a2508

Please sign in to comment.