Почему моя программа на C++ такая медленная при переключении с long double на float128?

Я программирую на Unix, используя компилятор g++ 4.8.2. В настоящее время мне нужно конвертировать мою программу на C++, которая на данный момент использует long double (со значением 64 бит в моем случае), для программы, которая использует __float128 тип (со значением 113 бит). Для этого я использовал пакет libquadmath0 и библиотеку boost, но результирующая программа работает в 10-20 раз медленнее, чем с long double,

Это сбивает с толку, так как размер значения не намного выше, и я не заметил такой разницы при переходе от double в long double, Нормальная ли разница во времени, и если нет, как я могу это исправить?

Код:

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <complex.h>
extern "C" {
#include <quadmath.h>
}
#include <gmp.h>
#include <iomanip>
#include <cfloat>
#include <boost/multiprecision/float128.hpp>


using namespace boost::multiprecision;
using namespace std;

typedef __float128 long_double_t;

void main()
{
...
}

Инструкция по компиляции:

g++ --std=c++11 main.cc -o main -lgmp -lquadmath -Ofast -m64

1 ответ

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

Возьмем простой пример: используйте карманный калькулятор с 12 цифрами, чтобы сложить два 11-значных числа, и с помощью этого калькулятора сложите два 23-значных числа. Как вы думаете, какое из них будет медленнее? Очевидно, что последнее требует намного больше операций (а также места, так как вам нужно записать промежуточные результаты в бумагу)

В x86 имеется аппаратная поддержка одинарной, двойной и 80-битной расширенной точности IEEE-754, поэтому операции с этими типами полностью выполняются аппаратно, что обычно представляет собой всего лишь одну инструкцию. double + double ничем не отличается от long double + long double, что то же самое FADDинструкция в x87. Если вы используете SSE, то double будет немного быстрее, чем из-за использования новых регистров и инструкций SIMD

Когда вы используете __float128однако компилятор должен использовать программную эмуляцию, которая работает намного медленнее. Вы не можете добавить 2 long doubleзначения с 2 инструкциями. Все нужно делать вручную:

  • Разбейте компоненты знака, экспоненты и мантиссы (как минимум ~3 инструкции). Значение должно храниться в нескольких регистрах, потому что у вас нет такого большого единственного целочисленного регистра.
  • Выровняйте положение точки счисления для двух значений, что требует множества операций сдвига и маски (опять же, потому что мантисса хранится в нескольких регистрах)
  • Добавьте 2 значения, для чего нужны 2 инструкции на 64-битной платформе.
  • Нормализовать результат, для чего необходимо проверить сумму на условия переполнения / недостаточного переполнения, найти наиболее значимую битовую позицию, вычислить экспоненту ...
  • Объедините знак, показатель и значение результата

Эти шаги включают в себя несколько ветвей (что может привести к неверному предсказанию ветвления), загрузку / сохранение памяти (потому что x86 не имеет большого количества регистров) и многое другое, что в итоге приводит к минимуму десятков инструкций. Выполнение сложных задач в 10 раз медленнее - уже большое достижение. И мы еще не подошли к умножению, которое становится в 4 раза сложнее, когда ширина значащей увеличивается вдвое. Деление, квадратный корень, возведение в степень, тригонометрия ... намного сложнее и будут значительно медленнее

Другие вопросы по тегам