График зависимости между размером типов-значений и скорости их копирования, например, при передаче в качестве параметров методов или сохранении в массиве типов-значений. Некоторые выводы:

  • Выравнивание очень важно и значительно влияет на производительность копирования. Не смотря на шум в результатах, прекрасно видно, что automatic layout всегда даёт лучший результат. Не выровненные структуры небольших размеров просто адски тормозят по сравнению со своими выровненными аналогами.
  • Значительный провал производительности на платформе x86 происходит при размерах более 24 байт, а не более 16 байт - самого часто упоминаемого рекомендуемого максимального размера структуры во всяческих гайдлайнах.
  • За счёт большого размера регистров, платформа x64 не испытывает такой деградации производительности при увлечении размера структуры, какая проявляется на платформе x86.

Код теста (warning, написано на коленке):

using System;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Threading;

static class Program
{
  static void Main() {
    Thread.CurrentThread.Priority = ThreadPriority.Highest;

    var name = new AssemblyName("FooAssembly");
    var module = AppDomain.CurrentDomain
      .DefineDynamicAssembly(name, AssemblyBuilderAccess.Run)
      .DefineDynamicModule("FooModule");

    for (var structSize = 0; structSize < 1000; structSize++) {
      Console.Write("{0};", structSize);

      foreach (var layoutMode in Layouts) {
        // генерируем новый struct-тип с заданным режимом layout'а
        var typeName = Guid.NewGuid().ToString();
        var typeBuilder = module.DefineType(typeName, layoutMode |
          TypeAttributes.Class | TypeAttributes.BeforeFieldInit |
          TypeAttributes.Sealed, typeof(ValueType));

        // добавляем в тип n полей типа byte
        for (var i = 0; i < structSize; i++)
          typeBuilder.DefineField(
            "field" + i, typeof(byte), FieldAttributes.Public);

        // создаём тип TestClass, параметризованный новым типом
        var testType = typeof(TestClass<>)
          .MakeGenericType(typeBuilder.CreateType());

        // запускаем сам тест
        var stopwatch = Stopwatch.StartNew();
        testType.GetMethod("DoTest").Invoke(null, null);
        Console.Write("{0};", stopwatch.Elapsed.TotalMilliseconds);
      }

      Console.WriteLine();
    }
  }

  private static readonly TypeAttributes[] Layouts = {
    TypeAttributes.SequentialLayout,
    TypeAttributes.AutoLayout
  };
}

static class TestClass<T> where T : struct {
  private const int COUNT = 10000000;

  public static void DoTest() {
    var array = new T[1000];
    for (var i = 0; i < COUNT; i++) {
      var temp1 = new T();
      var temp2 = Foo1(temp1); // пропускаем через методы
      array[i % 1000] = temp2; // сохраняем в массив
    }
  }

  [MethodImpl(MethodImplOptions.NoInlining)]
  static T Foo1(T x) { return Foo2(x); }
  [MethodImpl(MethodImplOptions.NoInlining)]
  static T Foo2(T x) { return Foo3(x); }
  [MethodImpl(MethodImplOptions.NoInlining)]
  static T Foo3(T x) { return Foo4(x); }
  [MethodImpl(MethodImplOptions.NoInlining)]
  static T Foo4(T x) { return Foo5(x); }
  [MethodImpl(MethodImplOptions.NoInlining)]
  static T Foo5(T x) { return x; }
}