Inline и throw

от автора

Picture from mobillegends.net about inline functions

Picture from mobillegends.net about inline functions

Изучая производительность методов в различных коллекциях, я наткнулся на интересный факт: там, где нужно выбросить Exception, программисты дёргают метод в статическом классе, в котором и происходит throw. Поначалу я думал, что это просто удобно — иметь все ошибки в одном месте и там следить за их единообразием. Это да, это действительно удобно. Но есть нюанс…

Вторая проблема — inline. Дело в том, что JIT по-умолчанию старается не инлайнить методы с throw, чтобы избежать сложной обвязки вокруг выброса исключения. Анализируя метод, JIT собирает некие метрики, из которого получается число. Если число превышает некий порог, то метод заинлайнен не будет. В случае с throw, это число сразу превышает порог.

Более глубоко о том, что такое inline методов, можно, например, прочитать вот тут. Узнать, чем руководствуется JIT при inline’e методов можно, например, вот тут и вот тут (раньше статья была на Хабре, теперь исчезла). Мы же сразу перейдём к benchmark’у.

Проверяем inline

Ситуация простая: у нас есть класс, в котором несколько абсолютно одинаковых методов. Ну, почти.

public sealed class InliningService {         private readonly int _min;     ...     [MethodImpl(MethodImplOptions.AggressiveInlining)]         public int AggressiveInline(int a, int b) {             if (a < _min || b < _min)         throw new InvalidOperationException();              return a + b;     }  public int AutoInline(int a, int b) {     if (_min < 0 || b < _min) Errors.InvalidOperation();                return a + b;     }  public int WithoutInline(int a, int b) {     if (a < _min || b < _min)          throw new InvalidOperationException();     return a + b;     }

В одном случае мы делаем throw прямо в методе (Without Inline), в другом случае мы просим всё-таки заинлайнить такой метод (Aggressive Inline), а в третьем случае мы полагаемся на JIT и вызов статического метода, в котором выбрасывается ошибка (Auto Inline).

Benchmark: inline method in C#

Benchmark: inline method in C#

Глядя на этот benchmark, можно сделать несколько наблюдений.

Во-первых, скорость работы во всех трёх популярных версиях .NET примерно одинаковая. Странное увеличение времени работы метода без inline’a на .NET Core 3.1 я предлагаю списать на погрешность. Тем более, что размер IL-кода (Code Size) во всех трёх framework’ах одинаковый.

Во-вторых, скорость работы метода, который не был заинлайнен, предсказуемо ниже, чем версия, где JIT принял решение сделать inline. Причём почти в два раза. Это позволяет нам говорить о том, что нужно прятать throw в статический класс там, где выброс Exception будет дорогим и мы надеемся на inline.

В-третьих, колонка Code Size достаточно чётко намекает нам на то, что aggressive inline метода с throw в этом случае позволяет JIT сделать inline, но путём увеличения размера кода. По сравнению с AutoInline — разница драматичная. Подобный inline плох, поскольку повлиял бы на работу и возможность inline’a других методов, сделал бы невозможным inline тех методов, где это действительно важно.

Выводы

  1. Создайте статический класс а-ля Errors для выброса Exception’ов. Это стандартизирует выброс ошибок и сделает код чище.

  2. Методы класса Errors могут возвращать объектное представление сформированного Exception, но лучше, чтобы throw происходил прямо в методе этого класса. Введя подобную практику при написании кода, можно расширить возможности JIT’a по инлайну.

  3. Выброс Exception в критичном месте кода, где мы надеемся на inline — плохая идея, которая мешает JIT’у заинлайнить метод.

  4. Не надо баловаться с MethodImplAttribute, если вы не понимаете, как это работает и на что может повлиять. Используйте aggressive inline только тогда, когда вы имеете подтверждение (benchmark) того, что это положительно скажется на работе приложения.

P.S.: Начал писать в телегу про производительность. Заглядывайте, если интересно.


ссылка на оригинал статьи https://habr.com/ru/post/722374/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *