Как се изпълнява fma().

Според документацията има функция fma() в math.h. Това е много хубаво и знам как работи 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 използва round to even по подразбиране, ако не греша. Защо кръгло към нечетно е уместно в този контекст? В момента внедрявам библиотека с множествена точност, така че съм малко запознат с вътрешната работа, но не съм чувал за кръгло към нечетно да е особено важно. Много поетично между другото, браво! - 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-битови числа, които и двете могат да бъдат точно представени като float (един неявен бит + 23 дробни). Техният продукт ще бъде два пъти по-голям от ширината, така че ще се закръгли и сумата по същия начин. Разбирам, мислех, че ще трябва изрично да закръглите цялото число до най-близкото четно, не разбрах, че това се случва автоматично при преобразуването в плаващо - очаквах да видя някои битови операции там. Няма да провери дали 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};
}

След като разделите 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 в статията Числа с плаваща запетая с разширена точност за 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
Вашата версия може да е по-бърза, тъй като не обработва специални числа (особено безкрайности). Може да греша, но изглежда, че умножаването / добавянето с безкрайност ще накара алгоритъма на Dekker да генерира NaN. Очаквам времето за изпълнение да се държи правилно там, оттам и наказанието за скорост. - person the swine; 12.05.2015
comment
Има много повече от _mm_fmadd_ss за комплекта x86 (и _mm_fmadd_ps така или иначе ми е по-интересен), ако искате да видите всички тях, отидете на IntrinsicsGuide и под Technologies изберете 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, за съжаление е неправилно. За разлика от twoProduct на Dekker, в по-общия случай на FMA големината на c не е известна спрямо условията на продукта и следователно могат да възникнат грешни анулации.

И така, докато twoProduct на Dekker може да бъде значително ускорен с хардуерен FMA, изчислението на термина за грешка на twoProduct на Dekker не е стабилна 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