Renaming and cleanup. Begin to use own small arena allocator

This commit is contained in:
2025-08-07 18:52:31 +03:00
parent 37f5d8a198
commit 89a2bee8d2
14 changed files with 280 additions and 23 deletions
+150
View File
@@ -0,0 +1,150 @@
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);
}
}
+1 -1
View File
@@ -5,4 +5,4 @@ using mROA.Benchmark;
Console.WriteLine("Hello, World!");
BenchmarkRunner.Run<IdGeneration>();
BenchmarkRunner.Run<ConcurrentAlloc>();
+34
View File
@@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
using mROA.Implementation;
namespace mROA.Test;
[TestFixture]
public class ConcurrentTest
{
private CircularMemoryManager _cmm;
[SetUp]
public void Setup()
{
_cmm = new CircularMemoryManager(100);
}
[Test]
public void ParallelAlloc()
{
Parallel.For(0, 100, i =>
{
var data = _cmm.AllocSlice(1);
data.Span[0] = 1;
});
var total = _cmm.AllocSlice(100).Span;
if (total.IndexOf((byte)0) == -1)
{
Assert.Pass();
}
Assert.Fail();
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace mROA.Abstract
{
ICommandExecution? Execute(CallRequest command, IInstanceRepository instanceRepository,
IRepresentationModule representationModule, IEndPointContext context);
ICommandExecution Cancel(CancelRequest command);
ICommandExecution Cancel(CancelRequest command);
}
}
+2 -1
View File
@@ -25,7 +25,8 @@ namespace mROA.Abstract
void PostCallMessage<T>(RequestId id, EMessageType eMessageType, T payload, IEndPointContext? context)
where T : notnull;
Task PostCallMessageUntrustedAsync<T>(RequestId id, EMessageType eMessageType, T payload, IEndPointContext? context)
Task PostCallMessageUntrustedAsync<T>(RequestId id, EMessageType eMessageType, T payload,
IEndPointContext? context)
where T : notnull;
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace mROA.Abstract
public interface IRequestExtractor
{
Task StartExtraction();
void PushMessage(object parced, EMessageType originalType);
void PushMessage(object parsed, EMessageType originalType);
Predicate<NetworkMessage> Rule { get; }
Func<NetworkMessage, Type?>[] Converters { get; }
}
@@ -94,7 +94,8 @@ namespace mROA.Implementation.Backend
}
private void HandleNewClient(EndPointContext context, ChannelInteractionModule interaction,
ChannelInteractionModule.StreamExtractor streamExtractor, CancellationTokenSource cts, NetworkMessage connection)
ChannelInteractionModule.StreamExtractor streamExtractor, CancellationTokenSource cts,
NetworkMessage connection)
{
context.HostId = 0;
context.OwnerId = -interaction.ConnectionId;
@@ -127,7 +128,7 @@ namespace mROA.Implementation.Backend
var func = converters[i];
if (func(message) is not { } t) continue;
var deserialized = _serialization.Deserialize(message.Data, t, context);
var deserialized = _serialization.Deserialize(message.Data, t, context)!;
Task.Run(() => requestExtractor.PushMessage(deserialized, message.MessageType));
break;
}
-2
View File
@@ -2,8 +2,6 @@
namespace mROA.Implementation
{
public struct CallRequest
{
public RequestId Id { get; set; }
@@ -191,9 +191,9 @@ namespace mROA.Implementation
message.Data.CopyTo(_buffer.Span[19..]);
var sendingSpan = _buffer[..(19 + meta.BodyLength)];
await _ioStream.WriteAsync(sendingSpan, token);
#if TRACE
#if TRACE
Console.WriteLine("SEND " + message);
#endif
#endif
// _logger.LogTrace("SEND {0}", message.ToString());
}
@@ -0,0 +1,60 @@
using System;
using System.Threading;
namespace mROA.Implementation
{
public class CircularMemoryManager
{
private readonly Memory<byte> _buffer;
private Memory<byte> _current;
private SpinLock _spinLock = new(false);
public CircularMemoryManager(int size)
{
_buffer = new Memory<byte>(new byte[size]);
_current = _buffer;
}
public Span<byte> AllocSlice(int size)
{
Span<byte> order;
var lockTaken = false;
try
{
_spinLock.Enter(ref lockTaken);
if (_current.Length < size)
_current = _buffer;
order = _current.Span[..size];
_current = _current[size..];
}
finally
{
if (lockTaken) _spinLock.Exit(false);
}
return order;
}
public Memory<byte> AllocMemory(int size)
{
Memory<byte> order;
var lockTaken = false;
try
{
_spinLock.Enter(ref lockTaken);
if (_current.Length < size)
_current = _buffer;
order = _current[..size];
_current = _current[size..];
}
finally
{
if (lockTaken) _spinLock.Exit(false);
}
return order;
}
}
}
@@ -0,0 +1,11 @@
using System;
using mROA.Abstract;
namespace mROA.Implementation.CommandExecution
{
public class AsyncCommandExecution : ICommandExecution
{
public RequestId Id { get; set; }
public EMessageType MessageType => EMessageType.Unknown;
}
}
@@ -36,20 +36,20 @@ namespace mROA.Implementation.Frontend
}
}
public void PushMessage(object parced, EMessageType originalType)
public void PushMessage(object parsed, EMessageType originalType)
{
switch (originalType)
{
case EMessageType.CallRequest:
HandleCallRequest((CallRequest)parced);
HandleCallRequest((CallRequest)parsed);
break;
case EMessageType.ClientDisconnect:
return;
case EMessageType.EventRequest:
HandleEventRequest((CallRequest)parced);
HandleEventRequest((CallRequest)parsed);
break;
case EMessageType.CancelRequest:
HandleCancelRequest((CancelRequest)parced);
HandleCancelRequest((CancelRequest)parsed);
break;
default:
throw new ArgumentOutOfRangeException();
+1
View File
@@ -37,6 +37,7 @@ namespace mROA.Implementation
public byte[] Data { get; set; }
public object Serialized { get; set; }
public IEndPointContext Context { get; set; }
public override string ToString()
{
return $" {Id}:{MessageType} [{Data.Length}]";
+2 -1
View File
@@ -9,7 +9,8 @@ namespace mROA
{
public static async ValueTask<int> ReadExactlyAsync(this Stream stream, byte[] buffer, int offset, int count)
{
return await stream.ReadAtLeastAsyncCore(buffer.AsMemory(offset, count), count, true, CancellationToken.None);
return await stream.ReadAtLeastAsyncCore(buffer.AsMemory(offset, count), count, true,
CancellationToken.None);
}
public static ValueTask<int> ReadExactlyAsync(this Stream stream, Memory<byte> buffer,