2015-05-30 5 views
5

Каждый раз, когда я Bumb в них в IL: br_S, ldc_i4_S, ldarg_S, и т.д., и т.д ... Так что я просто должен задать вопрос:Какова цель «короткой» нотации IL?

Я имею в виду ... Если вы JIT'ing язык от ИЛ до родного ассемблера, это не имеет никакого значения с точки зрения производительности, не так ли? Итак, какова цель иметь эти «короткие руки»? Это просто для того, чтобы иметь меньшее количество байтов в бинарных файлах IL (например, в качестве механизма сжатия) или есть какая-то другая причина?

И если это просто механизм сжатия, почему бы не использовать алгоритм сжатия, например дефлят?

+0

@HansPassant Как вы, вероятно, тоже (и команда JIT определенно сделала), я довольно долго провожу время в инструкции Intel® x64-86 [...]. Одна вещь, которая действительно выделяется, заключается в том, что они начинались с коротких нотных обозначений, и когда набор инструкций рос, так же как и количество очевидных хаков. Мой урок узнал, что есть: не делайте этого, он просто добавит много сложности в долгосрочной перспективе. Имея это в виду, использование стандартного алгоритма сжатия внезапно делает намного больше смысла, чем короткие байт-коды, не так ли? Возможно, я ошибаюсь, мне просто любопытно ... – atlaste

+0

Я нахожусь на тонком льду здесь, прося «почему они так не делают», я понимаю, что ...Мне просто интересно, какие причины имеют исторический опыт. Если причина * является * сжатием, я понимаю, что это правильное решение, которое они явно выбрали. Просто интересно, если это единственный. – atlaste

+0

@atlaste Вы должны помнить, что, возможно, когда они создали язык IL, они думали о встроенных устройствах (где память была премией) и, возможно, о возможности возможного микропроцессора на основе IL-кода (Java имел [их] (http: // ru.wikipedia.org/wiki/Java_processor)) – xanatos

ответ

9

Несомненно, это микро-оптимизация. Но Золотое правило микрооптимизации здесь сильно применяется, они превращают макрос, когда вы можете применять оптимизацию снова и снова. Что, безусловно, имеет место здесь, тела методов малы, поэтому подавляющее большинство ветвей являются короткими, методы имеют ограниченное количество аргументов и локальных переменных, поэтому для их разрешения достаточно одного байта, константы от 0 до 9 очень часто появляются в реальная программа.

Добавьте их все, и у вас есть макро-оптимизация на большой сборке, выведенная на много килобайт. Какой делает вопрос во время выполнения, это ИЛ, который не должен быть поврежден страницей в ОЗУ. Время жаркого начала в jitted-программе всегда является проблемой и подвергается атаке со всех сторон.

В целом, .NET Framework является неустанно микро-оптимизирован. Во многом потому, что Microsoft просто не может предположить, что их код не будет находиться на критическом пути в программе.

+0

Если я правильно помню, много лет назад медленная фаза JIT была большой проблемой (которая была частично решена более быстрыми процессорами :-) и частично решена многопоточным JITting) – xanatos

+0

Как это микро-оптимизация. 'ldarg_S' и т. д. являются мнемониками, а не самими опкодами, которые являются байтовыми данными, а не текстом. Длина мнемоники не связана с размером опкода. –

+2

Мнемоника не имеет значения, все, что имеет значение, заключается в том, что короткая версия занимает меньше байтов в IL. ldarg_0 и друзья принимают 1, ldarg_S занимает 2, ldarg занимает 3 байта. Если вы когда-либо находите ldarg, тогда вы нашли программиста, которого нужно уволить :) –

-1

Это не из-за сжатия, так как каждый байт имеет «имя-представление».

Это означает, что байты 0x00 (который представляет команду NOP) хранятся в виде 00, а не как 6e 6f 70 (являющегося ASCII-версию строки «nop»).
Эти «короткие» нотации являются короткими из-за целей отладки - даже если ldarg.0 немного труднее читать, чем Load argument 0 onto the stack.

Полный список CIL команды можно посмотреть здесь: http://en.wikipedia.org/wiki/List_of_CIL_instructions


пожалуйста, простите мои довольно упрощенные объяснения из-за мое незнание английского языка

+0

Я не уверен, что вы имеете в виду ...? Конечно, мы храним байтовые коды, это просто удобнее. Теперь 'ldarg.0' не сложнее читать, чем' ldarg 0', 'ldc_i4_0' читать не сложно, чем' ldc_i4 0', а 'br_s 0x10' не сложнее читать, чем' br 0x10' - они просто добавляют дополнительные инструкции к таблице, а это значит, что им нужны многобайтовые коды операций, больше кода в их таблице «switch» в JIT и т. д. – atlaste

+0

@atlaste: Пожалуйста, извините меня, сэр - я, кажется, неправильно понял вопрос ...... – Unknown6656

+0

Вы, кажется, единственный, кто ответил правильно: что коды операций не хранятся как, например, байт 0xD7, а не строка «add.ovf.un», я не следую за вами однако рассуждение относительно отладчика. –

2

Конца дня короткого коды пытаются сделать «меньший» код. На самом деле это не имеет смысловой разницы.

5

не ответ: уже есть ответ, он постулирует, что без «коротких» кодов операций размер сборки будет раздуваться. Это правда? Да ... но у нас (у меня, потому что мне нечего было делать этим вечером), чтобы продемонстрировать это :-)

Сколько байтов «получено» с помощью «коротких» (по правде говоря) byte) значения для OpCodes (например, Bge_S против Bge)? И от использования OpCodes, которые включают «фиксированный» индекс/номер? (например, Ldarg_0 против Ldarg или Ldc_I4_0 и Ldc_I4_M1 против Ldc_I4)?

Мы могли бы написать программу, чтобы проверить это! :-) Результат для mscorlib 4.5.2:

4.5.2 или более поздняя версия

сборки: C: \ Windows \ Microsoft.NET \ Framework64 \ v4.0.30319 \ mscorlib.dll

Размер: 5217440

Из них ресурсы: 948657

Пропущенные: 9496 методы

Успешно разобран: 63720 методы

Получили 420786 байт из коротких аргументов

Получили 1062014 байт с «фиксированной» номер

Так с «оптимизированными опкодами» mscorlib является 5.2Mb ... Без него было бы 6.6MB только для значения размеров. 25% больше! (и это игнорирование ресурсов в 5,2 МБ, 0,9 МБ ресурсов! Таким образом, процентное усиление в размере кода операции еще больше)

(Я использую mscorlib, потому что, хотя это не ваша типичная сборка, это, безусловно, достаточно полно кода каждого типа)

программы: (я использую Mono.Reflection):

public static void Main(string[] args) 
{ 
    Console.WriteLine(CheckFor45DotVersion(GetReleaseKey())); 

    string assemblyName = "mscorlib"; 
    Assembly assembly = Assembly.Load(assemblyName); 

    Console.WriteLine("Assembly: {0}", assembly.Location); 

    long fullSize = new FileInfo(assembly.Location).Length; 

    Console.WriteLine("Size: {0}", fullSize); 

    var allOpCodes = typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public) 
     .Where(x => x.FieldType == typeof(OpCode)) 
     .Select(x => (OpCode)x.GetValue(null)); 

    Dictionary<OpCode, int> opcodes = allOpCodes.ToDictionary(x => x, x => 0); 

    long resourcesLength = 0; 
    int skippedMethods = 0; 
    int parsedMethods = 0; 

    ParseAssembly(assembly, resource => 
    { 
     ManifestResourceInfo info = assembly.GetManifestResourceInfo(resource); 

     if (info.ResourceLocation.HasFlag(ResourceLocation.Embedded)) 
     { 
      using (Stream stream = assembly.GetManifestResourceStream(resource)) 
      { 
       resourcesLength += stream.Length; 
      } 
     } 
    }, method => 
    { 
     if (method.MethodImplementationFlags.HasFlag(MethodImplAttributes.InternalCall)) 
     { 
      skippedMethods++; 
      return; 
     } 

     if (method.Attributes.HasFlag(MethodAttributes.PinvokeImpl)) 
     { 
      skippedMethods++; 
      return; 
     } 

     if (method.IsAbstract) 
     { 
      skippedMethods++; 
      return; 
     } 

     parsedMethods++; 

     IList<Instruction> instructions = method.GetInstructions(); 

     foreach (Instruction instruction in instructions) 
     { 
      int num; 
      opcodes.TryGetValue(instruction.OpCode, out num); 
      opcodes[instruction.OpCode] = num + 1; 
     } 
    }); 

    Console.WriteLine("Of which resources: {0}", resourcesLength); 

    Console.WriteLine(); 

    Console.WriteLine("Skipped: {0} methods", skippedMethods); 
    Console.WriteLine("Parsed: {0} methods", parsedMethods); 

    int gained = 0; 
    int gainedFixedNumber = 0; 

    // m1: Ldc_I4_M1 
    var shortOpcodes = opcodes.Where(x => 
     x.Key.Name.EndsWith(".s") || 
     x.Key.Name.EndsWith(".m1") || 
      // .0 - .9 
     x.Key.Name[x.Key.Name.Length - 2] == '.' && char.IsNumber(x.Key.Name[x.Key.Name.Length - 1])); 

    foreach (var @short in shortOpcodes) 
    { 
     OpCode opCode = @short.Key; 
     string name = opCode.Name.Remove(opCode.Name.LastIndexOf('.')); 
     OpCode equivalentLong = opcodes.Keys.First(x => x.Name == name); 

     int lengthShort = GetLength(opCode.OperandType); 
     int lengthLong = GetLength(equivalentLong.OperandType); 

     int gained2 = @short.Value * (lengthLong - lengthShort); 

     if (opCode.Name.EndsWith(".s")) 
     { 
      gained += gained2; 
     } 
     else 
     { 
      gainedFixedNumber += gained2; 
     } 
    } 

    Console.WriteLine(); 

    Console.WriteLine("Gained {0} bytes from short arguments", gained); 
    Console.WriteLine("Gained {0} bytes from \"fixed\" number", gainedFixedNumber); 
} 

private static int GetLength(OperandType operandType) 
{ 
    switch (operandType) 
    { 
     case OperandType.InlineNone: 
      return 0; 

     case OperandType.ShortInlineVar: 
     case OperandType.ShortInlineI: 
     case OperandType.ShortInlineBrTarget: 
      return 1; 

     case OperandType.InlineVar: 
      return 2; 

     case OperandType.InlineI: 
     case OperandType.InlineBrTarget: 
      return 4; 

    } 

    throw new NotSupportedException(); 
} 

private static void ParseAssembly(Assembly assembly, Action<string> resourceAction, Action<MethodInfo> action) 
{ 
    string[] names = assembly.GetManifestResourceNames(); 

    foreach (string name in names) 
    { 
     resourceAction(name); 
    } 

    Module[] modules = assembly.GetModules(); 

    foreach (Module module in modules) 
    { 
     ParseModule(module, action); 
    } 
} 

private static void ParseModule(Module module, Action<MethodInfo> action) 
{ 
    MethodInfo[] methods = module.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 

    foreach (MethodInfo method in methods) 
    { 
     action(method); 
    } 

    Type[] types = module.GetTypes(); 

    foreach (Type type in types) 
    { 
     ParseType(type, action); 
    } 
} 

private static void ParseType(Type type, Action<MethodInfo> action) 
{ 
    if (type.IsInterface) 
    { 
     return; 
    } 

    // delegate (in .NET all delegates are MulticastDelegate 
    if (type != typeof(MulticastDelegate) && typeof(MulticastDelegate).IsAssignableFrom(type)) 
    { 
     return; 
    } 

    MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); 

    foreach (MethodInfo method in methods) 
    { 
     action(method); 
    } 

    Type[] nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); 

    foreach (Type nestedType in nestedTypes) 
    { 
     ParseType(nestedType, action); 
    } 
} 

// Adapted from https://msdn.microsoft.com/en-us/library/hh925568.aspx 
private static int GetReleaseKey() 
{ 
    using (RegistryKey ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) 
    { 
     // .NET 4.0 
     if (ndpKey == null) 
     { 
      return 0; 
     } 

     int releaseKey = (int)ndpKey.GetValue("Release"); 
     return releaseKey; 
    } 
} 

// Checking the version using >= will enable forward compatibility, 
// however you should always compile your code on newer versions of 
// the framework to ensure your app works the same. 
private static string CheckFor45DotVersion(int releaseKey) 
{ 
    if (releaseKey >= 393273) 
    { 
     return "4.6 RC or later"; 
    } 
    if ((releaseKey >= 379893)) 
    { 
     return "4.5.2 or later"; 
    } 
    if ((releaseKey >= 378675)) 
    { 
     return "4.5.1 or later"; 
    } 
    if ((releaseKey >= 378389)) 
    { 
     return "4.5 or later"; 
    } 
    // This line should never execute. A non-null release key should mean 
    // that 4.5 or later is installed. 
    return "No 4.5 or later version detected"; 
} 

Вдохновленного код Ксанатоса, тот же самый код, используя исходные байты IL может дать нам еще лучше оценить, сколько мы можем получить. Я разделил прибыль/убытки на категории, чтобы получить хорошую картину.

class Program 
{ 
    internal class Calculator 
    { 
     static Calculator() 
     { 
      Register(OpCodes.Beq_S, 1, 3); // category 1: short-hand notations 
      Register(OpCodes.Bge_S, 1, 3); 
      Register(OpCodes.Bge_Un_S, 1, 3); 
      Register(OpCodes.Bgt_S, 1, 3); 
      Register(OpCodes.Bgt_Un_S, 1, 3); 
      Register(OpCodes.Ble_S, 1, 3); 
      Register(OpCodes.Ble_Un_S, 1, 3); 
      Register(OpCodes.Blt_S, 1, 3); 
      Register(OpCodes.Blt_Un_S, 1, 3); 
      Register(OpCodes.Bne_Un_S, 1, 3); 
      Register(OpCodes.Br_S, 1, 3); 
      Register(OpCodes.Brfalse_S, 1, 3); 
      Register(OpCodes.Brtrue_S, 1, 3); 
      Register(OpCodes.Conv_I, 2, 4); // category 2: types can be generalized 
      Register(OpCodes.Conv_I1, 2, 4); 
      Register(OpCodes.Conv_I2, 2, 4); 
      Register(OpCodes.Conv_I4, 2, 4); 
      Register(OpCodes.Conv_I8, 2, 4); 
      Register(OpCodes.Conv_Ovf_I, 2, 4); 
      Register(OpCodes.Conv_Ovf_I_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_I1, 2, 4); 
      Register(OpCodes.Conv_Ovf_I1_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_I2, 2, 4); 
      Register(OpCodes.Conv_Ovf_I2_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_I4, 2, 4); 
      Register(OpCodes.Conv_Ovf_I4_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_I8, 2, 4); 
      Register(OpCodes.Conv_Ovf_I8_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_U, 2, 4); 
      Register(OpCodes.Conv_Ovf_U_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_U1, 2, 4); 
      Register(OpCodes.Conv_Ovf_U1_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_U2, 2, 4); 
      Register(OpCodes.Conv_Ovf_U2_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_U4, 2, 4); 
      Register(OpCodes.Conv_Ovf_U4_Un, 2, 4); 
      Register(OpCodes.Conv_Ovf_U8, 2, 4); 
      Register(OpCodes.Conv_Ovf_U8_Un, 2, 4); 
      Register(OpCodes.Conv_R_Un, 2, 4); 
      Register(OpCodes.Conv_R4, 2, 4); 
      Register(OpCodes.Conv_R8, 2, 4); 
      Register(OpCodes.Conv_U, 2, 4); 
      Register(OpCodes.Conv_U1, 2, 4); 
      Register(OpCodes.Conv_U2, 2, 4); 
      Register(OpCodes.Conv_U4, 2, 4); 
      Register(OpCodes.Conv_U8, 2, 4); 
      Register(OpCodes.Ldarg_0, 3, 2); 
      Register(OpCodes.Ldarg_1, 3, 2); 
      Register(OpCodes.Ldarg_2, 3, 2); 
      Register(OpCodes.Ldarg_3, 3, 2); 
      Register(OpCodes.Ldarg_S, 1, 2); 
      Register(OpCodes.Ldarga, 3, 2); 
      Register(OpCodes.Ldarga_S, 1, 2); 
      Register(OpCodes.Ldc_I4_0, 3, 2); // category 3: small value loads 
      Register(OpCodes.Ldc_I4_1, 3, 2); 
      Register(OpCodes.Ldc_I4_2, 3, 2); 
      Register(OpCodes.Ldc_I4_3, 3, 2); 
      Register(OpCodes.Ldc_I4_4, 3, 2); 
      Register(OpCodes.Ldc_I4_5, 3, 2); 
      Register(OpCodes.Ldc_I4_6, 3, 2); 
      Register(OpCodes.Ldc_I4_7, 3, 2); 
      Register(OpCodes.Ldc_I4_8, 3, 2); 
      Register(OpCodes.Ldc_I4_M1, 3, 2); 
      Register(OpCodes.Ldc_I4_S, 1, 3); 
      Register(OpCodes.Ldelem_I, 2, 4); 
      Register(OpCodes.Ldelem_I1, 2, 4); 
      Register(OpCodes.Ldelem_I2, 2, 4); 
      Register(OpCodes.Ldelem_I4, 2, 4); 
      Register(OpCodes.Ldelem_I8, 2, 4); 
      Register(OpCodes.Ldelem_R4, 2, 4); 
      Register(OpCodes.Ldelem_R8, 2, 4); 
      Register(OpCodes.Ldelem_Ref, 2, 4); 
      Register(OpCodes.Ldelem_U1, 2, 4); 
      Register(OpCodes.Ldelem_U2, 2, 4); 
      Register(OpCodes.Ldelem_U4, 2, 4); 
      Register(OpCodes.Ldind_I, 2, 4); 
      Register(OpCodes.Ldind_I1, 2, 4); 
      Register(OpCodes.Ldind_I2, 2, 4); 
      Register(OpCodes.Ldind_I4, 2, 4); 
      Register(OpCodes.Ldind_I8, 2, 4); 
      Register(OpCodes.Ldind_R4, 2, 4); 
      Register(OpCodes.Ldind_R8, 2, 4); 
      Register(OpCodes.Ldind_Ref, 2, 4); 
      Register(OpCodes.Ldind_U1, 2, 4); 
      Register(OpCodes.Ldind_U2, 2, 4); 
      Register(OpCodes.Ldind_U4, 2, 4); 
      Register(OpCodes.Ldloc_0, 3, 1); 
      Register(OpCodes.Ldloc_1, 3, 1); 
      Register(OpCodes.Ldloc_2, 3, 1); 
      Register(OpCodes.Ldloc_3, 3, 1); 
      Register(OpCodes.Ldloc_S, 1, 1); 
      Register(OpCodes.Ldloca_S, 1, 1); 
      Register(OpCodes.Leave_S, 1, 3); 
      Register(OpCodes.Starg_S, 1, 1); 
      Register(OpCodes.Stelem_I, 2, 4); 
      Register(OpCodes.Stelem_I1, 2, 4); 
      Register(OpCodes.Stelem_I2, 2, 4); 
      Register(OpCodes.Stelem_I4, 2, 4); 
      Register(OpCodes.Stelem_I8, 2, 4); 
      Register(OpCodes.Stelem_R4, 2, 4); 
      Register(OpCodes.Stelem_R8, 2, 4); 
      Register(OpCodes.Stelem_Ref, 2, 4); 
      Register(OpCodes.Stind_I, 2, 4); 
      Register(OpCodes.Stind_I1, 2, 4); 
      Register(OpCodes.Stind_I2, 2, 4); 
      Register(OpCodes.Stind_I4, 2, 4); 
      Register(OpCodes.Stind_I8, 2, 4); 
      Register(OpCodes.Stind_R4, 2, 4); 
      Register(OpCodes.Stind_R8, 2, 4); 
      Register(OpCodes.Stind_Ref, 2, 4); 
      Register(OpCodes.Stloc_0, 3, 1); 
      Register(OpCodes.Stloc_1, 3, 1); 
      Register(OpCodes.Stloc_2, 3, 1); 
      Register(OpCodes.Stloc_3, 3, 1); 
      Register(OpCodes.Stloc_S, 1, 1); 
     } 

     private Calculator() { } 

     private static void Register(OpCode opCode, int category, int delta) 
     { 
      dict[opCode] = new KeyValuePair<int, int>(category, delta); 
     } 

     private static Dictionary<OpCode, KeyValuePair<int, int>> dict = new Dictionary<OpCode, KeyValuePair<int, int>>(); 

     public static void Update(int[] data, OpCode opcode) 
     { 
      KeyValuePair<int, int> kv; 
      if (dict.TryGetValue(opcode, out kv)) 
      { 
       data[kv.Key] += kv.Value; 
      } 
     } 
    } 

    internal class Decompiler 
    { 
     public Decompiler() { } 

     static Decompiler() 
     { 
      singleByteOpcodes = new OpCode[0x100]; 
      multiByteOpcodes = new OpCode[0x100]; 
      FieldInfo[] infoArray1 = typeof(OpCodes).GetFields(); 
      for (int num1 = 0; num1 < infoArray1.Length; num1++) 
      { 
       FieldInfo info1 = infoArray1[num1]; 
       if (info1.FieldType == typeof(OpCode)) 
       { 
        OpCode code1 = (OpCode)info1.GetValue(null); 
        ushort num2 = (ushort)code1.Value; 
        if (num2 < 0x100) 
        { 
         singleByteOpcodes[(int)num2] = code1; 
        } 
        else 
        { 
         if ((num2 & 0xff00) != 0xfe00) 
         { 
          throw new Exception("Invalid opcode: " + num2.ToString()); 
         } 
         multiByteOpcodes[num2 & 0xff] = code1; 
        } 
       } 
      } 
     } 

     private static OpCode[] singleByteOpcodes; 
     private static OpCode[] multiByteOpcodes; 

     public int[] Delta = new int[5]; 

     public void Decompile(MethodBase mi, byte[] ildata) 
     { 
      Module module = mi.Module; 

      int position = 0; 
      while (position < ildata.Length) 
      { 
       OpCode code = OpCodes.Nop; 

       ushort b = ildata[position++]; 
       if (b != 0xfe) 
       { 
        code = singleByteOpcodes[b]; 
       } 
       else 
       { 
        b = ildata[position++]; 
        code = multiByteOpcodes[b]; 
        b |= (ushort)(0xfe00); 
        Delta[4]++; 
       } 

       switch (code.OperandType) 
       { 
        case OperandType.InlineBrTarget: 
         position += 4; 
         break; 
        case OperandType.InlineField: 
         position += 4; 
         break; 
        case OperandType.InlineI: 
         position += 4; 
         break; 
        case OperandType.InlineI8: 
         position += 8; 
         break; 
        case OperandType.InlineMethod: 
         position += 4; 
         break; 
        case OperandType.InlineNone: 
         break; 
        case OperandType.InlineR: 
         position += 8; 
         break; 
        case OperandType.InlineSig: 
         position += 4; 
         break; 
        case OperandType.InlineString: 
         position += 4; 
         break; 
        case OperandType.InlineSwitch: 
         int count = BitConverter.ToInt32(ildata, position); 
         position += count * 4 + 4; 
         break; 
        case OperandType.InlineTok: 
        case OperandType.InlineType: 
         position += 4; 
         break; 
        case OperandType.InlineVar: 
         position += 2; 
         break; 
        case OperandType.ShortInlineBrTarget: 
         position += 1; 
         break; 
        case OperandType.ShortInlineI: 
         position += 1; 
         break; 
        case OperandType.ShortInlineR: 
         position += 4; 
         break; 
        case OperandType.ShortInlineVar: 
         position += 1; 
         break; 

        default: 
         throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType); 
       } 

       Calculator.Update(Delta, code); 
      } 
     } 
    } 

    static void Main(string[] args) 
    { 
     string assemblyName = "mscorlib"; 
     Assembly assembly = Assembly.Load(assemblyName); 

     int skippedMethods = 0; 
     int parsedMethods = 0; 
     Decompiler decompiler = new Decompiler(); 

     long totalbytes = 0; 

     Console.WriteLine("Assembly: {0}", assembly.Location); 
     foreach (var type in assembly.GetTypes()) 
     { 
      foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) 
      { 
       if (method.MethodImplementationFlags.HasFlag(MethodImplAttributes.InternalCall) || 
        method.Attributes.HasFlag(MethodAttributes.PinvokeImpl) || 
        method.IsAbstract) 
       { 
        skippedMethods++; 
       } 
       else 
       { 
        var body = method.GetMethodBody(); 
        byte[] bytes; 
        if (body != null && (bytes = body.GetILAsByteArray()) != null) 
        { 
         decompiler.Decompile(method, bytes); 
         ++parsedMethods; 

         totalbytes += bytes.Length; 
        } 
        else 
        { 
         skippedMethods++; 
        } 
       } 
      } 
     } 

     var delta = decompiler.Delta; 

     Console.WriteLine("{0} methods parsed, {1} methods skipped", parsedMethods, skippedMethods); 
     Console.WriteLine("- {0} bytes total", totalbytes); 
     Console.WriteLine("- {0} bytes gained from generalizing short-hand notations", delta[1]); 
     Console.WriteLine("- {0} bytes gained from generalizing type notations", delta[2]); 
     Console.WriteLine("- {0} bytes gained from generalizing loads notations", delta[3]); 
     Console.WriteLine("- {0} bytes lost from multi-byte opcodes", delta[4]); 

     Console.ReadLine(); 
    } 
} 

Результаты:

Assembly: C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll 
56117 methods parsed, 10605 methods skipped 
- 2489275 bytes total 
- 361193 bytes gained from generalizing short-hand notations 
- 126724 bytes gained from generalizing type notations 
- 618858 bytes gained from generalizing loads notations 
- 9447 bytes lost from multi-byte opcodes 

Что это говорит? Во-первых, общее число основано на фактических байтах IL методов. Короткие нотные выигрыши - это количество байтов, полученных от использования кодов операций _S. Обозначения типа нотации - это коды операций, которые можно было бы обобщить, используя токен типа, вместо специализированной операции (например, преобразования, такие как conv_i4). Прирост нагрузки - это байты, которые будут получены из специализированных функций нагрузки (например, ldc_i4_0). И, наконец, потому что у них теперь слишком много кодов операций, чтобы поместиться в байте, IL также нуждается в еще нескольких байтах.

Общий выигрыш от всего этого означает, что у нас будет DLL размером 3586603 вместо 2489275 байт, что составляет коэффициент сжатия +/- 30%.

+0

Хороший эксперимент. Честно говоря, я действительно удивлен, что это не так. – atlaste

+0

@atlaste Вероятно, хороший кусок пространства используется для типов, вызовов методов и подписей – xanatos

+0

Nice. Но разве это не было бы в духе SO, чтобы фактически увеличить/улучшить ответ Ганса? –