Информация об изменениях

Сообщение Re: Unsafe mem mapping от 28.02.2024 10:59

Изменено 28.02.2024 14:19 Sinclair

Re: Unsafe mem mapping
Здравствуйте, 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; tt] означает "скопируй мне 8 байт из _readBuffer в стек", и потом ещё "скопируй мне эти 8 байт туда, где будет выполнен возврат". Детали зависят от множества вещей вроде размера NativeStruct и конкретной версии JIT.
S>Читал про Span<T> но не понял как его применить для задачи, если он вообще применим для такого.
Чтобы понять, можно ли применить Span<T> нужно подняться на пару уровней выше над [tt]*(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() без выделения памяти.

Но всё это зависит от того, что именно вы делаете.
Re: Unsafe mem mapping
Здравствуйте, 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() без выделения памяти.

Но всё это зависит от того, что именно вы делаете.