Как реализуется fma()

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Ответ 2

Одним из способов реализации 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};
}

Как только вы разделите float, вы можете рассчитать 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 в документе Расширенные значения чисел с плавающей запятой для вычисления графического процессора и из 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 в любом случае).

Ответ 3

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

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

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