#c# #memory #parameters #unsafe #stackalloc
Вопрос:
Я пишу класс, который извлекает двоичные данные и поддерживает общее преобразование их в примитивные типы. Предполагается, что это должно быть максимально эффективно. Вот как это выглядит прямо сейчас:
public abstract class MemorySource {
public abstract Span<byte> ReadBytes(ulong address, int count);
public unsafe bool TryRead<T>(ulong address, out T result) where T : unmanaged {
Span<byte> buffer = ReadBytes(address, sizeof(T));
result = defau<
// If the above line is commented, `result = ref <...>` won't compile, showing CS0177.
if (!buffer.IsEmpty) {
result = ref Unsafe.As<byte, T>(ref buffer.GetPinnableReference());
return true;
} else
return false;
}
}
Так как я работаю с большим объемом памяти, и мой код будет выполнять множество небольших операций чтения. Я хочу свести к минимуму количество раз, когда память копируется.
ReadBytes
Реализация либо а) создаст промежуток между частью уже существующего массива в куче, либо б) stackalloc
создаст буфер и заполнит его данными из удаленного источника (в зависимости от данных, с которыми я буду работать). Дело в том, что он сам по себе ничего не будет выделять в куче.
Я хочу, чтобы мой TryRead<T>
метод возвращал типизированную ссылку на память span, а не копировал эту память в новое значение, и я хочу знать, возможно ли это. Я заметил, что не могу присвоить ref
значение out
аргументу без его инициализации, но я могу это сделать после, что имеет мало смысла, если предположить, что я назначаю ссылку.
Я предполагаю, что я спрашиваю, что на самом деле происходит в этом коде? Возвращаю ли я ссылку на существующее значение или это значение копируется в новое, выделенное стеком? И как будет отличаться поведение с распределенными по стеку и распределенными по куче промежутками? Будет ли GC знать, чтобы обновить ссылку типа T
при перемещении данных в случае использования выделенного для кучи промежутка?
Ответ №1:
Примитивные типы в любом случае являются типами значений, поэтому вам не следует беспокоиться о выделении в куче при их чтении.
Вы не можете использовать stackalloc
этот код, потому что вы не можете (или не должны пытаться) возвращать указатель на него, так как он будет уничтожен в конце функции.
Код, который у вас есть до сих пор, опасен, потому что вы возвращаете доступную ссылку, которая на самом деле не закреплена.
Причина, по которой у вас возникли проблемы с ref
параметром, заключается в том, что else
вы его вообще не назначаете. Вы должны переместить result = defau<
линию в else
ответвление.
В любом случае, вам гораздо лучше использовать MemoryMarshal
все это, обратите внимание, что для этого не требуется небезопасный код
public bool TryRead<T>(ulong address, out T result) where T : unmanaged
{
ReadOnlySpan<byte> buffer = ReadBytes(address, sizeof(T));
if (!buffer.IsEmpty)
{
result = MemoryMarshal.Read<T>(buffer);
return true;
}
result = defau<
return false;
}
Комментарии:
1. Хм, да, я только что попытался отладить этот код с помощью фиктивного чтения, и , хотя промежуток был правильно инициализирован
ReadBytes
, все содержимое промежутка0xFF
было за его пределами (я полагаю, что это то, что раньше было стеком метода), так что, похоже, то, о чем я думаю, невозможно… Думаю, мне тоже придется сделатьTryRead
абстракцию, чтобы наследующие классы отвечали за минимизацию операций копирования. Спасибо!2. Предположительно вы используете
Span
то, что было выделено в стеке. Как уже упоминалось, вы не можете этого сделать, потому что он будет стерт. Это должно быть изbyte[]
массива3. Я закончил тем, что абстрагировался
void*
, чтобы мне больше не приходилось беспокоиться о продолжительности жизни выделенных стеком промежутков.4.
void*
также опасно, что вы позволяете указателю выходить из-под контроля сбора мусора.Span
есть ли причина, по которой он остается под контролем ГК. Вы могли бы использоватьMemory
, может быть, с вами было бы проще иметь дело5. Да, идея заключается в том, что небезопасный код действительно используется только внутри этого класса, и рабочий код может работать с безопасными типами, такими как
Span
иMemory
. Я думаю, что эта новая реализация работает лучше всего: gist.github.com/TheLeftExit/68be0989ffbc77e43fe9c8a36c1b99ed