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