Memory<T>
предназначен для использования в качестве цели некоторого управляемого объекта (например, массива).Преобразование Memory<T>
в Span<T>
затем просто закрепляет целевой объект в памяти и использует его адрес для создания Span<T>
.Но противоположное преобразование невозможно - поскольку Span<T>
может указывать на часть памяти, которая не принадлежит ни одному из управляемых объектов (неуправляемая память, стек и т. Д.), Невозможно напрямую преобразовать Span<T>
в Memory<T>
.(На самом деле есть способ сделать это, но он включает в себя реализацию собственного MemoryManager<T>
, аналогичного NativeMemoryManager , небезопасно и опасно, и я уверен, что это не то, что вам нужно).
Использование stackalloc
является плохой идеей по двум причинам:
Поскольку вы не знаете размер полезной нагрузки в Advace, вы можете легко получить StackOverflowException
если полезная нагрузка слишком велика.
(как уже предполагает комментарий в вашем исходном коде) Страшная идея пытаться вернуть что-то, выделенное в стеке текущегометод, так как это может привести к повреждению данных или к аварийному завершению работы приложения.
Единственный способ вернуть результат в стеке - это заранее вызвать вызывающую память от GetNodeSpan
до stackalloc
.преобразуйте его в Span<T>
и передайте в качестве дополнительного аргумента.Проблема в том, что (1) вызывающий абонент GetNodeSpan
должен был бы знать, сколько выделить, и (2) не помог бы вам преобразовать Span<T>
в Memory<T>
.
. Таким образом, для сохранения результата вы будетенужен объект, выделенный в куче.Простое решение - просто выделить новый массив вместо stackalloc
.Такой массив можно затем использовать для построения Span<T>
(используется для копирования), а также Memory<T>
(используется как результат метода):
static Memory<byte> GetNodeSpan(in ReadOnlyMemory<byte> payload)
{
ReadOnlySpan<byte> payloadHeader = BitConverter.GetBytes(payload.Length);
byte[] result = new byte[RESP_BULK_ID.Length +
payloadHeader.Length +
RESP_FOOTER.Length +
payload.Length +
RESP_FOOTER.Length];
Span<byte> cursor = result;
// ...
return new Memory<byte>(result);
}
Очевидный недостаток заключается в том, что вам нужно выделить новый массивдля каждого вызова метода.Чтобы избежать этого, вы можете использовать пул памяти, где выделенные массивы используются повторно:
static IMemoryOwner<byte> GetNodeSpan(in ReadOnlyMemory<byte> payload)
{
ReadOnlySpan<byte> payloadHeader = BitConverter.GetBytes(payload.Length);
var result = MemoryPool<byte>.Shared.Rent(
RESP_BULK_ID.Length +
payloadHeader.Length +
RESP_FOOTER.Length +
payload.Length +
RESP_FOOTER.Length);
Span<byte> cursor = result.Memory.Span;
// ...
return result;
}
Обратите внимание, что это решение возвращает IMemoryOwner<byte>
(вместо Memory<T>
).Вызывающий может получить доступ к Memory<T>
с помощью свойства IMemoryOwner<T>.Memory
и должен вызвать IMemoryOwner<byte>.Dispose()
, чтобы вернуть массив обратно в пул, когда память больше не нужна.Второе, на что следует обратить внимание, это то, что MemoryPool<byte>.Shared.Rent()
может на самом деле возвращать массив, который длиннее требуемого минимума.Таким образом, ваш метод, вероятно, должен будет также возвращать фактическую длину результата (например, в качестве параметра out
), потому что IMemoryOwner<byte>.Memory.Length
может вернуть больше, чем было фактически скопировано в результат.
PS: я быожидайте, что цикл for
будет немного быстрее только для копирования очень коротких массивов (если вообще), где вы можете сохранить несколько циклов ЦП, избегая вызова метода.Но Span<T>.CopyTo()
использует оптимизированный метод, который может копировать несколько байтов одновременно, и (я твердо верю) использует специальные инструкции процессора для копирования блоков памяти и поэтому должен быть намного быстрее.