150 lines
4.5 KiB
C#
150 lines
4.5 KiB
C#
using System.Formats.Cbor;
|
|
using BenchmarkDotNet.Attributes;
|
|
using BenchmarkDotNet.Jobs;
|
|
using mROA.Implementation;
|
|
|
|
namespace mROA.Benchmark;
|
|
|
|
[MemoryDiagnoser]
|
|
public class ConcurrentAlloc
|
|
{
|
|
private readonly CircularMemoryManager _cmm = new(1024);
|
|
private readonly FastCircularMemoryManager _fmm = new();
|
|
private CborWriter _writer = new();
|
|
|
|
[GlobalSetup]
|
|
public void Setup()
|
|
{
|
|
_writer.WriteStartArray(2);
|
|
_writer.WriteByteString([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
_writer.WriteStartArray(2);
|
|
_writer.WriteInt32(12);
|
|
_writer.WriteTextString("tralala 7EBC1458-BB53-49EA-84C9-EFECC0FC08FD");
|
|
_writer.WriteEndArray();
|
|
_writer.WriteEndArray();
|
|
}
|
|
|
|
[Benchmark(Baseline = true)]
|
|
public int CircularAllocate()
|
|
{
|
|
var writer = _writer;
|
|
var buffer = _cmm.AllocSlice(writer.BytesWritten);
|
|
writer.Encode(buffer);
|
|
|
|
return buffer.Length;
|
|
}
|
|
|
|
[Benchmark]
|
|
public int CircularMemoryAllocate()
|
|
{
|
|
var writer = _writer;
|
|
var buffer = _cmm.AllocMemory(writer.BytesWritten);
|
|
writer.Encode(buffer.Span);
|
|
return buffer.Length;
|
|
}
|
|
|
|
[Benchmark]
|
|
public int FastCircularAllocate()
|
|
{
|
|
var writer = _writer;
|
|
var buffer = _fmm.Alloc(writer.BytesWritten);
|
|
writer.Encode(buffer);
|
|
return buffer.Length;
|
|
}
|
|
|
|
[Benchmark]
|
|
public int FastMemoryCircularAllocate()
|
|
{
|
|
var writer = _writer;
|
|
var buffer = _fmm.AllocMem(writer.BytesWritten);
|
|
writer.Encode(buffer.Span);
|
|
return buffer.Length;
|
|
}
|
|
|
|
[Benchmark]
|
|
public int HeapAllocate()
|
|
{
|
|
var writer = _writer;
|
|
var encoded = writer.Encode();
|
|
return encoded.Length;
|
|
}
|
|
}
|
|
|
|
public class FastCircularMemoryManager
|
|
{
|
|
private readonly byte[] _buffer;
|
|
private long _offset; // atomic offset (in bytes)
|
|
private readonly int _mask; // если размер степени двойки — можно использовать маску
|
|
|
|
public FastCircularMemoryManager(int size = 4096) // 4KB buffer
|
|
{
|
|
if (!IsPowerOfTwo(size))
|
|
throw new ArgumentException("Size should be power of two for performance.", nameof(size));
|
|
|
|
_buffer = new byte[size];
|
|
_offset = 0;
|
|
_mask = size - 1; // для быстрого циклического сдвига: (offset & _mask)
|
|
}
|
|
|
|
private static bool IsPowerOfTwo(int x) => x > 0 && (x & (x - 1)) == 0;
|
|
|
|
public Span<byte> Alloc(int size)
|
|
{
|
|
if (size > _buffer.Length)
|
|
return new byte[size]; // fallback
|
|
|
|
long oldOffset, newOffset;
|
|
int start;
|
|
|
|
// Atomic "bump pointer" с циклическим переполнением
|
|
do
|
|
{
|
|
oldOffset = Volatile.Read(ref _offset);
|
|
start = (int)(oldOffset & _mask);
|
|
|
|
// Проверяем, не пересекает ли выделение границу буфера
|
|
if (start + size > _buffer.Length)
|
|
{
|
|
// Переполнение — обнуляем (циклический буфер)
|
|
newOffset = size; // сбрасываем на начало + size
|
|
start = 0;
|
|
}
|
|
else
|
|
{
|
|
newOffset = oldOffset + size;
|
|
}
|
|
} while (Interlocked.CompareExchange(ref _offset, newOffset, oldOffset) != oldOffset);
|
|
|
|
return _buffer.AsSpan(start, size);
|
|
}
|
|
|
|
public Memory<byte> AllocMem(int size)
|
|
{
|
|
if (size > _buffer.Length)
|
|
return new byte[size]; // fallback
|
|
|
|
long oldOffset, newOffset;
|
|
int start;
|
|
|
|
// Atomic "bump pointer" с циклическим переполнением
|
|
do
|
|
{
|
|
oldOffset = Volatile.Read(ref _offset);
|
|
start = (int)(oldOffset & _mask);
|
|
|
|
// Проверяем, не пересекает ли выделение границу буфера
|
|
if (start + size > _buffer.Length)
|
|
{
|
|
// Переполнение — обнуляем (циклический буфер)
|
|
newOffset = size; // сбрасываем на начало + size
|
|
start = 0;
|
|
}
|
|
else
|
|
{
|
|
newOffset = oldOffset + size;
|
|
}
|
|
} while (Interlocked.CompareExchange(ref _offset, newOffset, oldOffset) != oldOffset);
|
|
|
|
return _buffer.AsMemory(start, size);
|
|
}
|
|
} |