Как реализован fma ()

Согласно документации, в math.h есть функция fma(). Это очень хорошо, и я знаю, как работает FMA и для чего ее использовать. Однако я не уверен, как это реализуется на практике? Меня больше всего интересуют архитектуры x86 и x86_64.

Есть ли инструкция с плавающей запятой (не векторная) для FMA, возможно, как определено в IEEE-754 2008?

Используется ли инструкция FMA3 или FMA4?

Есть ли что-то внутреннее, чтобы убедиться, что используется настоящая FMA, когда полагается на точность?


person the swine    schedule 20.02.2015    source источник
comment
На x86 и x86_64 gcc выдает инструкции fma, если ему разрешено (-mfma или -mfma4 или -march=something, где something - это процессор, поддерживающий fma). В Linux вы можете посмотреть sysdeps/ieee754/dbl-64/s_fma.c в glibc, чтобы получить представление о том, как выглядит резервная функция библиотеки.   -  person tmyklebu    schedule 20.02.2015


Ответы (3)


Фактическая реализация варьируется от платформы к платформе, но в очень широком смысле:

  • Если вы укажете компилятору нацеливаться на машину с аппаратными инструкциями FMA (PowerPC, ARM с VFPv4 или AArch64, Intel Haswell или AMD Bulldozer и далее), компилятор может заменить вызовы fma( ), просто отбросив соответствующий инструкция в ваш код. Это не гарантируется, но, как правило, является хорошей практикой. В противном случае вы получите вызов математической библиотеки и:

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

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

  • Результат функции fma( ) всегда должен быть правильно округлен (т. Е. «Настоящая fma»). Если это не так, это ошибка в математической библиотеке вашей системы. К сожалению, fma( ) - одна из наиболее сложных для правильной реализации функций математической библиотеки, поэтому многие реализации содержат ошибки. Сообщите о них продавцу вашей библиотеки, чтобы они были исправлены!

Есть ли что-то внутреннее, чтобы убедиться, что используется настоящая FMA, когда полагается на точность?

При наличии хорошего компилятора в этом не должно быть необходимости; достаточно использовать функцию fma( ) и сообщить компилятору, на какую архитектуру вы ориентируетесь. Однако компиляторы не идеальны, поэтому вам может потребоваться использовать _mm_fmadd_sd( ) и связанные с ним встроенные функции на x86 (но сообщите об ошибке поставщику компилятора!)

person Stephen Canon    schedule 20.02.2015
comment
«Возможность объяснять округлость - это как тур де Франс: его ждут долго, и он быстро проходит». - person Pascal Cuoq; 20.02.2015
comment
@PascalCuoq IEEE-754 по умолчанию использует округление до четности, если я не ошибаюсь. Почему в этом контексте уместно округление до нечетного? В настоящее время я реализую библиотеку с множественной точностью, поэтому я немного знаком с внутренней работой, но я не слышал, чтобы округление до нечетного было особенно важным. Кстати, очень поэтично, молодец! - person the swine; 20.02.2015
comment
@theswine Если у вас есть формат с двойной шириной FMA, к которой вы стремитесь, вы можете выполнить умножение без ошибок. Скажем, вы реализуете fmaf с двойной точностью double. Остается проблема добавления double значения (double)a*(double)b и float c и округления этого прибавления до ближайшего float. Эта операция обычно недоступна, но может быть реализована как double сложение с округлением до нечетного с последующим округлением от double до float с округлением до ближайшего. Отсутствие округления до нечетного для промежуточного результата вызывает проблемы с двойным округлением. - person Pascal Cuoq; 20.02.2015
comment
@theswine См. код: permalink.gmane.org/gmane.comp .lib.glibc.alpha / 15546 - person Pascal Cuoq; 20.02.2015
comment
@PascalCuoq Понятно. Это довольно элегантно. Если бы я реализовывал fma в программном обеспечении, я бы пошел путем вычисления приблизительного результата и поправки в виде расширения. Затем его можно округлить до ближайшего представимого числа. У него есть то преимущество, что это можно сделать и с двойной (без необходимости в длинной двойной поддержке), но для поплавков это, вероятно, будет медленнее с точки зрения количества операций. Однако изменение режима округления x86 происходит довольно медленно, поэтому в конечном итоге его можно сравнить с двойным и округленным до нечетного. - person the swine; 20.02.2015
comment
@PascalCuoq вы случайно не написали этот патч? - person the swine; 20.02.2015
comment
Я не писал патч, на который ссылался, но я написал правильный (AFAICT) fmaf, используя наивный подход: (при условии a, b, c ≥ 0) ideone.com/kx7MXE. И вам также следует взглянуть на эту реализацию, если вас интересует тема: opensource.apple.com/source/Libm/Libm-315/Source/Intel/ - person Pascal Cuoq; 20.02.2015
comment
@PascalCuoq, вы уверены, что ваш fmaf правильный? Если я правильно понимаю, вы умножаете / добавляете точно представимые целые числа, давая точно представимый результат. Хотя это проверяет, что он действительно вычисляет a + b * c, я считаю, что он не проверяет округление, не говоря уже об обработке переполнения. Однако я могу ошибаться, и я, конечно же, не утверждаю, что эта процедура ошибочна - только модульный тест. - person the swine; 21.02.2015
comment
@theswine Я думаю, что и реализация, и тест довольно хороши для комментария в ответ на косвенное замечание к сообщению в блоге (контекст, в котором эта функция была написана). Я не понимаю, что вы говорите об округлении. Вы хотите сказать, что в float truefma = (long long) a * b + c; нет округления? - person Pascal Cuoq; 21.02.2015
comment
@theswine Я признаю, что тест излишне ограничен: все эти маски с 0xfffff должны быть масками с 0xffffff. Я посмотрю, позволит ли мне ideone это изменить. - person Pascal Cuoq; 21.02.2015
comment
@PascalCuoq извините, я не хотел критиковать, и я думаю, что ваш код великолепен, но вы написали его для какой-то цели, а не из-за меня. И я благодарен за кучу интересных статей о круглых с лишним. Мне просто интересно, можно ли как-нибудь улучшить тест. Я думаю, если вы умножите несколько более длинные числа на int64, а затем перейдете на 23 дробных бита (поскольку это число с плавающей запятой), вы сможете смоделировать и проверить округление. - person the swine; 22.02.2015
comment
Округление @theswine при преобразовании a, b или c в float не является необходимым и действительно является неприятностью, которую нужно будет контролировать, если это произойдет. Пока существует округление при преобразовании значения long long (long long) a * b + c в float (и такое преобразование происходит неявно в присваивании float truefma = …), тест проверяет поведение myfma в отношении округления. Округление происходит во время этого присвоения из-за значений различных масок, примененных к результатам rand(). Таким образом, тест проверяет поведение myfma с округлением. - person Pascal Cuoq; 22.02.2015
comment
@theswine Вы ведь понимаете, что делают (rand() & 0xFFFFFF) << (rand() & 7) и (long long)(rand() & 0xFFFFFF) << (rand() & 31), верно? - person Pascal Cuoq; 22.02.2015
comment
@PascalCuoq Он генерирует два (до) 24-битных числа, которые оба точно могут быть представлены как числа с плавающей запятой (один неявный бит + 23 дробных числа). Их продукт будет вдвое больше ширины, поэтому округлится и сумма. Понятно, я думал, что вам нужно явно округлить целое число до ближайшего четного, я не понимал, что это происходит автоматически при преобразовании в float - я ожидал увидеть там несколько битовых операций. Он не будет проверять, установлен ли режим FPU, например. округлите до нуля (но опять же, ваша процедура, скорее всего, в таком случае не сработает, вы просто никогда не знаете, произойдет ли это). - person the swine; 22.02.2015
comment
@PascalCuoq Однако вы можете убедиться, что RAND_MAX является степенью двойки больше, чем 0xffffff, на некоторых платформах это не так. Извиняюсь за пространные комментарии. Если вы просто ненавидите обсуждение на этом этапе, нам не нужно продолжать. Я просто пытаюсь внести свой вклад. - person the swine; 22.02.2015

Одним из способов программной реализации FMA является разделение значимого на старшие и младшие биты. Я использую Алгоритм Деккера

typedef struct { float hi; float lo; } doublefloat;  
doublefloat split(float a) {
    float t = ((1<<12)+1)*a;
    float hi = t - (t - a);
    float lo = a - hi;
    return (doublefloat){hi, lo};
}

После того, как вы разделите число с плавающей запятой, вы можете вычислить a*b-c с одним округлением, как это

float fmsub(float a, float b, float c) {
    doublefloat as = split(a), bs = split(b);
    return ((as.hi*bs.hi - c) + as.hi*bs.lo + as.lo*bs.hi) + as.lo*bs.lo;
}

Это в основном вычитает c из (ahi,alo)*(bhi,blo) = (ahi*bhi + ahi*blo + alo*bhi + alo*blo).

Я получил эту идею от функции twoProd в статье Числа с плавающей запятой повышенной точности для вычислений на GPU и из функции mul_sub_x в библиотеке векторных классов Agner Fog. Он использует другую функцию для разделения векторов поплавков, которая разделяется по-разному. Я пытался воспроизвести здесь скалярную версию

typedef union {float f; int i;} u;
doublefloat split2(float a) {
    u lo, hi = {a};
    hi.i &= -(1<<12);
    lo.f = a - hi.f;
    return (doublefloat){hi.f,lo.f};
}

В любом случае использование split или split2 в fmsub хорошо согласуется с fma(a,b,-c) из математической библиотеки в glibc. По какой-то причине моя версия значительно быстрее, чем fma, за исключением машины с аппаратным fma (в этом случае я все равно использую _mm_fmsub_ss).

person Z boson    schedule 08.05.2015
comment
Хорошие ссылки. Мне известны работы Щевчука и Приста. В этом вопросе меня больше интересовало, какие инструкции есть в текущих наборах инструкций. Думаю, _mm_fmadd_ss в значительной степени подводит итог. - person the swine; 12.05.2015
comment
Ваша версия может быть быстрее, поскольку она не обрабатывает специальные числа (особенно бесконечности). Я могу ошибаться, но кажется, что умножение / сложение с бесконечностью заставит алгоритм Деккера генерировать NaN. Я ожидал, что среда выполнения будет вести себя там правильно, отсюда и снижение скорости. - person the swine; 12.05.2015
comment
Для набора x86 гораздо больше, чем _mm_fmadd_ss_mm_fmadd_ps в любом случае мне интереснее), если вы хотите увидеть все, перейдите на IntrinsicsGuide и в разделе" Технологии "выберите FMA. - person Z boson; 12.05.2015
comment
@theswine, хорошие замечания по поводу специальных номеров. Это может объяснить снижение скорости с fma для glibc. - person Z boson; 13.05.2015
comment
А как насчет вычислений a*b+c? - person plasmacel; 19.06.2018
comment
@plasmacel, я думаю, ты меняешь (as.hi*bs.hi - c) на (as.hi*bs.hi + c). - person Z boson; 20.06.2018

К сожалению, предложение FMA Z-бозона, основанное на алгоритме Деккера, неверно. В отличие от Dekker's twoProduct, в более общем случае FMA величина c не известна относительно условий продукта, и, следовательно, могут произойти неправильные отмены.

Таким образом, хотя Dekker's twoProduct может быть значительно ускорен с помощью аппаратного FMA, вычисление условия ошибки для Dekker twoProduct не является надежной реализацией FMA.

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

person aki    schedule 10.01.2017
comment
Обратите внимание, что он делает fmsub. Предполагая, что количества положительные, я бы сказал, что его реализация работает. Во всяком случае, яркий комментарий от кого-то с 11 xp, хорошая работа. - person the swine; 14.01.2017
comment
Да нет, ты прав. Если c очень мало, то при вычитании из ahi*bhi он забивается округлением, и это совершенно не помогает. Ему нужно будет сформировать более длинное расширение и начать добавлять с самого маленького элемента, используя, по сути, то, что известно как суммирование Кахана. Несмотря на то, что результат округляется до числа с плавающей запятой, этот порядок по-прежнему имеет значение, поскольку он может повлиять на направление округления. - person the swine; 14.01.2017
comment
Я написал краткое замечание о том, что суммирование Кахана здесь недостаточно, а затем понял, что вы действительно имели в виду выполнение обоих, сортировку входных данных по величине и затем сложение с суммированием Кахана. Я полностью согласен с тем, что комбинация будет дают правильно округленный результат FMA. - person aki; 18.01.2017