S>Читал про Span<T> но не понял как его применить для задачи, если он вообще применим для такого.
Span<T> это только тип, к нему ещё идут всякие классы, которые позволяют что-то полезное с этим Span делать. Посмотри на документацию к MemoryMarshal:
struct S
{
int N;
}
public void Set10()
{
S[] items = new S[1];
var span = new Span<S>(items);
var bytesSpan = MemoryMarshal.AsBytes(span);
bytesSpan[0] = 10;
Здравствуйте, hi_octane, Вы писали:
S>>Читал про Span<T> но не понял как его применить для задачи, если он вообще применим для такого. _>Span<T> это только тип, к нему ещё идут всякие классы, которые позволяют что-то полезное с этим Span делать. Посмотри на документацию к MemoryMarshal: _>
_> struct S
_> {
_> int N;
_> }
_> public void Set10()
_> {
_> S[] items = new S[1];
_> var span = new Span<S>(items);
_> var bytesSpan = MemoryMarshal.AsBytes(span);
_> bytesSpan[0] = 10;
_>
Как-то не очень понятно как сделать с помощью этого класса memcpy (из байтов в структуру) и будет ли это быстрее чем работая с fixed указателем?
S>Как-то не очень понятно как сделать с помощью этого класса memcpy (из байтов в структуру)
Ты документацию читал? Методы Span<T>.CopyTo() и MemoryMarshal.Cast как бы есть...
Но главный вопрос — зачем делать memcpy? На Span-ах уже давное делают модное zero-copy. Берёшь массив байтов, читаешь как массив структур, пользуешься.
S>и будет ли это быстрее чем работая с fixed указателем?
Ну сделай бенчмарк. Утвержают что должно быть быстрее. На моих сценариях по скорости плюс-минус также, но эстетически лучше — всё типа safe, нету работы с указателями. Ну и Span-ы сделали удобной работу со stackalloc.
Здравствуйте, hi_octane, Вы писали:
S>>Как-то не очень понятно как сделать с помощью этого класса memcpy (из байтов в структуру) _>Ты документацию читал? Методы Span<T>.CopyTo() и MemoryMarshal.Cast как бы есть...
Я читал. Мне сложно пока представить на своей задаче. Вроде понятно как работает, что не копирует, а мы рефами на стек работаем. Но что-то не могу понять дойти до своего кейса.
_>Но главный вопрос — зачем делать memcpy? На Span-ах уже давное делают модное zero-copy. Берёшь массив байтов, читаешь как массив структур, пользуешься.
Интересно. А если в структуре динамический массив другой структуры? Тоесть первые два байта под длину массива, а дальше сам массив. Такое уже не описать полями декларативно. Как здесь решает Спан? Допустим длину я еще упакую в поле short. А дальше, как мне получить ссылку на массив? В случае с поинтерами я просто делаю
var bytes = count * sizeof(InnerStruct);
var inner = new InnerStruct[count];
fixed (void* innerMap = &inner[0])
Buffer.MemoryCopy(map + offset, innerMap, bytes, bytes);
S>Интересно. А если в структуре динамический массив другой структуры? Тоесть первые два байта под длину массива, а дальше сам массив. Такое уже не описать полями декларативно. Как здесь решает Спан? Допустим длину я еще упакую в поле short. А дальше, как мне получить ссылку на массив? В случае с поинтерами я просто делаю
S>
struct S
{
public int x;
}
void MapSpan()
{
byte[] bytes = new byte[100];
//принципиально делаем всё на спанах
Span<byte> bytesSpan = bytes;
//допустим первые 2 байта это количествоint count = MemoryMarshal.Read<short>(bytesSpan.Slice(0, 2));
Span<S> structsSpan = MemoryMarshal.Cast<byte, S>(bytesSpan.Slice(2));
structsSpan[0].x = 10;
}
По ощущению от вопросов, у тебя задача не быстренько обработать какой-то прилетающий массив, а хочется получить чуть ли не враппер вокруг массива байт, который позволит с этим массивом работать как со сложной структурой. Если я угадал, то тебе пригодится ещё тип Memory<T>, который как спан, только обычная структура.
Спасибо, буду пробовать.
_>По ощущению от вопросов, у тебя задача не быстренько обработать какой-то прилетающий массив, а хочется получить чуть ли не враппер вокруг массива байт, который позволит с этим массивом работать как со сложной структурой. Если я угадал, то тебе пригодится ещё тип Memory<T>, который как спан, только обычная структура.
Хочется понять для себя упростят ли код новые вещи. Без потери производительности. Скорость не влияет, текущая удовлетворяет. А вот читабельность страдает. Но со спанами вот смотрю и думаю — как-то не сильно и лучше. Это все расширение языка, а значит много-много вызовов методов.
Здравствуйте, Sаныч, Вы писали:
S>Появился ли в последний версия языка что-то для сабжа более правильный с т.з. дизайна кода? Сейчас делаю так
А какую задачу решаем ?
Трактовать структуру как набор байтов ?
Здравствуйте, Sаныч, Вы писали:
S>Хочется понять для себя упростят ли код новые вещи. Без потери производительности. Скорость не влияет, текущая удовлетворяет. А вот читабельность страдает. Но со спанами вот смотрю и думаю — как-то не сильно и лучше. Это все расширение языка, а значит много-много вызовов методов.
Если весь небезопасный код состоит в том, чтобы вызвать fixed и скопировать в структуру, то тут ничего менять не нужно.
Можно MemoryMarshal.Read задействовать если не хочется unsafe в коде явно.
using System.Runtime.InteropServices;
byte[] _readBuffer = new byte[100];
NativeStruct ns = MemoryMarshal.Read<NativeStruct>(_readBuffer);
[StructLayout(LayoutKind.Explicit)]
struct NativeStruct
{
[FieldOffset(0)]
public int Field1;
[FieldOffset(4)]
public uint Field2;
// дальше
}
Здравствуйте, Sаныч, Вы писали:
S>Появился ли в последний версия языка что-то для сабжа более правильный с т.з. дизайна кода? Сейчас делаю так
S>
S>[StructLayout(LayoutKind.Explicit)]
S>struct NativeStruct
S>{
S> [FieldOffset(0)]
S> public int Field1;
S> [FieldOffset(4)]
S> public uint Field2;
S> // дальше
S>}
S>fixed (byte* map = &_readBuffer[0])
S>{
S> return *(NativeStruct*)map;
S>}
S>
Этот подход — небезопасный и медленный. Вот эта строчка: return *(NativeStruct*)map; означает "скопируй мне 8 байт из _readBuffer в стек", и потом ещё "скопируй мне эти 8 байт туда, где будет выполнен возврат". Детали зависят от множества вещей вроде размера NativeStruct и конкретной версии JIT. S>Читал про Span<T> но не понял как его применить для задачи, если он вообще применим для такого.
Чтобы понять, можно ли применить Span<T> нужно подняться на пару уровней выше над *(NativeStruct*)map. Что именно вы делаете потом?
Span нужен для того, чтобы, к примеру, с нулевым оверхедом превратить "массив байт" в "массив NativeStruct".
Вот, например, такой код вызывает полноценное копирование 8 байт минимум дважды (а, может быть, и трижды):
private byte[] _readBuffer = new byte[80];
public void Test1()
{
var t = Foo().Field2;
Console.WriteLine(t);
}
public unsafe NativeStruct Foo()
{
fixed (byte* map = &_readBuffer[0])
{
return *(NativeStruct*)map;
}
}
Это выглядит расточительным, если вам нужны только 4 байта Field2.
А вот такой код — только однократное копирование 4х байт (и без unsafe):
public void Test2()
{
var t = Bar().Field2;
Console.WriteLine(t);
}
public ref NativeStruct Bar()
{
return ref Marshal.AsRef<NativeStruct>(_readBuffer.AsSpan());
}
private byte[] _readBuffer = new byte[80];
Если в _readBuffer на самом деле лежит целый массив NativeStruct, то Span позволит его отмаршалить с O(1) оверхедом:
public Span<NativeStruct> NativeStructs()
=> Marshal.Cast<byte, NativeStruct>(_readBuffer.AsSpan());
После этого вы сможете, к примеру, итерироваться по NativeStructs() без выделения памяти.
Но всё это зависит от того, что именно вы делаете.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.