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!"); 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, ICommandExecution? Execute(CallRequest command, IInstanceRepository instanceRepository,
IRepresentationModule representationModule, IEndPointContext context); 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) void PostCallMessage<T>(RequestId id, EMessageType eMessageType, T payload, IEndPointContext? context)
where T : notnull; 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; where T : notnull;
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@ namespace mROA.Abstract
public interface IRequestExtractor public interface IRequestExtractor
{ {
Task StartExtraction(); Task StartExtraction();
void PushMessage(object parced, EMessageType originalType); void PushMessage(object parsed, EMessageType originalType);
Predicate<NetworkMessage> Rule { get; } Predicate<NetworkMessage> Rule { get; }
Func<NetworkMessage, Type?>[] Converters { get; } Func<NetworkMessage, Type?>[] Converters { get; }
} }
@@ -79,7 +79,7 @@ namespace mROA.Implementation.Backend
switch (connectionRequest.MessageType) switch (connectionRequest.MessageType)
{ {
case EMessageType.ClientConnect: case EMessageType.ClientConnect:
HandleNewClient(context, interaction, streamExtractor, cts, connectionRequest); HandleNewClient(context, interaction, streamExtractor, cts, connectionRequest);
break; break;
case EMessageType.ClientRecovery: case EMessageType.ClientRecovery:
@@ -94,7 +94,8 @@ namespace mROA.Implementation.Backend
} }
private void HandleNewClient(EndPointContext context, ChannelInteractionModule interaction, 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.HostId = 0;
context.OwnerId = -interaction.ConnectionId; context.OwnerId = -interaction.ConnectionId;
@@ -126,8 +127,8 @@ namespace mROA.Implementation.Backend
{ {
var func = converters[i]; var func = converters[i];
if (func(message) is not { } t) continue; 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)); Task.Run(() => requestExtractor.PushMessage(deserialized, message.MessageType));
break; break;
} }
-2
View File
@@ -2,8 +2,6 @@
namespace mROA.Implementation namespace mROA.Implementation
{ {
public struct CallRequest public struct CallRequest
{ {
public RequestId Id { get; set; } public RequestId Id { get; set; }
@@ -153,16 +153,16 @@ namespace mROA.Implementation
} }
public Action<NetworkMessage> MessageReceived = _ => { }; public Action<NetworkMessage> MessageReceived = _ => { };
public async Task SingleReceive(CancellationToken token = default) public async Task SingleReceive(CancellationToken token = default)
{ {
var firstRead = await _ioStream.ReadAsync(_buffer, token); var firstRead = await _ioStream.ReadAsync(_buffer, token);
var meta = MemoryMarshal.Read<NetworkMessage.NetworkMessageMeta>(_buffer.Span); var meta = MemoryMarshal.Read<NetworkMessage.NetworkMessageMeta>(_buffer.Span);
var len = meta.BodyLength; var len = meta.BodyLength;
var readLen = firstRead - 19; var readLen = firstRead - 19;
if (readLen != len) if (readLen != len)
{ {
var lastPart = _buffer[firstRead..(len + 19)]; var lastPart = _buffer[firstRead..(len + 19)];
@@ -191,9 +191,9 @@ namespace mROA.Implementation
message.Data.CopyTo(_buffer.Span[19..]); message.Data.CopyTo(_buffer.Span[19..]);
var sendingSpan = _buffer[..(19 + meta.BodyLength)]; var sendingSpan = _buffer[..(19 + meta.BodyLength)];
await _ioStream.WriteAsync(sendingSpan, token); await _ioStream.WriteAsync(sendingSpan, token);
#if TRACE #if TRACE
Console.WriteLine("SEND " + message); Console.WriteLine("SEND " + message);
#endif #endif
// _logger.LogTrace("SEND {0}", message.ToString()); // _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) switch (originalType)
{ {
case EMessageType.CallRequest: case EMessageType.CallRequest:
HandleCallRequest((CallRequest)parced); HandleCallRequest((CallRequest)parsed);
break; break;
case EMessageType.ClientDisconnect: case EMessageType.ClientDisconnect:
return; return;
case EMessageType.EventRequest: case EMessageType.EventRequest:
HandleEventRequest((CallRequest)parced); HandleEventRequest((CallRequest)parsed);
break; break;
case EMessageType.CancelRequest: case EMessageType.CancelRequest:
HandleCancelRequest((CancelRequest)parced); HandleCancelRequest((CancelRequest)parsed);
break; break;
default: default:
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException();
@@ -76,7 +76,7 @@ namespace mROA.Implementation.Frontend
private void HandleCallRequest(CallRequest request) private void HandleCallRequest(CallRequest request)
{ {
var result = _executeModule.Execute(request, _context.RealRepository, _representationModule, _context); var result = _executeModule.Execute(request, _context.RealRepository, _representationModule, _context);
if (result is null) if (result is null)
{ {
return; return;
+2 -1
View File
@@ -37,6 +37,7 @@ namespace mROA.Implementation
public byte[] Data { get; set; } public byte[] Data { get; set; }
public object Serialized { get; set; } public object Serialized { get; set; }
public IEndPointContext Context { get; set; } public IEndPointContext Context { get; set; }
public override string ToString() public override string ToString()
{ {
return $" {Id}:{MessageType} [{Data.Length}]"; return $" {Id}:{MessageType} [{Data.Length}]";
@@ -51,7 +52,7 @@ namespace mROA.Implementation
Id = Id Id = Id
}; };
} }
public struct NetworkMessageMeta public struct NetworkMessageMeta
{ {
public RequestId Id; public RequestId Id;
+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) 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, public static ValueTask<int> ReadExactlyAsync(this Stream stream, Memory<byte> buffer,