Comments 77
хороший перевод, спасибо
- Knock knock
- Branch prediction
- Who's there?
В основном, каждое отдельное ветление часто срабатывают в одном направвлении, и очень редко в другом.
Так себе гипотеза.
Таким образом мы получаем начальный уровень успеха предсказания не 50:50, а 80:20. Что дает хорошую прибавку к пенсии, пока накапливается статистика для остальных предикторов.
Какая-та статическая модель предсказаний до сих пор используется в случаях, если таблицы для динамических предикторов пусты.
Ничего страшного в этом нет: по сравнению с чтением из диска, разгон конвейера — мгновение.
Скорее всего, в процессоре присутствует просто небольшая табличка на несколько значений (т.е. внутренние регистры процессора), содержащая в себе физический адрес инструкции перехода и статистические данные для неё.
А вообще, это нужно уже инженеров Intel спрашивать, потому что гадание на кофейной гуще получается.
if( a < 10.) b = 20.; else b = 30.;
можно
b = 20. + step(a, 10.) * 10.;
Разворот циклов, условное выполнение операций (в SSE/AVX расширениях) — и ветвлений становится значительно меньше.
void mult(int n, double *a, *b, *c) {
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
....
При n=3 динамический предиктор рано или поздно выявит паттерн, что во внутреннем цикле каждый 3-й переход не выполняется, а как это должен понять компилятор?
Поэтому n будет константой, и оптимизирующий компилятор просто развернёт цикл.
PS Компиляторов не писал, процессоров не разрабатывал, так что все мои рассуждения сугубо теоретические
Насколько я видел (OpenMP, TPL и т.п.), везде программист даёт указания, что можно попробовать запараллелить. Поскольку программисты оптимизируют только горячие места, а не всё подряд, оптимизации всего остального лучше переложить на процессор.
Можете сами померять, сколько что стоит, и сделать соответствующие расчёты.
Вот одна из простейших реализаций parallel do: https://gist.github.com/e673/31b73495bfb83e818f226d2300568176
Варьируете N и смотрите, сколько итераций будет в зависимости от N.
Оптимальное значение N = числу логических ядер минус 1 (текущий поток тоже должен выполнять задачу).
Под Windows на 4-ядерном процессоре при N = 3 у меня получается 400к переключений в секунду (при N = 1, кстати, получается ~2 миллиона), т.е. ~10000 тактов уходит только на одну синхронизацию. Под Linux на 2-ядернике вдвое меньшей частоты при N = 1 — 80к переключений.
Чем на большее число потоков вы параллелите задачу, тем выше накладные расходы. Я даже боюсь представить, что будет твориться на Threadripper при 32 потоках, ведь блокировка шины при изменении атомарной переменной, за которую борется куча потоков — штука очень неприятная.
То есть, чтобы задачу вообще имело смысл параллелить, задача должна выполняться ощутимое время — несколько десятков микросекунд.
Кстати, низкая гранулярность тоже может оказаться не в фаворе, если будет страдать локальность доступа к памяти.
Довольно интересная ситуация, когда предсказание переходов можно пощупать на практике:
Задача: пробежаться по массиву байт и посчитать количество байт, больших 128.
Наблюдение: при случайном заполнении скорость работы алгоритма в 6 раз медленнее, чем при неслучайном (отсортированный массив).
Я может быть глупость спрошу, но вот это предсказание нужно чтобы выбрать одну из двух ветвей выполнения. Если предсказание не сбылось, то результат работы с неверной веткой просто отбрасывается. Можно ли пойти сразу по двум веткам одновременно и отбросить результат работы той, что не сбылась?
Можно, но это дорого: в процессоре лишних вычислительных блоков под такое просто нет.
Возможно, это и используется, но очень ограничено: для выборки команд в кэш из обеих веток, например.
По такому принципу кстати работают GPU. Но там это обусловлено тем, что блоки по 32 ядра (warpы в cuda) в один момент времени могут выполнять только одинаковые инструкции.
С другой стороны, зачем? Если ветвление выполняется часто, оно будет запомнено и не вытеснится из буфера предсказаний. А если редко, потеря нескольких тактов не важна.
https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf, раздел 3.4.1.
Про статическое предсказание (п. 3.4.1.3) вполне конкретно написано: The Intel Core microarchitecture does not use the static prediction heurist.
может даже так сложиться, что именно вторая, малоприоритетная ветка станет выполняться чаще, что добавит тактов 10 на перезагрузку конвейера в случае, когда код должен быть максимально быстрымНе понимаю примера. Статически — это значит один раз при компиляции, без возможности изменения. То есть, эта ветка 100 раз выполнялась, её предиктор пометил как приоритетную, а потом она 3 раза не выполнилась и предиктор промахнулся? А что будет, если её статически пометить как не приоритетную? Будет 100 промахов и 3 попадания?
Если они близки к случайным, то в половине случаев (!) предсказатель будет ошибаться и результат работы будет ужасныйЧто предлагается? Например, в цикле
for (int i = 0; i < N; i++) { if (i & 1) { .... } }
Перед условным переходом вычислить i&1 и выполнить какую-то команду, которая подскажет по значению i, будет ли переход? Так такая команда — то же самое, что и ветвление, почему бы эти 2 команды не объединить в логику инструкции ветвления. На самом дела, нужно загрузить конвеер намного заранее до вычисления условия, а не прямо перед ним.
if x > 0:
x -= 1
if y > 0:
y -= 1
if x * y > 0:
foo()
Если произойдёт переход по первой или второй ветви, то третья определённо останется незадействованной.
Пусть x = 3, y = 3… Независимо от того, по какой из ветвей пойдёт код (даже если по обеим), условие выполнения третьей всегда будет истинно (2*2 > 0). Или какой это язык? Возможно имелось ввиду x = -1?
В чём смысл вставок с кодом, если они никаких пояснений не вносят?
История предсказания переходов с 1 500 000 года до н.э. по 1995 год