Подтвердить что ты не робот

Отраслевое предсказание и разделение на ноль

Я писал код, который выглядел следующим образом:

if(denominator == 0){
    return false;
}
int result = value / denominator;

... когда я думал о ветвящем поведении в CPU.

qaru.site/info/1/... В этом ответе говорится, что ЦП будет пытаться правильно догадаться, в какую сторону будет идти ветка, и опускать эту ветвь только для остановки, если она обнаружит ее неверно догадался о филиале.

Но если процессор предсказывает ветвь выше неправильно, она будет делиться на ноль в следующих инструкциях. Однако этого не происходит, и мне было интересно, почему? Фактически ли ЦПУ выполняет деление на ноль и ждет, чтобы убедиться, что ветка правильная, прежде чем что-либо делать, или она может сказать, что она не должна продолжаться в этих ситуациях? Что происходит?

4b9b3361

Ответ 1

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

Как проектировщик ЦП, я бы не стал предсказывать прошлую такую ​​ошибку. Это, вероятно, не стоит. Вероятность ошибки, вероятно, означает плохое предсказание, и это скоро разрешится.

Эта свобода - хорошая вещь. Рассмотрим простой цикл std::accumulate. Предиктор ветвления будет правильно предсказать много прыжков (for (auto current = begin, current != end; ++current), которые обычно возвращаются к началу цикла), и есть много считываний в памяти, которые могут потенциально вызвать ошибку (sum += *current). Но процессор, который откажется читать значение памяти до тех пор, пока предыдущая ветвь не будет решена, будет намного медленнее. И все же неверно предсказанный прыжок в конце цикла может очень хорошо вызвать безобидную ошибку памяти, поскольку предсказанная ветка пытается прочесть за буфером. Это необходимо устранить без видимой неисправности.

Ответ 2

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

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

Для аналогии со ссылкой, поезд должен немедленно остановиться на перекрестке, если переключатель не был в правильном положении, он не может перейти на следующую станцию ​​по неправильному пути или если он не может остановиться до этого, нет пассажиров разрешается въезжать или выходить из поезда

(*) Процессоры Itanium могли бы обрабатывать многие пути параллельно. Логика Intel заключалась в том, что они могут создавать широкие процессоры (которые много работают параллельно), но они боролись с эффективной скоростью обучения. Посредством спекулятивного выполнения обеих ветвей они использовали много аппаратного обеспечения (я думаю, что они могли бы сделать это на несколько уровней в глубину, работая на 2 ^ N ветвях), но это помогло очевидной скорости одного ядра, так как оно всегда предсказывало правильную ветвь в одном HW единица - Кредиты должны отправляться в MSalters для этой точности

Ответ 3

Но если процессор предсказывает ветвь выше неправильно, она будет делить на ноль в следующих инструкциях. Однако этого не происходит, и Мне было интересно, почему?

Это может случиться, однако вопрос в том, является ли это наблюдаемым? Очевидно, что это спекулятивное деление на ноль не должно и не должно "терпеть крах" процессора, но это не происходит даже для неспекулятивного деления на ноль. Существует длинная причинно-следственная цепочка между делением на ноль и процессом, выходящим с сообщением об ошибке. Это примерно так (на POSIX, x86):

  • ALU или микрокод, ответственный за деление, делит на нуль как ошибку.
  • Загружается дескриптор прерывания # 0 (int 0 означает деление на нулевую ошибку на x86).
  • Набор регистров (включая текущий счетчик программ) помещается в стек. Соответствующие строки кэша, возможно, необходимо извлечь из ОЗУ.
  • Выполняется обработчик прерываний (часть кода ядра). Он вызывает сигнал SIGFPE в текущем процессе.
  • В конце концов обработка сигнала решает, что действие по умолчанию должно быть принято (при условии, что вы не установили обработчик), который должен отображать сообщение об ошибке и завершать процесс.
  • Это требует много дополнительных действий (например, использование драйверов устройств) до тех пор, пока пользователь не увидит изменения, а именно некоторые графики, выводимые с помощью ввода-вывода с отображением памяти.

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

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

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

Ответ 4

Деление на ноль ничего особенного. Это условие, которое обрабатывается ALU, чтобы дать некоторый эффект, например присвоение частного значения частному. Он также может вызвать исключение, если этот тип исключения включен.

Сравнение с фрагментом

if (denominator == 0) {
    return false;
}
int result = value * denominator;

Умножение может выполняться спекулятивно, а затем отменяется без вашего ведома. То же самое для деления. Не беспокойтесь.