Хабрахабр

Сколько инструкций процессора использует компилятор?

Месяц назад я попытался сосчитать, сколько разных инструкций поддерживается современными процессорами, и насчитал 945 в Ice Lake. Комментаторы затронули интересный вопрос: какая часть всего этого разнообразия реально используется компиляторами? Например, некто Pepijn de Vos в 2016 подсчитал, сколько разных инструкций задействовано в бинарниках у него в /usr/bin, и насчитал 411 — т.е. примерно треть всех инструкций x86_64, существовавших на тот момент, не использовались ни в одной из стандартных программ в его ОС. Другая любопытная его находка — что код для x86_64 на треть состоит из инструкций mov. (В общем-то известно, что одних инструкций mov достаточно, чтобы написать любую программу.)

Я решил развить исследование de Vos, взяв в качестве «эталонного кода» компилятор LLVM/Clang. У него сразу несколько преимуществ перед содержимым /usr/bin неназванной версии неназванной ОС:

  1. С ним удобно работать: это один огромный бинарник, по размеру сопоставимый со всем содержимым /usr/bin среднестатистического линукса;
  2. Он позволяет сравнить разные ISA: на releases.llvm.org/download.html доступны официальные бинарники для x86, ARM, SPARC, MIPS и PowerPC;
  3. Он позволяет отследить исторические тренды: официальные бинарники доступны для всех релизов начиная с 2003;
  4. Наконец, в исследовании компиляторов логично использовать компилятор и в качестве подопытного объекта 🙂

Начну со статистики по мартовскому релизу LLVM 10.0:
В прошлом топике комментаторы упомянули, что самый компактный код у них получается для SPARC. Здесь же видим, что бинарник для AArch64 оказывается на треть меньше что по размеру, что по общему числу инструкций.

А вот распределение по числу инструкций:

Неожиданно, что код для SPARC на 11% состоит из nop-ов, заполняющих branch delay slots. Для i386 среди самых частых инструкций видим и int3, заполняющую промежутки между функциями, и nop, используемую для выравнивания циклов на строки кэша. Наблюдение de Vos о том, что код на треть состоит из mov, подтверждается на обоих вариантах x86; но даже и на load-store-архитектурах mov оказывается если не самой частой инструкцией, то второй после load.

А как набор используемых инструкций менялся со временем?

Единственная ISA, для которой в каждом релизе есть официальный бинарник — это i386:

Серая линия, отложенная на правой оси — это число разных инструкций, использованных в компиляторе. Как мы видим, некоторое время назад компилятор компилировался гораздо разнообразнее. int3 стала использоваться для заполнения промежутков только с 2018; до этого использовались такие же nop, как и для выравнивания внутри функций. Здесь же видно, что выравнивание внутри функций стало использоваться с 2013; до этого nop-ов было гораздо меньше. Ещё интересно, что до 2016 mov-ы составляли почти половину компилятора.

Самые первые версии LLVM, до появления clang, выпускались ещё и с бинарниками для SPARC. Потом поддержка SPARC утратила актуальность, и вновь она появилась лишь через 14 лет — с на порядок увеличившимся числом nop-ов:

Исторически следующая ISA, для которой выпускались бинарники LLVM — это PowerPC: сначала для Mac OS X и затем, после десятилетнего перерыва, для RHEL. Как видно из графика, переход после этого перерыва к 64-битному варианту ISA сопровождался заменой большинства lwz на ld, и вдобавок удвоением разнообразия инструкций:

В бинарниках для x86_64 и ARM частота использования разных инструкций почти не изменялась:

При подсчёте инструкций ARM я отсекал суффиксы условий — вместе с ними получалось около тысячи разных инструкций, но даже и без них ARM сильно опережает другие ISA по разнообразию генерируемых инструкций. Таким образом, слой b на этом графике включает и все условные переходы тоже. Для остальных ISA, где условными могут быть только переходы и немногие другие инструкции, суффиксы условий при подсчёте не отсекались.

Наконец, самая недавняя ISA, для которой публикуются официальные бинарники — это AArch64. Здесь интересно то, что orr с прошлого года почти перестала использоваться:

PowerPC и AArch64 оказались единственными ISA, для которых число разных используемых инструкций всё растёт и растёт.

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»