Skip to content

Commit

Permalink
struct RcRentedArray : IDispose -> ref struct RcRentedArray
Browse files Browse the repository at this point in the history
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.2605)
AMD Ryzen 5 3600, 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.100
  [Host]     : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
  DefaultJob : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2

| Method     | HashTableSize | Mean         | Error      | StdDev     | Median       | Gen0   | Allocated |
|----------- |-------------- |-------------:|-----------:|-----------:|-------------:|-------:|----------:|
| New        | 16            |     5.842 ns |  0.0500 ns |  0.0443 ns |     5.849 ns | 0.0182 |     152 B |
| Stackalloc | 16            |     4.142 ns |  0.0831 ns |  0.0777 ns |     4.112 ns |      - |         - |
| Rent       | 16            |    16.409 ns |  0.0244 ns |  0.0204 ns |    16.399 ns |      - |         - |
| New        | 256           |    50.550 ns |  1.0255 ns |  2.8245 ns |    49.036 ns | 0.2477 |    2072 B |
| Stackalloc | 256           |    65.315 ns |  0.2746 ns |  0.2293 ns |    65.259 ns |      - |         - |
| Rent       | 256           |    39.722 ns |  0.0638 ns |  0.0597 ns |    39.734 ns |      - |         - |
| New        | 1024          |   285.897 ns | 22.9065 ns | 67.5402 ns |   303.147 ns | 0.9813 |    8216 B |
| Stackalloc | 1024          |   261.509 ns |  0.3847 ns |  0.3410 ns |   261.528 ns |      - |         - |
| Rent       | 1024          |    87.780 ns |  0.1627 ns |  0.1359 ns |    87.752 ns |      - |         - |
| New        | 8192          | 1,156.367 ns |  9.6633 ns |  9.0390 ns | 1,156.284 ns | 7.8125 |   65560 B |
| Stackalloc | 8192          | 2,134.754 ns |  5.4929 ns |  4.8693 ns | 2,134.541 ns |      - |         - |
| Rent       | 8192          |   582.443 ns |  1.0532 ns |  0.9336 ns |   582.631 ns |      - |         - |
  • Loading branch information
ikpil committed Dec 14, 2024
1 parent 14c3423 commit 156e0f4
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 203 deletions.
119 changes: 31 additions & 88 deletions src/DotRecast.Core/Buffers/RcRentedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,130 +6,73 @@

namespace DotRecast.Core.Buffers
{
public readonly struct RcRentIdGen
public class RcRentedArray
{
public readonly int Id;
public readonly int Gen;
public static readonly RcRentedArray Shared = new RcRentedArray();

public RcRentIdGen(int id, int gen)
private RcRentedArray()
{
Id = id;
Gen = gen;
}
}

internal sealed class RcRentIdPool
{
private int[] _generations;
private readonly Queue<int> _freeIds;
private int _maxId;

public RcRentIdPool(int capacity)
{
_generations = new int[capacity];
_freeIds = new Queue<int>(capacity);
}

internal RcRentIdGen AcquireId()
public RcRentedArray<T> Rent<T>(int minimumLength)
{
if (!_freeIds.TryDequeue(out int id))
{
id = _maxId++;
if (_generations.Length <= id)
{
Array.Resize(ref _generations, _generations.Length << 1);
}
}

return new RcRentIdGen(id, _generations[id]);
return new RcRentedArray<T>(minimumLength);
}

internal void ReturnId(int id)
public void Return<T>(RcRentedArray<T> array)
{
_generations[id]++;
_freeIds.Enqueue(id);
}
if (array.IsDisposed)
return;

internal int GetGeneration(int id)
{
return _generations.Length <= id ? 0 : _generations[id];
array.Dispose();
}
}

public static class RcRentedArray
public ref struct RcRentedArray<T>
{
public const int START_RENT_ID_POOL_CAPACITY = 16;
private static readonly ThreadLocal<RcRentIdPool> _rentPool = new ThreadLocal<RcRentIdPool>(() => new RcRentIdPool(START_RENT_ID_POOL_CAPACITY));

public static RcRentedArray<T> Rent<T>(int minimumLength)
{
var array = ArrayPool<T>.Shared.Rent(minimumLength);
return new RcRentedArray<T>(ArrayPool<T>.Shared, _rentPool.Value.AcquireId(), array, minimumLength);
}
private readonly T[] _items;
public readonly int Length;
private bool _disposed;

internal static bool IsDisposed(RcRentIdGen rentIdGen)
{
return _rentPool.Value.GetGeneration(rentIdGen.Id) != rentIdGen.Gen;
}
public bool IsDisposed => _disposed;

internal static void ReturnId(RcRentIdGen rentIdGen)
internal RcRentedArray(int length)
{
_rentPool.Value.ReturnId(rentIdGen.Id);
}
}



public struct RcRentedArray<T> : IDisposable
{
private ArrayPool<T> _owner;
private T[] _array;
private readonly RcRentIdGen _rentIdGen;

public int Length { get; }
public bool IsDisposed => null == _owner || null == _array || RcRentedArray.IsDisposed(_rentIdGen);

internal RcRentedArray(ArrayPool<T> owner, RcRentIdGen rentIdGen, T[] array, int length)
{
_owner = owner;
_array = array;
Length = length;
_rentIdGen = rentIdGen;
_items = ArrayPool<T>.Shared.Rent(length);
_disposed = false;
}

public void Dispose()
{
if (null != _owner && null != _array && !RcRentedArray.IsDisposed(_rentIdGen))
{
RcRentedArray.ReturnId(_rentIdGen);
_owner.Return(_array, true);
}
if (_disposed)
return;

_owner = null;
_array = null;
_disposed = true;
ArrayPool<T>.Shared.Return(_items, true);
}

public ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);
if (IsDisposed)
throw new NullReferenceException();
return ref _array[index];
}
}
if (0 > index || Length <= index)
RcThrowHelper.ThrowExceptionIfIndexOutOfRange(index, Length);

if (_disposed)
RcThrowHelper.ThrowNullReferenceException("already disposed");

public T[] AsArray()
{
return _array;
return ref _items[index];
}
}

public Span<T> AsSpan()
{
return new Span<T>(_array, 0, Length);
if (_disposed)
RcThrowHelper.ThrowNullReferenceException("already disposed");

return new Span<T>(_items, 0, Length);
}
}
}
6 changes: 3 additions & 3 deletions test/DotRecast.Benchmark/BenchmarkProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ public static class BenchmarkProgram
public static int Main(string[] args)
{
var runs = ImmutableArray.Create(
BenchmarkConverter.TypeToBenchmarks(typeof(VectorBenchmarks)),
BenchmarkConverter.TypeToBenchmarks(typeof(PriorityQueueBenchmarks)),
BenchmarkConverter.TypeToBenchmarks(typeof(StackallocBenchmarks))
// BenchmarkConverter.TypeToBenchmarks(typeof(VectorBenchmarks)),
// BenchmarkConverter.TypeToBenchmarks(typeof(PriorityQueueBenchmarks)),
BenchmarkConverter.TypeToBenchmarks(typeof(ArrayBenchmarks))
);

var summary = BenchmarkRunner.Run(runs.ToArray());
Expand Down
82 changes: 82 additions & 0 deletions test/DotRecast.Benchmark/Benchmarks/ArrayBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using DotRecast.Core.Buffers;

namespace DotRecast.Benchmark.Benchmarks;

/*
BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.2605)
AMD Ryzen 5 3600, 1 CPU, 12 logical and 6 physical cores
.NET SDK 9.0.100
[Host] : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
DefaultJob : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2
| Method | HashTableSize | Mean | Error | StdDev | Median | Gen0 | Allocated |
|----------- |-------------- |-------------:|-----------:|-----------:|-------------:|-------:|----------:|
| New | 16 | 5.842 ns | 0.0500 ns | 0.0443 ns | 5.849 ns | 0.0182 | 152 B |
| Stackalloc | 16 | 4.142 ns | 0.0831 ns | 0.0777 ns | 4.112 ns | - | - |
| Rent | 16 | 16.409 ns | 0.0244 ns | 0.0204 ns | 16.399 ns | - | - |
| New | 256 | 50.550 ns | 1.0255 ns | 2.8245 ns | 49.036 ns | 0.2477 | 2072 B |
| Stackalloc | 256 | 65.315 ns | 0.2746 ns | 0.2293 ns | 65.259 ns | - | - |
| Rent | 256 | 39.722 ns | 0.0638 ns | 0.0597 ns | 39.734 ns | - | - |
| New | 1024 | 285.897 ns | 22.9065 ns | 67.5402 ns | 303.147 ns | 0.9813 | 8216 B |
| Stackalloc | 1024 | 261.509 ns | 0.3847 ns | 0.3410 ns | 261.528 ns | - | - |
| Rent | 1024 | 87.780 ns | 0.1627 ns | 0.1359 ns | 87.752 ns | - | - |
| New | 8192 | 1,156.367 ns | 9.6633 ns | 9.0390 ns | 1,156.284 ns | 7.8125 | 65560 B |
| Stackalloc | 8192 | 2,134.754 ns | 5.4929 ns | 4.8693 ns | 2,134.541 ns | - | - |
| Rent | 8192 | 582.443 ns | 1.0532 ns | 0.9336 ns | 582.631 ns | - | - |
*/

[MemoryDiagnoser]
public class ArrayBenchmarks
{
private readonly Consumer _consumer = new();

[Params(1 << 4, 1 << 8, 1 << 10, 1 << 13)]
public int HashTableSize;

[Benchmark]
public void New()
{
Span<long> hashTable = new long[HashTableSize];
_consumer.Consume(hashTable[0]);
}

[Benchmark]
public void Stackalloc()
{
Span<long> hashTable = stackalloc long[HashTableSize];
_consumer.Consume(hashTable[0]);
}

[Benchmark]
public void Rent()
{
using var hashTable = RcRentedArray.Shared.Rent<long>(HashTableSize);
_consumer.Consume(hashTable[0]);
}


// [Benchmark]
// [SkipLocalsInit]
// public void Stackalloc_Long_SkipLocalsInit()
// {
// Span<long> hashTable = stackalloc long[HashTableSize];
//
// _consumer.Consume(hashTable[0]);
// }


// [Benchmark]
// [SkipLocalsInit]
// public void New_Long_SkipLocalsInit()
// {
// Span<long> hashTable = new long[HashTableSize];
//
// _consumer.Consume(hashTable[0]);
// }
}
71 changes: 0 additions & 71 deletions test/DotRecast.Benchmark/Benchmarks/StackallocBenchmarks.cs

This file was deleted.

6 changes: 3 additions & 3 deletions test/DotRecast.Core.Test/RcArrayBenchmarkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ private void RoundForPureRentArray(int len)

private void RoundForRcRentedArray(int len)
{
using var rentedArray = RcRentedArray.Rent<int>(len);
var array = rentedArray.AsArray();
using var rentedArray = RcRentedArray.Shared.Rent<int>(len);
var array = rentedArray.AsSpan();
for (int i = 0; i < rentedArray.Length; ++i)
{
array[i] = _rand.NextInt32();
Expand Down Expand Up @@ -88,7 +88,7 @@ public void TestBenchmarkArrays()
var results = new List<(string title, long ticks)>();
results.Add(Bench("new int[len]", RoundForArray));
results.Add(Bench("ArrayPool<int>.Shared.Rent(len)", RoundForPureRentArray));
results.Add(Bench("RcRentedArray.Rent<int>(len)", RoundForRcRentedArray));
results.Add(Bench("RcRentedArray.Shared.Rent<int>(len)", RoundForRcRentedArray));
results.Add(Bench("new RcStackArray512<int>()", RoundForRcStackArray));
results.Add(Bench("stackalloc int[len]", RoundForStackalloc));

Expand Down
Loading

0 comments on commit 156e0f4

Please sign in to comment.