вторник, 21 февраля 2017 г.

Оптимизация типового пути исполнения

Роясь недавно в исходниках TPL Dataflow я заметил некоторый паттерн: многие функции разбиты на две. Основная называется обычным образом, AddElement или что-то в этом роде, а вторая – AddElement_Slow.

При этом мне очень понравилась причина, по которой это дело делалось: эта оптимизация позволяет заинлайнить метод в типовом кейсе. Казалось бы, а есть ли в этом толк? И, как оказалось, что есть (я бы удивился, если бы камрад Тауб решил бы использовать подобную оптимизацию не проверив, что она имеет смысл).

В результате, если вы пишите библиотеку или работаете над очень горячим куском вашего приложения, то такой подход вполне оправдан: если метод имеет типовой быстрый и нетиповой медленный пути исполнения, то второй кейс разумно выделить в отдельный метод, что сделает основной метод более пригодным для инлайна.

Ну, и подробности, у меня в англоязычном посте: “A common execution path optimization”.

11 комментариев:

  1. В англоязычном блоге какой-то неправильный code highlighter - не добавляется горизонтальная прокрутка, поэтому с телефона, например, читать пост бесполезно.

    ОтветитьУдалить
    Ответы
    1. А в этом блоге хайлайтинг не так выглядит? Я посмотрел в мобильной версии этот и новый блог и разницы не увидел. Да, и там и там прокрутки нет, но это потому, что я не использую хайлайтеры, а копирую код из студии с сохранением форматирования.

      Удалить
  2. Интересная статья. Мне в ней нравится то что там нет каких-то прямо "хаков" для производительности - просто идея "вынести долгий и нечасто исполняемый код в отдельный метод" здравая сама по себе и не ухудшает читабельности и поддерживаемости.

    ОтветитьУдалить
    Ответы
    1. И еще вопрос - а заинлайнится ли
      if (not_enough_space)
      {
      Resize();
      }

      array[i] = val;

      где Resize вынесен в отдельный метод и вызывается нечасто?

      Удалить
    2. По умолчанию, лимит для инлайна - 20 IL инструкций. Кажется, что этот лимит не будет достигнут и в этом случае. Плюс, если метод будет вызываться из цикла, то данный лимит будет немного увеличен.

      Т.е. я думаю, что этот метод заинлайнится тоже.

      Удалить
    3. Конечно, гадание с (не) заинлайнится вещь всё же неочевидная. Я например далеко не настолько крут чтобы быстренько на глаз посчитать во сколько IL команд скомпилируется мой код.
      А есть какой-то способ для проверки - инлайнится метод или нет?
      Ну кроме ковыряния результирующего байт-кода руками.

      Удалить
    4. Тут даже не IL смотреть нужно, а генерируемый JIT-ом asm.

      Удалить
    5. Есть еще одна подобная фишка: если имеется здоровенный If-then-else-if-then-else... или switch - самые часто встречающиеся варианты выносить в начало списка.

      Правда, обязательно надо тестировать: компилятор может много чего наменять, плюс не уверен в соблюдении порядка switch-eм.

      Удалить
    6. Да, интересный момент. Как-то не думал в эту сторону, но частенько итак это делал.

      В приведенном примере реалокация/ресайз явно обладает достаточной ответсвенностью для выделения в отдельный метод. Хотелось бы отметить, что это не только не ухудшает читаемость, это улучшает читаемость, да и код "в общем". И я думаю, что зачастую можно выделить подобную ответственность у такого куска кода, а если так, то можно будет использовать вменяемый нейминг, вроде Reallocate/Resize etc. То, что в примере осознанно сохранен стиль нейминга я понимаю.

      Удалить
  3. Как можно выйти с Вами на связь? На этом сайте нашел только как комментарии оставлять. У меня есть к Вам предложение. Моя почта zemlyanikin@recursion.ru

    ОтветитьУдалить