Запись байтового массива в Span и отправка его с памятью

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

Например: начальный буфер: "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 ответ

Решение

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

С помощью stackalloc плохая идея по двум причинам:

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

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

Другие вопросы по тегам