понедельник, 3 июня 2013 г.

Ручное размещение полей структуры в C#

Начнём с задачки. Имеется структура:

struct MyStruct
{
    public Int16 Value;
}

Требуется научится получать младший байт поля Value. Но сделать это нужно так, чтобы обеспечить максимально возможную производительность.

Казалось бы, решение очевидно:
struct MyStruct
{
    public Int16 Value;
    public Byte LowByte { get { return (byte)(Value & 255); } }
}

Но при таком подходе при каждом обращении к LowByte на самом деле будет вызываться метод get_LowByte, который на IL-уровне выглядит следующим образом:

.method public hidebysig specialname instance uint8 get_LowByte() cil managed
{
  // Загружаем в стек указатель на текущий объект
  L_0000: ldarg.0
  // Заменяем верхушку стека на значение свойства Value текущего объекта
  L_0001: ldfld int16 ConsoleApplication.MyStruct::Value 
  // Кладём в стек 255
  L_0006: ldc.i4 0xff 
  // Достаём из стека два последних значения (Value и 255) и кладём обратно их конъюнкцию
  L_000b: and
  // Выполняем приведение типа к byte
  L_000c: conv.u1 
  // Возвращаем управление
  L_000d: ret 
}

Итого имеем 6 команд метода + накладки на вызов функции. Есть более быстрый способ, который находится в пространстве имён System.Runtime.InteropServices. Суть в том, что в C# есть возможность явно указать в каком порядке и с каким смещением размещать поля структуры. Для этого нужно пометить структуру атрибутом StructLayout с параметром LayoutKind.Explicit. После этого для каждого поля можно вручную прописать смещение. Таким образом, наша задачка решается так:

[StructLayout(LayoutKind.Explicit)]
struct MyStruct
{
    [FieldOffset(0)]
    public Int16 Value;
    [FieldOffset(0)]
    public Byte LowByte;
}

Теперь вместо генерации вспомогательного метода для получения младшего байта необходимое значение берётся непосредственно из памяти. Следующий код демонстрирует использование структуры:

var s = new MyStruct();
s.Value = 256 + 100;
Console.WriteLine(s.LowByte); // 100

Сказанное выше вовсе не означает, что теперь нужно срочно переписывать все ваши структуры, вручную размещая поля. Платформа .NET и сама с этим прекрасно справляется. Но если есть жестокие требования по производительности, то данный метод может помочь.