Запись байтового массива в Span и отправка его с памятью - PullRequest
0 голосов
/ 16 мая 2018

Я получаю буфер и хочу создать из него новый буфер (объединяющий байты с префиксом, префиксом и постфиксом) и затем отправить его в сокет.

Например: начальный буфер: "aaaa"
Конечный буфер: "$4\r\naaaa\r\n" (Redis RESP Protocol - Bulk Strings)

Как я могу преобразовать span в memory?(Я не знаю, должен ли я использовать stackalloc, учитывая тот факт, что я не знаю, насколько велик входной сигнал buffer. Я подумал, что это будет быстрее).

        private static readonly byte[] RESP_BULK_ID =BitConverter.GetBytes('$');
        private static readonly byte[] RESP_FOOTER = Encoding.UTF8.GetBytes("\r\n");


        static Memory<byte> GetNodeSpan(in ReadOnlyMemory<byte> payload) {

            ReadOnlySpan<byte> payloadHeader = BitConverter.GetBytes(payload.Length);

            Span<byte> result = stackalloc byte[
                                                    RESP_BULK_ID.Length +
                                                    payloadHeader.Length + 
                                                    RESP_FOOTER.Length + 
                                                    payload.Length + 
                                                    RESP_FOOTER.Length
                                                    ];
            Span<byte> cursor = result;

            RESP_BULK_ID.CopyTo(cursor);
            cursor=cursor.Slice(RESP_BULK_ID.Length);

            payloadHeader.CopyTo(cursor);
            cursor = cursor.Slice(payloadHeader.Length);

            RESP_FOOTER.CopyTo(cursor);
            cursor = cursor.Slice(RESP_FOOTER.Length);

            payload.Span.CopyTo(cursor);
            cursor = cursor.Slice(payload.Span.Length);

            RESP_FOOTER.CopyTo(cursor);

            return new Memory<byte>(result.AsBytes()) // ?can not convert from span to memory ,and cant return span because it can be referenced outside of scope
        }

PS: Должен ли яиспользуйте петли for старой школы вместо CopyTo?

1 Ответ

0 голосов
/ 17 мая 2018

Memory<T> предназначен для использования в качестве цели некоторого управляемого объекта (например, массива).Преобразование Memory<T> в Span<T> затем просто закрепляет целевой объект в памяти и использует его адрес для создания Span<T>.Но противоположное преобразование невозможно - поскольку Span<T> может указывать на часть памяти, которая не принадлежит ни одному из управляемых объектов (неуправляемая память, стек и т. Д.), Невозможно напрямую преобразовать Span<T> в Memory<T>.(На самом деле есть способ сделать это, но он включает в себя реализацию собственного MemoryManager<T>, аналогичного NativeMemoryManager , небезопасно и опасно, и я уверен, что это не то, что вам нужно).

Использование stackalloc является плохой идеей по двум причинам:

  1. Поскольку вы не знаете размер полезной нагрузки в Advace, вы можете легко получить StackOverflowException если полезная нагрузка слишком велика.

  2. (как уже предполагает комментарий в вашем исходном коде) Страшная идея пытаться вернуть что-то, выделенное в стеке текущегометод, так как это может привести к повреждению данных или к аварийному завершению работы приложения.

Единственный способ вернуть результат в стеке - это заранее вызвать вызывающую память от 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() использует оптимизированный метод, который может копировать несколько байтов одновременно, и (я твердо верю) использует специальные инструкции процессора для копирования блоков памяти и поэтому должен быть намного быстрее.

...