Можно ли сообщить предсказателю ветки, насколько вероятно, что он должен следовать за веткой?

Просто, чтобы было ясно, я не собираюсь заниматься какой-либо мобильностью здесь, поэтому любые решения, которые привяжут меня к определенному ящику, прекрасны.

В принципе, у меня есть оператор if, который в 99% случаев оценит значение true, и я пытаюсь выполнить каждый последний час работы, могу ли я выполнить какую-то команду компилятора (используя GCC 4.1.2 и x86 ISA, если это имеет значение) сообщить предсказателю ветки, что он должен кэшировать эту ветвь?

Ответ 1

Да. http://kerneltrap.org/node/4705

__builtin_expect - это метод, который gcc (версии >= 2.96) предлагают программистам указать ветвь информации прогнозирования компилятор. Возвращаемое значение __builtin_expect - это первый аргумент (который может быть только целым числом) передан ему.

if (__builtin_expect (x, 0))
                foo ();

     [This] would indicate that we do not expect to call `foo', since we
     expect `x' to be zero. 

Ответ 2

Да, но это не будет иметь никакого эффекта. Исключения составляют старые (устаревшие) архитектуры pre Netburst, и даже тогда это не делает ничего измеримого.

Существует код операции "отраслевой подсказки", представленный Intel с архитектурой Netburst, и предсказание статической ветки по умолчанию для холодных скачков (предсказанное назад предсказанное, предсказанное вперед, не принятое) на некоторых старых архитектурах. GCC реализует это с помощью __builtin_expect (x, prediction), где прогнозирование обычно равно 0 или 1. Кодовый код, выданный компилятором, игнорируется на всех новых архитектурах процессоров (> = Core 2). Маленький угловой случай, когда это действительно что-то делает, - это случай холодного перехода на старую архитектуру Netburst. Intel рекомендует не использовать подсказки статической ветки, вероятно, потому, что они считают увеличение размера кода более опасным, чем возможная предельная скорость.

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

Существует несколько причин, по которым он не работает должным образом.

  • Процессор может точно предсказать небольшие циклы (n <64).
  • Процессор может точно предсказать небольшие повторяющиеся шаблоны (n ~ 7).
  • Сам процессор может оценивать вероятность ветвления во время выполнения лучше, чем компилятор/программист во время компиляции.
  • Предсказуемость (= вероятность того, что ветвь будет правильно предсказана) ветки гораздо важнее вероятности того, что ветвь будет взята. К сожалению, это сильно зависит от архитектуры, и предсказать предсказуемость отрасли, как известно, сложно.

Узнайте больше о внутренних работах предсказания ветвлений в руководствах Agner Fogs. См. Также список рассылки gcc.

Ответ 3

В Pentium 4 (известной как микроархитектура Netburst) в качестве префиксов к инструкциям jcc использовались подсказки предсказателей ветвлений, но только P4 когда-либо с ними что-то делал. См. Http://ref.x86asm.net/geek32.html. И раздел 3.5 превосходного руководства Agner Fog от http://www.agner.org/optimize/. У него также есть руководство по оптимизации в C++.

Ранее и позже процессоры x86 молча игнорировали эти байты префикса. Есть ли результаты теста производительности для использования вероятных/маловероятных подсказок? упоминает, что PowerPC имеет некоторые инструкции перехода, которые имеют подсказку предсказания ветвления как часть кодирования. Это довольно редкая архитектурная особенность. Статическое предсказание ветвей во время компиляции очень сложно сделать точно, поэтому обычно лучше оставить это аппаратному обеспечению, чтобы выяснить это.

Официально опубликовано немного о том, как именно работают предсказатели ветвления и целевые буферы ветвления в самых последних процессорах Intel и AMD. Руководства по оптимизации (их легко найти на сайтах AMD и Intel) дают некоторые советы, но не документируют конкретное поведение. Некоторые люди запускают тесты, чтобы попытаться угадать реализацию, например, сколько записей BTB есть в Core2... В любом случае, идея явного намека на предиктор была отброшена (на данный момент).

Документировано, например, что у Core2 есть буфер истории ветвлений, который может избежать неправильного прогнозирования выхода из цикла, если цикл всегда выполняет постоянное короткое число итераций, <8 или 16 IIRC. Но не спешите развертывать, потому что цикл, который умещается в 64 байта (или 19 мегапикселей на Penryn), не будет иметь узких мест при извлечении инструкций, потому что он воспроизводит из буфера... читайте PDF файлы Agner Fog, они превосходны.

См. Также Почему Intel изменила механизм статического прогнозирования ветвлений за эти годы? Intel, так как Sandybridge вообще не использует статическое предсказание, насколько мы можем судить по экспериментам с производительностью, которые пытаются реинжинировать то, что делают процессоры. (Многие старые процессоры имеют статическое предсказание как запасной вариант, когда отсутствует динамическое предсказание. Нормальным статическим предсказанием является то, что прямые ветки не берутся, а обратные ветки берутся (потому что обратные ветки часто являются ветвями цикла).)


Эффект макросов likely()/unlikely() использующих GNU C __builtin_expect (как упоминается в ответе Дракоши), напрямую не вставляет подсказки BP в asm. (Возможно, это можно сделать с помощью gcc -march=pentium4, но не при компиляции для чего-либо еще).

Фактический эффект состоит в том, чтобы выложить код так, чтобы на быстром пути было меньше взятых ветвей и, возможно, меньше команд. Это поможет прогнозированию ветвлений в случаях, когда статическое прогнозирование вступает в игру (например, динамические предикторы холодны, на процессорах, которые используют статическое прогнозирование вместо того, чтобы просто давать ветвям псевдонимы друг другу в кэшах предикторов).

См. В чем преимущество __builtin_expect GCC в операторах if else? для конкретного примера code-gen.

Взятые ветки стоят немного дороже, чем не взятые, даже если прогнозируется идеально. Когда ЦП выбирает код в виде 16-байтовых блоков для параллельного декодирования, взятая ветвь означает, что более поздние инструкции в этом блоке выборки не являются частью потока команд, которые должны быть выполнены. Он создает пузыри во внешнем интерфейсе, которые могут стать узким местом в коде с высокой пропускной способностью (который не блокируется в серверном конце при промахах кэша и имеет высокий параллелизм на уровне команд).

Перемещение между различными блоками также потенциально затрагивает больше строк кэша кода, увеличивая объем кэша L1i и, возможно, вызывая больше промахов кэша инструкций, если он был холодным. (И, возможно, след Uop-кэша). Так что еще одно преимущество быстрого пути - быть коротким и линейным.


Оптимизация в профиле GCC обычно делает ненужные/маловероятные макросы ненужными. Компилятор собирает данные времени выполнения о том, каким образом каждая ветвь пошла для принятия решений по компоновке кода, и для определения горячих и холодных блоков/функций. (например, он будет развертывать циклы в горячих функциях, но не в холодных функциях.) См. -fprofile-generate и -fprofile-use в руководстве по GCC. Как использовать профилированные оптимизации в g++?

В противном случае GCC должен угадать, используя различные эвристические методы, если вы не использовали вероятные/маловероятные макросы и не использовали PGO. -fguess-branch-probability включена по умолчанию в -O1 и выше.

https://www.phoronix.com/scan.php?page=article&item=gcc-82-pgo&num=1 имеет результаты тестов производительности для PGO по сравнению с обычными с gcc8.2 на ЦП масштабируемого сервера Xeon. (Skylake-AVX512). Каждый тест получил как минимум небольшое ускорение, а некоторые выиграли примерно на 10%. (Большая часть этого, вероятно, происходит из-за развертывания циклов в горячих циклах, но, вероятно, частично из-за лучшего расположения ветвей и других эффектов.)

Ответ 4

Я предлагаю, а не беспокоиться о предсказании ветвлений, профилировать код и оптимизировать код, чтобы уменьшить количество ветвей. Одним из примеров является разворачивание цикла, а другое - с использованием методов булевого программирования, а не с помощью операторов if.

Большинство процессоров любят предвыборные заявления. Как правило, оператор ветки генерирует fault внутри процессора, заставляя его сбросить очередь предварительной выборки. Здесь самое большое наказание. Чтобы уменьшить это штрафное время, перепишите (и разработайте) код, чтобы было доступно меньше веток. Кроме того, некоторые процессоры могут условно выполнять инструкции без необходимости ветвления.

Я оптимизировал программу с 1 часа времени выполнения до 2 минут с помощью циклического разворачивания и больших буферов ввода-вывода. Прогнозирование ветвей не принесло бы большой экономии времени в этом случае.

Ответ 5

SUN C Studio имеет некоторые прагмы, определенные для этого случая.

#pragma rare_called()

Это работает, если одна часть условного выражения является вызовом функции или начинается с вызова функции.

Но не существует способа пометить общий оператор if/while

Ответ 6

Нет, потому что нет команды сборки, чтобы знать предсказатель ветвления. Не беспокойтесь об этом, предиктор ветки довольно умный.

Также, обязательный комментарий о преждевременной оптимизации и о том, как это зло.

EDIT: Дракоша упомянул некоторые макросы для GCC. Тем не менее, я считаю, что это оптимизация кода и фактически не имеет ничего общего с предсказанием ветвей.

Ответ 7

Это звучит для меня как overkill - этот тип оптимизации позволит сэкономить мало времени. Например, использование более современной версии gcc будет оказывать гораздо большее влияние на оптимизацию. Кроме того, попробуйте включить и отключить все различные флаги оптимизации; они не все повышают производительность.

В принципе, кажется маловероятным, что это будет иметь какое-то существенное отличие по сравнению со многими другими плодотворными путями.

EDIT: спасибо за комментарии. Я создал эту вики сообщества, но оставил ее, чтобы другие могли видеть комментарии.