Unsafe mem mapping
От: Sаныч Таиланд  
Дата: 27.02.24 07:19
Оценка: +1
Появился ли в последний версия языка что-то для сабжа более правильный с т.з. дизайна кода? Сейчас делаю так

[StructLayout(LayoutKind.Explicit)]
struct NativeStruct
{
    [FieldOffset(0)]
    public int Field1;

    [FieldOffset(4)]
    public uint Field2;
    
    // дальше
}

fixed (byte* map = &_readBuffer[0])
{
    return *(NativeStruct*)map;
}


Читал про Span<T> но не понял как его применить для задачи, если он вообще применим для такого.
Re: Unsafe mem mapping
От: hi_octane Беларусь  
Дата: 27.02.24 11:39
Оценка: 3 (1)
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;
Re[2]: Unsafe mem mapping
От: Sаныч Таиланд  
Дата: 27.02.24 12:17
Оценка:
Здравствуйте, 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 указателем?
Re[3]: Unsafe mem mapping
От: hi_octane Беларусь  
Дата: 27.02.24 14:43
Оценка:
S>Как-то не очень понятно как сделать с помощью этого класса memcpy (из байтов в структуру)
Ты документацию читал? Методы Span<T>.CopyTo() и MemoryMarshal.Cast как бы есть...

Но главный вопрос — зачем делать memcpy? На Span-ах уже давное делают модное zero-copy. Берёшь массив байтов, читаешь как массив структур, пользуешься.

S>и будет ли это быстрее чем работая с fixed указателем?

Ну сделай бенчмарк. Утвержают что должно быть быстрее. На моих сценариях по скорости плюс-минус также, но эстетически лучше — всё типа safe, нету работы с указателями. Ну и Span-ы сделали удобной работу со stackalloc.
Re[4]: Unsafe mem mapping
От: Sаныч Таиланд  
Дата: 27.02.24 15:06
Оценка:
Здравствуйте, 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);


Какое решение дает Спан?
Re[5]: Unsafe mem mapping
От: hi_octane Беларусь  
Дата: 27.02.24 16:20
Оценка: 17 (3)
S>Интересно. А если в структуре динамический массив другой структуры? Тоесть первые два байта под длину массива, а дальше сам массив. Такое уже не описать полями декларативно. Как здесь решает Спан? Допустим длину я еще упакую в поле short. А дальше, как мне получить ссылку на массив? В случае с поинтерами я просто делаю

S>
S>var bytes = count * sizeof(InnerStruct);
S>var inner = new InnerStruct[count];
S>fixed (void* innerMap = &inner[0])
S>    Buffer.MemoryCopy(map + offset, innerMap, bytes, bytes);
S>


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>, который как спан, только обычная структура.
Re[6]: Unsafe mem mapping
От: Sаныч Таиланд  
Дата: 27.02.24 16:27
Оценка:
Здравствуйте, hi_octane, Вы писали:

Спасибо, буду пробовать.

_>По ощущению от вопросов, у тебя задача не быстренько обработать какой-то прилетающий массив, а хочется получить чуть ли не враппер вокруг массива байт, который позволит с этим массивом работать как со сложной структурой. Если я угадал, то тебе пригодится ещё тип Memory<T>, который как спан, только обычная структура.


Хочется понять для себя упростят ли код новые вещи. Без потери производительности. Скорость не влияет, текущая удовлетворяет. А вот читабельность страдает. Но со спанами вот смотрю и думаю — как-то не сильно и лучше. Это все расширение языка, а значит много-много вызовов методов.
Re: Unsafe mem mapping
От: _NN_ www.nemerleweb.com
Дата: 28.02.24 06:53
Оценка:
Здравствуйте, Sаныч, Вы писали:

S>Появился ли в последний версия языка что-то для сабжа более правильный с т.з. дизайна кода? Сейчас делаю так

А какую задачу решаем ?
Трактовать структуру как набор байтов ?
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[7]: Unsafe mem mapping
От: _NN_ www.nemerleweb.com
Дата: 28.02.24 09:26
Оценка: 3 (1)
Здравствуйте, 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;

    // дальше
}
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Unsafe mem mapping
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.02.24 10:59
Оценка: 19 (3)
Здравствуйте, 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() без выделения памяти.

Но всё это зависит от того, что именно вы делаете.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 28.02.2024 14:19 Sinclair . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.