Ответ 1
На практике существует множество примеров алгоритма. Например:
Ньютон Рафсон с SSE2 - может кто-нибудь объяснить мне эти 3 строки имеет ответ, объясняющий итерацию, используемую один из примеров Intel.
Для персистентного анализа пусть Haswell (который может FP mul на двух портах выполнения, в отличие от предыдущих проектов), я воспроизведу код здесь (с одним оператором на строку). См. http://agner.org/optimize/ для таблиц пропускной способности и латентности команд, а также для документов о том, как понять больше фона.
// execute (aka dispatch) on cycle 1, results ready on cycle 6
nr = _mm_rsqrt_ps( x );
// both of these execute on cycle 6, results ready on cycle 11
xnr = _mm_mul_ps( x, nr ); // dep on nr
half_nr = _mm_mul_ps( half, nr ); // dep on nr
// can execute on cycle 11, result ready on cycle 16
muls = _mm_mul_ps( xnr , nr ); // dep on xnr
// can execute on cycle 16, result ready on cycle 19
three_minus_muls = _mm_sub_ps( three, muls ); // dep on muls
// can execute on cycle 19, result ready on cycle 24
result = _mm_mul_ps( half_nr, three_minus_muls ); // dep on three_minus_muls
// result is an approximation of 1/sqrt(x), with ~22 to 23 bits of precision in the mantissa.
Здесь есть много места для других вычислений, если оно не является частью цепочки зависимостей. Однако, если данные для каждой итерации вашего кода зависят от данных предыдущего, вам может быть лучше с задержкой в 11 циклов от sqrtps
. Или даже если каждая итерация цикла достаточно длинная, что выполнение вне порядка не может скрыть все это путем перекрытия независимых итераций.
Чтобы получить sqrt(x)
вместо 1/sqrt(x)
, умножьте на x
(и fixup, если x
может быть нулевым, например, путем маскировки (_mm_andn_ps
) с результатом CMPPS против 0,0). Оптимальным способом является замена half_nr
на half_xnr = _mm_mul_ps( half, xnr );
. Это не удлиняет цепочку отрезков, потому что half_xnr
может начинаться в цикле 11, но не требуется до конца (цикл 19). То же самое с доступным FMA: нет увеличения общей задержки.
Если достаточно 11 бит точности (без итерации Newton), Руководство по оптимизации Intel (раздел 11.12.3) предлагает использовать rcpps (rsqrt (x)), что хуже, чем умножение на исходное x, по крайней мере, с AVX. Это может спасти инструкцию movdqa с 128-разрядным SSE, но 256b rcpps медленнее, чем 256b mul или fma. (И он позволяет вам добавить результат sqrt к чему-то бесплатно с помощью FMA вместо mul для последнего шага).
Версия SSE этого цикла, не учитывающая никаких инструкций по перемещению, составляет 6 часов. Это означает, что он должен иметь пропускную способность в Haswell один на 3 цикла (учитывая, что два порта выполнения могут обрабатывать FP mul, а rsqrt находится на противоположном порту от FP add/sub). На SnB/IvB (и, вероятно, Nehalem) он должен иметь пропускную способность один на 5 циклов, так как mulps и rsqrtps конкурируют за порт 0. subps находится на порту1, что не является узким местом.
Для Haswell мы можем использовать FMA для объединения вычитания с mul. Однако блок divers/sqrt не имеет ширины 256b, поэтому, в отличие от всего остального, divps/sqrtps/rsqrtps/rcpps на ymm regs требует дополнительных часов и дополнительных циклов.
// vrsqrtps ymm has higher latency
// execute on cycle 1, results ready on cycle 8
nr = _mm256_rsqrt_ps( x );
// both of can execute on cycle 8, results ready on cycle 13
xnr = _mm256_mul_ps( x, nr ); // dep on nr
half_nr = _mm256_mul_ps( half, nr ); // dep on nr
// can execute on cycle 13, result ready on cycle 18
three_minus_muls = _mm256_fnmadd_ps( xnr, nr, three ); // -(xnr*nr) + 3
// can execute on cycle 18, result ready on cycle 23
result = _mm256_mul_ps( half_nr, three_minus_muls ); // dep on three_minus_muls
Мы сохраняем 3 цикла с FMA. Это компенсируется использованием 2-cyle-slower 256b rsqrt для чистого выигрыша на 1 цикл меньше латентности (довольно хорошо в два раза больше). SnB/IvB AVX будет латентностью 24 с для задержки 128b, 26c для 256b.
Версия 256b FMA использует 7 uops total. (VRSQRTPS
- 3 uops, 2 для p0 и один для p1/5.) 256b mulps и fma являются однопледными инструкциями, и оба могут работать на порту 0 или в порту 1. (p0 только на pre-Haswell), Таким образом, пропускная способность должна быть: одна на 3c, если двигатель ООО отправляет оптимальные порты выполнения. (т.е. перетасовка uop из rsqrt всегда переходит в p5, а не в p1, где она будет занимать полосу пропускания mul/fma.) Что касается перекрытия с другими вычислениями (а не только независимым выполнением самого себя), снова это довольно легкий. Только 7 uops с цепочкой отрезков в 23 цикла оставляют много места для других вещей, чтобы случиться, в то время как эти uops сидят в буфере повторного заказа.
Если это шаг в гигантской цепочке отрезков, где ничего не происходит (даже независимая следующая итерация), тогда 256b vsqrtps
составляет 19 циклов задержки с пропускной способностью по одному на 14 циклов. (Хасуэллы). Если вам по-прежнему нужна обратная связь, тогда 256b vdivps
также имеет задержку 18-21 с, причем одна на пропускную способность 14 с. Поэтому для нормального sqrt инструкция имеет более низкую задержку. Для рецепта sqrt итерация аппроксимации меньше латентности. (И FAR больше пропускной способности, если он перекрывается с самим собой. Если совпадение с другими элементами, не разделяющими единицу, sqrtps
не является проблемой.)
sqrtps
может быть победой пропускной способности против rsqrt
+ итерации Newton, если она является частью тела цикла с достаточным количеством других недифференцированных и не-sqrt-операций, которые идут на то, что единица деления не насыщена.
Это особенно актуально, если вам нужно sqrt(x)
, а не 1/sqrt(x)
, например на Haswell с AVX2 контур copy + arcsinh над массивом поплавков, который вписывается в кеш-память L3, реализованный как fastlog(v + sqrt(v*v + 1))
, работает примерно с той же пропускной способностью с реальным VSQRTPS или с VRSQRTPS + итерацией Newton-Raphson. (Даже при очень быстрое приближение для log(), поэтому общий корпус цикла составляет около 9 операций FMA/add/mul/convert и 2 boolean, плюс VSQRTPS ymm. Ускорение использования только fastlog(v2_plus_1 * rsqrt(v2_plus_1) + v2_plus_1)
, поэтому оно не является узким местом по пропускной способности памяти, но это может быть узким местом в латентности (поэтому выполнение вне порядка не может использовать все parallelism независимых итераций).
Другие замечания
Для полуточности нет инструкций по вычислению на половинных поплавках. Вы должны конвертировать "на лету" при загрузке/хранении, используя инструкции преобразования.
Для двойной точности нет rsqrtpd
. Предположительно, мышление состоит в том, что если вам не нужна полная точность, вы должны использовать float в первую очередь. Таким образом, вы можете конвертировать в float и обратно, затем выполнить тот же алгоритм, но с pd
вместо ps
intrinsics. Или вы могли бы хранить свои данные как float на некоторое время. например конвертировать два ymm-регистра удвоения в один ymm-регистр синглов.
К сожалению, нет ни одной инструкции, которая берет два ymm-регистра удваивает и выводит ymm синглов. Вы должны пойти ymm- > xmm дважды, затем _mm256_insertf128_ps
один xmm к высокому 128 другого. Но тогда вы можете подавать этот вектор 256b ymm на тот же алгоритм.
Если вы собираетесь преобразовать обратно в двойное право, возможно, имеет смысл выполнить итерацию rsqrt + Newton-Raphson на двух 128-битных регистрах одиночных игр отдельно. Дополнительные 2 раза для вставки/извлечения, а также дополнительные 2 uops для 256b rsqrt, начинают складываться, не говоря уже о 3-тактной задержке vinsertf128
/vextractf128
. Вычисление будет перекрываться, и оба результата будут готовы на пару циклов. (Или 1 цикл в отдельности, если у вас есть специальная версия вашего кода для операций чередования на 2 входа одновременно).
Помните, что у одной точности есть меньший диапазон экспонентов, чем двойной, поэтому преобразование может переполняться до + Inf или underflow до 0.0. Если вы не уверены, определенно просто используйте обычный _mm_sqrt_pd
.
С AVX512F, там _mm512_rsqrt14_pd( __m512d a)
. С AVX512ER (KNL, но не SKX или Cannonlake), там _mm512_rsqrt28_pd
. _ps
версии также существуют. 14 бит точности мантиссы может быть достаточным для использования без итерации Ньютона в других случаях.
Итерация Newton по-прежнему не даст вам правильно округленного одноточечного поплавка, как обычно будет выполняться sqrt.