Renaming and cleanup. Begin to use own small arena allocator
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,4 @@ using mROA.Benchmark;
|
|||||||
|
|
||||||
Console.WriteLine("Hello, World!");
|
Console.WriteLine("Hello, World!");
|
||||||
|
|
||||||
BenchmarkRunner.Run<IdGeneration>();
|
BenchmarkRunner.Run<ConcurrentAlloc>();
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user