Оптимизация JIT: почему это медленно и как я могу улучшить это? - PullRequest
0 голосов
/ 01 мая 2018

Я изучал метод встраивания с помощью JIT и получил эту статью Скотта Хансельмана . Я немного углубился в его код, и кажется, что, хотя код запускается в режиме Release, хотя имеется всего несколько стеков вызовов, он действительно работает так, как будто эти дополнительные кадры все еще существуют в скомпилированном коде (даже если они это делают. не сообщать как таковой).

Для начала я разместил здесь код, если вы хотите подключиться и запустить его: https://github.com/Mike-EEE/StackOverflow.Performance

Я пробовал это на .NET 4.7.1, .NET Core 2.0 и даже на новом .NET Core 2.1 Preview , который был недавно анонсирован. Все показывают одинаковые результаты.

Я создал простую команду, которая генерирует сообщение, а затем создал последующую разноцветную команду, которая несколько раз оборачивает эту простую команду. В опубликованном коде это оформление выполняется 10 раз, в результате чего получается вложенная команда с 10 уровнями (или 11, если вы считаете простую команду origin).

Обе эти команды, используемые в тесте, используют пустой делегат для отправки сообщения, поскольку использование Console.WriteLine во время теста производительности становится довольно уродливым.

Прежде чем запускать тесты, я создаю декорированную команду, которая использует тот же код, что и тестируемый код, но вместо пустого делегата используйте Console.WriteLine для проверки трассировки стека в текущей среде выполнения.

В Debug эта трассировка стека выглядит следующим образом:

   at StackOverflow.Performance.EmitMessage.Emit(String message)
   at StackOverflow.Performance.EmitMessage.MethodC(String message)
   at StackOverflow.Performance.EmitMessage.MethodB(String message)
   at StackOverflow.Performance.EmitMessage.MethodA(String message)
   at StackOverflow.Performance.EmitMessage.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.DecoratedCommand.Execute(String message)
   at StackOverflow.Performance.Program.Main()

В Release это выглядит так:

   at StackOverflow.Performance.EmitMessage.Emit(String message)
   at StackOverflow.Performance.Program.Main()

На данный момент все выглядит потрясающе и именно так, как я ожидал. Однако затем я выполняю эти две команды через BenchmarkDotNet , чтобы посмотреть, каковы результаты в настройке производительности. Эти результаты, похоже, указывают на то, что цепочка вызовов оформленной команды выполнена полностью, хотя трассировка испущенного стека предполагает, что такой цепочки вызовов не существует:

// * Summary *

BenchmarkDotNet=v0.10.14, OS=Windows 10.0.16299.371 (1709/FallCreatorsUpdate/Redstone3)
Intel Core i7-4820K CPU 3.70GHz (Haswell), 1 CPU, 8 logical and 8 physical cores
.NET Core SDK=2.1.300-preview2-008533
  [Host]     : .NET Core 2.0.7 (CoreCLR 4.6.26328.01, CoreFX 4.6.26403.03), 64bit RyuJIT
  DefaultJob : .NET Core 2.0.7 (CoreCLR 4.6.26328.01, CoreFX 4.6.26403.03), 64bit RyuJIT


    Method |      Mean |     Error |    StdDev |
---------- |----------:|----------:|----------:|
    Direct |  3.581 ns | 0.0759 ns | 0.0710 ns |
 Decorated | 44.646 ns | 0.7701 ns | 0.7203 ns |

Итак, может показаться, что здесь выполняется более 2 кадров, и это привело меня к публикации этого вопроса здесь, в StackOverflow. У меня есть несколько вопросов по этому поводу:

  1. Есть ли что-то принципиально неточное в моем коде? Это было бы невероятно смущающим, но я хочу сначала отсеять очевидное. :)
  2. Если мой код и результаты действительно точные, то: Это известная проблема? И / или это выполняется как задумано?
  3. Мое предположение здесь - это Используемая Оптимизация Хвоста. Может ли быть так, что метод встраивания здесь тоже происходит? Я предполагаю, что мой основной вопрос здесь такой: что точно оптимизируется с такими неожиданно неоптимизированными результатами?
  4. Самое главное : Есть ли какой-либо способ обеспечить и достичь оптимизированных результатов, которые я ищу? Любая магия, которую нужно передать корневому делегату, была бы здесь полезна. Кажется, что корневой делегат разрешен правильно, только не вызван правильно.

Для полноты, вот весь код для запуска этого примера:

public class Program
{
    static void Main()
    {
        // Writes out the stack trace from a decorated command:
        Decorate.Get(new EmitMessage(Console.WriteLine))
                .Execute(null);

        BenchmarkRunner.Run<Program>();
        Console.ReadKey();
    }

    readonly ICommand _direct, _decorated;
    readonly string _message;

    public Program() : this(new EmitMessage()) {}

    public Program(ICommand direct) : this(direct, Decorate.Get(direct), "Hello World!") {}

    public Program(ICommand direct, ICommand decorated, string message)
    {
        _direct    = direct;
        _decorated = decorated;
        _message = message;
    }

    [Benchmark]
    public void Direct()
    {
        _direct.Execute(_message);
    }

    [Benchmark]
    public void Decorated()
    {
        _decorated.Execute(_message);
    }
}

static class Decorate
{
    public static ICommand Get(ICommand parameter)
        => Enumerable.Range(0, 10)
                     .Aggregate(parameter, (command, _) => new DecoratedCommand(command));
}

sealed class DecoratedCommand : ICommand
{
    readonly ICommand _command;

    public DecoratedCommand(ICommand command) => _command = command;

    public void Execute(string message)
    {
        _command.Execute(message);
    }
}

sealed class EmitMessage : ICommand
{
    readonly Action<string> _emit;

    public EmitMessage() : this(_ => {}) {}

    public EmitMessage(Action<string> emit) => _emit = emit;

    public void Execute(string message)
    {
        MethodA(message);
    }

    void MethodA(string message)
    {
        MethodB(message);
    }

    void MethodB(string message)
    {
        MethodC(message);
    }

    void MethodC(string message)
    {
        Emit(message);
    }

    void Emit(string message)
    {
        _emit(message ?? new StackTrace().ToString());
    }
}

public interface ICommand
{
    void Execute(string message);
}

Заранее благодарим вас за понимание и помощь, которую вы можете предоставить!

1 Ответ

0 голосов
/ 04 мая 2018

бесстыдно копирует работу Стивена Туба из здесь

Я только что посмотрел на разборку для DecoratedCommand, используя проверенную сборку coreclr и работающую с setCOMPlus_JitDisasm=Execute см. документация . На самом деле он использует хвостовые вызовы:

; Список сборок для метода DecoratedCommand:Execute(ref): this

; Излучение BLENDED_CODE для процессора X64 с AVX

; оптимизированный код

; кадр на основе RSP

; полностью прерываемый

; Окончательные присвоения локальной переменной

; V00 this [V00, T00] (3, 3) ref -> rcx this class-hnd

; V01 arg1 [V01, T01] (3, 3) ref -> rdx class-hnd

; # V02 OutArgs [V02] (1, 1) lclBlk (0) [rsp + 0x00]

; Размер кадра Lcl = 0

G_M223_IG01:

G_M223_IG02:

488B4908 mov rcx, gtr ptr [rcx + 8]

49BB48007733FD7F0000 mov r11, 0x7FFD33770048

488B05934FE5FF mov rax, qword ptr [(reloc)]

3909 cmp dword ptr [rcx], ecx

G_M223_IG03:

48FFE0 rex.jmp rax

...