Я изучал метод встраивания с помощью 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. У меня есть несколько вопросов по этому поводу:
- Есть ли что-то принципиально неточное в моем коде? Это было бы невероятно смущающим, но я хочу сначала отсеять очевидное. :)
- Если мой код и результаты действительно точные, то: Это известная проблема? И / или это выполняется как задумано?
- Мое предположение здесь - это Используемая Оптимизация Хвоста. Может ли быть так, что метод встраивания здесь тоже происходит? Я предполагаю, что мой основной вопрос здесь такой: что точно оптимизируется с такими неожиданно неоптимизированными результатами?
- Самое главное : Есть ли какой-либо способ обеспечить и достичь оптимизированных результатов, которые я ищу? Любая магия, которую нужно передать корневому делегату, была бы здесь полезна. Кажется, что корневой делегат разрешен правильно, только не вызван правильно.
Для полноты, вот весь код для запуска этого примера:
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);
}
Заранее благодарим вас за понимание и помощь, которую вы можете предоставить!