Введение
В научных вычислениях мы часто используем числа с плавающей запятой (плавающей точкой). Эта статья представляет собой руководство по выбору правильного представления числа с плавающей запятой. В большинстве языков программирования есть два встроенных вида точности: 32-битная (одинарная точность) и 64-битная (двойная точность). В семействе языков C они известны как
float
и double
, и здесь мы будем использовать именно такие термины. Есть и другие виды точности: half
, quad
и т. д. Я не буду заострять на них внимание, хотя тоже много споров возникает относительно выбора half
vs float
или double
vs quad
. Так что сразу проясним: здесь идёт речь только о 32-битных и 64-битных числах IEEE 754.Статья также написана для тех из вас, у кого много данных. Если вам требуется несколько чисел тут или там, просто используйте
double
и не забивайте себе голову!Статья разбита на две отдельные (но связанные) дискуссии: что использовать для хранения ваших данных и что использовать при вычислениях. Иногда лучше хранить данные во
float
, а вычисления производить в double
.Если вам это нужно, в конце статьи я добавил небольшое напоминание, как работают числа с плавающей запятой. Не стесняйтесь сначала прочитать его, а потом возвращайтесь сюда.
Точность данных
У 32-битных чисел с плавающей запятой точность примерно 24 бита, то есть около 7 десятичных знаков, а у чисел с двойной точностью — 53 бита, то есть примерно 16 десятичных знаков. Насколько это много? Вот некоторые грубые оценки того, какую точность вы получаете в худшем случае при использовании
float
и double
для измерения объектов в разных диапазонах:Масштаб | Одинарная точность | Двойная точность |
---|---|---|
Размер комнаты | микрометр | радиус протона |
Окружность Земли | 2,4 метра | нанометр |
Расстояние до Солнца | 10 км | толщина человеческого волоса |
Продолжительность суток | 5 миллисекунд | пикосекунда |
Продолительность столетия | 3 минуты | микросекунда |
Время от Большого взрыва | тысячелетие | минута |
(пример: используя
double
, мы можем представить время с момента Большого взрыва с точностью около минуты).Итак, если вы измеряете размер квартиры, то достаточно
float
. Но если хотите представить координаты GPS с точностью менее метра, то понадобится double
.Почему всегда не хранить всё с двойной точностью?
Если у вас много оперативной памяти, а скорость выполнения и расход аккумулятора не являются проблемой — вы можете прямо сейчас прекратить чтение и использовать
double
. До свидания и хорошего вам дня!Если же память ограничена, то причина выбора
float
вместо double
проста: он занимает вдвое меньше места. Но даже если память не является проблемой, сохранение данных во float
может оказаться значительно быстрее. Как я уже упоминал, double
занимает в два раза больше места, чем float
, то есть требуется в два раза больше времени для размещения, инициализации и копирования данных, если вы используете double
. Более того, если вы считываете данные непредсказуемым образом (случайный доступ), то с double
у вас увеличится количество промахов мимо кэша, что замедляет чтение примерно на 40% (судя по практическому правилу O(vN), что подтверждено бенчмарками).Влияние на производительность вычислений с одинарной и двойной точностью
Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив
double
на float
. Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float
и double
маленькая, а на ARM Cortex-A9 разница большая. Исчерпывающие результаты тестов см. здесь.Конечно, если данные хранятся в
double
, то мало смысла производить вычисления во float
. В конце концов, зачем хранить такую точность, если вы не собираетесь её использовать? Однако обратное неправильно: может быть вполне оправдано хранить данные во float
, но производить некоторые или все вычисления с двойной точностью.Когда производить вычисления с увеличенной точностью
Даже если вы храните данные с одинарной точностью, в некоторых случаях уместно использовать двойную точность при вычислениях. Вот простой пример на С:
float sum(float* values, long long count)
{
float sum = 0;
for (long long i = 0; i < count; ++i) {
sum += values[i];
}
return sum;
}
Если вы запустите этот код на десяти числах одинарной точности, то не заметите каких-либо проблем с точностью. Но если запустите на миллионе чисел, то определённо заметите. Причина в том, что точность теряется при сложении больших и маленьких чисел, а после сложения миллиона чисел, вероятно, такая ситуация встретится. Практическое правило такое: если вы складываете 10^N значений, то теряете N десятичных знаков точности. Так что при сложении тысячи (10^3) чисел теряются три десятичных знака точности. Если складывать миллион (10^6) чисел, то теряются шесть десятичных знаков (а у
float
их всего семь!). Решение простое: вместо этого выполнять вычисления в формате double
:float sum(float* values, long long count)
{
double sum = 0;
for (long long i = 0; i < count; ++i) {
sum += values[i];
}
return (float)sum;
}
Скорее всего, этот код будет работать так же быстро, как и первый, но при этом не будет теряться точность. Обратите внимание, что вовсе не нужно хранить числа в
double
, чтобы получить преимущества увеличенной точности вычислений!Пример
Предположим, что вы хотите точно измерить какое-то значение, но ваше измерительное устройство (с неким цифровым дисплеем) показывает только три значимых разряда. Измерение переменной десять раз выдаёт следующий ряд значений:
3.16, 3.15, 3.16, 3.18, 3.15, 3.11, 3.14, 3.11, 3.14, 3.15
Чтобы увеличить точность, вы решаете сложить результаты измерений и вычислить среднее значение. В этом примере используется число с плавающей запятой в base-10, у которого точность составляет точно семь десятичных знаков (похоже на 32-битный
float
). С тремя значимыми разрядами это даёт нам четыре дополнительных десятичных знака точности:3.160000 +
3.150000 +
3.160000 +
3.180000 +
3.150000 +
3.110000 +
3.140000 +
3.110000 +
3.140000 +
3.150000 =
31.45000
В сумме уже четыре значимых разряда, с тремя свободными. Что если сложить сотню таких значений? Тогда мы получим нечто вроде такого:
314.4300
Всё ещё остались два неиспользованных разряда. Если суммировать тысячу чисел?
3140.890
Десять тысяч?
31412.87
Пока что всё хорошо, но теперь мы используем все десятичные знаки для точности. Продолжим складывать числа:
31412.87 +
3.11 =
31415.98
Заметьте, как мы сдвигаем меньшее число, чтобы выровнять десятичный разделитель. У нас больше нет запасных разрядов, и мы опасно приблизились к потере точности. Что если сложить сто тысяч значений? Тогда добавление новых значений будет выглядеть так:
314155.6 +
3.12 =
314158.7
Обратите внимание, что последний значимый разряд данных (2 в 3.12) теряется. Вот теперь потеря точности действительно происходит, поскольку мы непрерывно будем игнорировать последний разряд точности наших данных. Мы видим, что проблема возникает после сложения десяти тысяч чисел, но до ста тысяч. У нас есть семь десятичных знаков точности, а в измерениях имеются три значимых разряда. Оставшиеся четыре разряда — это четыре порядка величины, которые выполняют роль своеобразного «числового буфера». Поэтому мы можем безопасно складывать четыре порядка величины = 10000 значений без потери точности, но дальше возникнут проблемы. Поэтому правило следующее:
Если в вашем числе с плавающей запятой P разрядов (7 для
float
, 16 для double
) точности, а в ваших данных S разрядов значимости, то у вас остаётся P-S разрядов для манёвра и можно сложить 10^(P-S) значений без проблем с точностью. Так, если бы мы использовали 16 разрядов точности вместо 7, то могли бы сложить 10^(16-3) = 10 000 000 000 000 значений без проблем с точностью.(Существуют численно стабильные способы сложения большого количества значений. Однако простое переключение с
float
на double
гораздо проще и, вероятно, быстрее).Выводы
- Не используйте лишнюю точность при хранении данных.
- Если складываете большое количество данных, переключайтесь на двойную точность.
Приложение: Что такое число с плавающей запятой?
Я обнаружил, что многие на самом деле не вникают, что такое числа с плавающей запятой, поэтому есть смысл вкратце объяснить. Я пропущу здесь мельчайшие детали о битах, INF, NaN и поднормалях, а вместо этого покажу несколько примеров чисел с плавающей запятой в base-10. Всё то же самое применимо к двоичным числам.
Вот несколько примеров чисел с плавающей запятой, все с семью десятичными разрядами (это близко к 32-битному
float
).1.875545 · 10^-18 = 0.000?000?000?000?000?001?875?545
3.141593 · 10^0 = 3.141593
2.997925 · 10^8 = 299?792?500
6.022141 · 10^23 = 602?214?100?000?000?000?000?000
Выделенная жирным часть называется мантиссой, а выделенная курсивом — экспонентой. Вкратце, точность хранится в мантиссе, а величина в экспоненте. Так как с ними работать? Ну, умножение производится просто: перемножаем мантисссы и складываем экспоненты:
1.111111 · 10^42 · 2.000000 · 10^7
= (1.111111 · 2.000000) · 10^(42 + 7)
= 2.222222 · 10^49
Сложение немного хитрее: чтобы сложить два числа разной величины, сначала нужно сдвинуть меньшее из двух чисел таким образом, чтобы запятая находилась в одном и том же месте.
3.141593 · 10^0 + 1.111111 · 10^-3 =
3.141593 + 0.0001111111 =
3.141593 + 0.000111 =
3.141704
Заметьте, как мы сдвинули некоторые из значимых десятичных знаков, чтобы запятые совпадали. Другими словами, мы теряем точность, когда складываем числа разных величин.
Комментарии (102)
f1inx
28.06.2017 14:10+3long long в примере вместо size_t ранит сердца опытных разработчиков (особенно знающих, что такое n32).
Кроме того всегда нужно оценивать необходимую разрядность мантиссы вместо тупого использования double вместо float.
Если в вашем числе с плавающей запятой P разрядов (7 для float, 16 для double) точности, а в ваших данных S разрядов значимости, то у вас остаётся P-S разрядов для манёвра и можно сложить 10^(P-S) значений без проблем с точностью. Так, если бы мы использовали 16 разрядов точности вместо 7, то могли бы сложить 10^(16-3) = 10 000 000 000 000 значений без проблем с точностью.
(Существуют численно стабильные способы сложения большого количества значений. Однако простое переключение с float на double гораздо проще и, вероятно, быстрее).
Проблемы с точностью у double могут возникнуть уже после сложения 2 чисел при разности порядков более 13, разве нет?
Не нужно поселять необоснованную уверенность универсальности double в мозги юных подаванов.
И это не частность, достаточно не мало задач, где нас интересует потом SAD в данных или именно младшие разряды.
alexeykuzmin0
28.06.2017 15:36Жаль, что не раскрыта тема вычислений на GPU, ведь именно там наблюдается наибольшая потеря скорости при переключении с float на double (например, для GeForce 900 series разница будет в 32 раза).
BD9
28.06.2017 16:03Обычно расчёты ведут на x86 (IA-32, AMD64). Соответственно полезно знать про внутреннее представление чисел (Extended precision). Кроме 80 бит добавляются новые виды нечисел (не-чисел).
Вместо разрядности чисел можно выбирать другие алгоритмы расчёта (с улучшениями в сохранении точности).
Также: округлением чисел можно управлять.
Насколько понимаю, результат расчётов может зависеть от использования обычного FPU с обычными числами или использования SIMD (SSE, AVX): в первом случае внутреннее представление будет 80 бит, во втором — 64 или 32.
И
https://habrahabr.ru/post/112953/#comment_3623058
https://habrahabr.ru/post/112953/#comment_8730233iburyl
28.06.2017 17:2580 бит только тогда, когда вас совершенно не интересует производительность решения, но есть ощущение, что ещё чуть-чуть и хватит точности. По большому счёту, в 2017 году можно смело рекомендовать не смотреть на 80 бит никогда, если только это не жуткое легаси.
Если даблов не хватает, а производительность не интересует, то есть библитеки для вычислений произвольной точность, типа GNU MPFR.
Если даблов не хватает, но производительность важна — меняйте алгоритм.
marsianin
28.06.2017 22:24Инструкции из набора x87 это legacy, в процессорах эти инструкции работают весьма медленно. И если нет требований именно к точности вычислений, лучше про них вообще забыть. Но выполнять точные вычисления на x87 это так себе занятие. В документации описаны ситуации, когда младший бит мантиссы результата принимает непредсказуемое значение. К сожалению, прямо сейчас я не смогу предоставить ссылку на конкретный раздел документации, но если интересно, завтра могу уточнить.
netch80
30.06.2017 08:03> Инструкции из набора x87 это legacy, в процессорах эти инструкции работают весьма медленно.
На десктопных и серверных процессорах это пока что не так. (Коллега khim@ говорил про проблемы на Atom и Bobcat, надо уточнять.) Я рядом показывал результаты от его теста после устранения проблем передачи параметров, разницы вообще не было.
Для сравнения стащил очень прямолинейный расчёт СЛАУ по Гауссу, матрица 1000*1000, случайные коэффициенты; i3-4170 (Haswell).
FPU+double: 4.6сек; FPU+long double: 5.5 сек — соответствует оценке влияния кэширования; SSE: 3.6 сек — с тем, что в выхлопе clang есть лёгкая векторизация.
Это ближе к тому, что времена собственно вычисления не отличаются.
> В документации описаны ситуации, когда младший бит мантиссы результата принимает непредсказуемое значение. К сожалению, прямо сейчас я не смогу предоставить ссылку на конкретный раздел документации, но если интересно, завтра могу уточнить.
Прошу таки уточнить, и заодно к каким версиям это относится. Где-то до 387-го у них было ещё много странностей, но потом вроде всё вычистили?
P.S. Есть один момент, в котором я полностью согласен, что x87 это legacy: это его неуместный стек регистров. Кому и зачем это стукнуло в голову — ХЗ, но это не единственная, мягко говоря, странность Intel. Но сейчас и его помеху снизили до минимума.marsianin
30.06.2017 13:25Относительно неточных вычислений, можно прочитать в «Intel® 64 and IA-32 Architectures Software Developer’s Manual», том 1, раздел 8.3.10, «Transcendental Instruction Accuracy»:
…
The accuracy of these instructions is measured in terms of units in the last place (ulp).
…
With the Pentium processor and later IA-32 processors, the worst case error on transcendental functions is less
than 1 ulp when rounding to the nearest (even) and less than 1.5 ulps when rounding in other modes.
Если это интерпретировать, получается, что младший бит в половине случаев будет неправильным. Чтобы он был правильным, необходима точность 0.5 ulp или лучше.vadimr
30.06.2017 16:00+1Более того, Intel и AMD дают разные результаты в младшем бите.
khim
01.07.2017 00:42Более того — это реально создаёт проблемы. У меня есть знакомые, которые исследовали как белок сворачивается. И вот это вот свойство выпило им много крови при написаниях тестов.
Понятно что получались два примерно разных результата. Понятно что «в природе» белок мог свернуться туда и сюда в таких случаях. Но когда на Intel'е он сворачивается в одну сторону, а на AMD — в другую… это просто усложняет отладку, вот и всё… но всё равно выливается в серьёзные затраты.netch80
01.07.2017 21:48+1Если на разных FPU белок сворачивается в разные стороны, значит, вычисления очень чувствительны к погрешностям такого порядка (~1ulp), а это уже признак, что им надо или повышать точность значений (double недостаточно), или менять модель на более устойчивую. При устойчивой модели такие различия могут сбить младшие разряды (нарушение духа IEEE754 — результаты должны быть воспроизводимы на любом железе), но не привести к принципиально различным результатам.
khim
02.07.2017 02:17Если на разных FPU белок сворачивается в разные стороны, значит, вычисления очень чувствительны к погрешностям такого порядка (~1ulp), а это уже признак, что им надо или повышать точность значений (double недостаточно), или менять модель на более устойчивую.
А теперь, внимание, вопрос: как вы собрались «менять модель на более устойчивую», если оригинальное явление обладает неустойчивостью? Которую мы, собственно, и исследуем? Как я уже сказал: если этот белок реально синтезировать и посмотреть — куда он сворачивается, то там возможны несколько вариантов. Причём это явление настолько распространено, что в клетке бывают даже специальные «помощники», помогающие определённым белкам выбрать правильное направление сворачивания. В совсем клинических случаях один и тот же белок, свёрнутый по разному, используется клеткой для разных целей (но, понятно, в лекарствах такого ужаса стараются не допускать).
При устойчивой модели такие различия могут сбить младшие разряды (нарушение духа IEEE754 — результаты должны быть воспроизводимы на любом железе), но не привести к принципиально различным результатам.
Вот только не всё в этом мире описывается устройчивыми моделями…
Понятно, что когда у вас явление неустойчиво малые модификации кода могут приводить к тому, что результаты будут меняться — тут ничего страшного нет, ещё раз повторюсь, мы исследуем явление, которое само по себе неустойчиво. Но когда результаты меняются без модификации кода — это просто неприятно и неудобно…netch80
02.07.2017 07:35> А теперь, внимание, вопрос: как вы собрались «менять модель на более устойчивую», если оригинальное явление обладает неустойчивостью? Которую мы, собственно, и исследуем?
Для неустойчивых явлений должна быть устойчивая модель анализа собственно неустойчивости. Методы для этого в общем известны, хотя там много уникальной теоретической работы в каждом случае.
> Понятно, что когда у вас явление неустойчиво малые модификации кода могут приводить к тому, что результаты будут меняться — тут ничего страшного нет, ещё раз повторюсь, мы исследуем явление, которое само по себе неустойчиво. Но когда результаты меняются без модификации кода — это просто неприятно и неудобно…
Может, не кода, а исходных данных? Хотя в задаче о свёртке белка входные данные самого белка дискретны, малые модификации не получатся — их надо вводить по ходу процесса свёртки.
Но когда результаты радикально меняются от сверхмалых изменений входных параметров или промежуточных значений — это признак того, что надо менять модель, и тут проблемы процессоров как раз пошли на пользу — явление опознано.
Вообще, тут надо подобные микроошибки как раз вводить в расчётные программы, для детекта неустойчивости.
netch80
30.06.2017 18:17Мнэээ… это, мягко говоря, не то, что Вы описывали раньше. Вы говорили про «непредсказуемое» значение младшего бита. В данной же документации, кроме этих слов, сказано ещё одно:
> The functions are guaranteed to be monotonic, with respect to the input operands, through the domain supported by the instruction.
Всё вместе наводит на несколько иное понимание — результат конкретной функции для конкретного аргумента всегда один и тот же, но все эти результаты вместе — могут показывать систематическое смещение относительно идеального значения.
Кроме того, это всё явно описано для максимальной точности (64 бита мантиссы). Уже для double (53 бита) эти ошибки становятся несущественными, потому что менее 1/2048 от ulp.
Для совсем глубокой раскопки можно было бы поискать, как именно эти систематические ошибки отличаются для разных моделей FPU. Но важно ли это, с учётом предыдущего абзаца?netch80
30.06.2017 19:29Сам себе поправлю (сбили с толку этим «необходима точность 0.5 ulp или лучше») — не «необходима точность», а «получается точность». Необходимая по IEEE754 — идеальная точность, но с округлением.
Непредсказуемым от этого результат в стиле FPU не становится, но нестандартным — вполне может.
vadimr
30.06.2017 19:37На самом деле речь идёт просто о том, что машинный результат операции не обязательно является ближайшим представимым приближением к идеальному результату. Но при этом если идеальный1 > идеальный2, то и машинный1 >= машинный2.
marsianin
30.06.2017 23:06Смещение может быть, а может и не быть, в зависимости от конкретной реализации FPU, и нигде это не описано точно для конкретной модели FPU. Это и называется «непредсказуемо». Другими словами, если в документации нет описания, позволяющего гарантированно точно рассчитать значение младшего бита в регистре после выполнения операции, значение этого бита в результате операции является непредсказуемым. То есть на конкретное поведение нельзя закладываться. Завтра Вы купите новый процессор, и в нём FPU будет реализован по-другому.
marsianin
30.06.2017 23:12Нет, естественно, реальное железо это конечный автомат, в котором полностью непредсказуемого результата не будет — результат будет рассчитан по каким-то правилам. Но покуда эти правила не определены в архитектурной документации, результат называется непредсказуемым, на него нельзя закладываться пользователям, и это даёт RTL-дизайнерам пространство для оптимизации железа.
marsianin
30.06.2017 23:23Чтобы быть более конкретным, в данном случае я исхожу из позиции Design Validation, когда процессорные инструкции оцениваются с точки зрения «для вот таких значений регистров на входе должно получиться строго конкретное значение результата, путём выполнения преобразований, описанных документацией».
netch80
01.07.2017 08:50> Завтра Вы купите новый процессор, и в нём FPU будет реализован по-другому.
С этим безусловно согласен. Но термин «непредсказуемый» мне тут откровенно не нравится, потому что слишком сильно намекает на варианты типа «сразу повторили ту же операцию, а результат другой».
Считаю тогда, что разногласий тут нет.marsianin
01.07.2017 12:43Под определение «дважды подряд выполнили операцию на одних и тех же исходных данных — получили разные результаты» скорее подойдёт термин «невоспроизводимый».
marsianin
28.06.2017 22:43Что касается управления точностью: если особая точность не нужна, можно вообще включить режим denormal flush to zero. Работает в этом режим обычно пошустрее. Ну и в 32-битном ARM единственная возможность использовать векторизацию — это использовать single precision арифметику и denormal flush to zero.
Regis
28.06.2017 17:17А еще не стоит забывать, что на самом деле бинарная арифметика плохо подходит для многих видов вычислений из реальной жизни. Например для финансовых данных. Так как все вычисления должны воплняться по правилам десятичной арифметики, а не двоичной.
Бинарная арифметика хорошо подходит там, где основание системы счисления не имеет значения и не требуется контроль округления младших разрядов. Например почти все виды данных связанных с измерением объектов реального физического мира (цаще всего это научные и инженерные данные). Кстати, все примеры в статье — это как раз такие данные ("Размер комнаты", "Окружность Земли" и т.д.).
iburyl
28.06.2017 17:33+1В частности поэтому в IEEE 754 2008 года добавили десятичную арифметику. Существую даже железки которые поддерживают десятичную арифметику с плавающей точкой на уровне инструкций.
Regis
28.06.2017 18:01Увы, поддержка IEEE 754 decimal32/decimal64 очень слабая и на сколько мне известно, на поддержку десятичной арифметики в новом железе все производители забили (поправьте если это не так). Плюс дополнительно проблема осложнаяется тем, что тут требуется некоторая поддержка со стороны языков программирования. А её нет (за редким исключением).
netch80
28.06.2017 21:09Десятичная плавучка, считаем, работает только у IBM, но сразу на двух их железных платформах — zSeries (S/390) и Power. Зато у них работает сразу с BCD (Chen-Ho) мантиссой.
Есть поддержка в GCC (почему-то только для C), софтово, с двоичной мантиссой.
netch80
28.06.2017 21:23+1Десятичная плавучка сама по себе имеет для финансовых целей только одно объективное существенное преимущество: это возможность контроля за корректностью операций через inexact exception. Если в decfloat32 ровно 7 цифр мантиссы, а мы складываем 99999.99+0.02 и получаем 100000.00 (100000.01 округлилось) — нам про это inexact скажет. В двоичном варианте практически любые операции с долями приведут к жуткому зашумлению inexact?ами даже на вполне законных операциях.
А где нет inexact?а (как .NET Decimal) — нет даже этого преимущества.
Второе преимущество уже не в качестве операций, но в устранении проблем понимания. Когда вопрос «Floating point is broken? 0.1+0.2 != 0.3» задаётся на Stackoverflow каждые пару дней — это показывает реальный барьер: людям сложно понимать двоичную плавучку. И десятичная тут помогает не отпугнуть широкие массы тех, кого нельзя по-честному называть программистами, но без кого на нынешнем безрыбье не обойтись ;( да, я тут чуть снобствую, для ясности объяснения.
Всё остальное у десятичной арифметики хуже. Точность операций хуже, за счёт размера младшего разряда. Реализации сильно сложнее, и аппаратно, и программно.
Если есть возможность делать финансовую арифметику на целых числах (копейки, сотые доли копейки — где как надо) — лучше делать так, несмотря на присутствие десятичной плавучки. (Разумеется, при наличии контроля переполнений. Это другая проблема, местами тяжёлая.)
khim
28.06.2017 17:36+2Так как все вычисления должны воплняться по правилам десятичной арифметики, а не двоичной.
А чисел с фиксированной точкой (то бишь, на самом деле, целых) — не хватает? Просто интересно — что за задача должна быть, чтобы нельзя было всё посчитать, условно говоря, «в копейках» и требовались числа с плавающей десятичной точкой…Regis
28.06.2017 17:57-2Двоичной арифметики с любой точностью не хватает если вам нужны точные (или контролируемо огругляемые) вычисления.
Канонический пример:
if (0.1+0.2==0.3) { // This will NOT be shown printf("Literals are decimal\n"); }
Больше примеров здесь:
http://speleotrove.com/decimal/decifaq1.html
Кстати, там же говорится, что по проводившемуся IBM по анализу данных от порядка 50 крупных организаций только порядка 2% от общего количества данных использовали двоичное представление (и 55% у десятичных).
netch80
28.06.2017 21:32+2Вам оппонент говорит про вычисления с фиксированной точкой (которые обычно делаются в целых числах с подразумеваемым порядком), а Вы возражаете про двоичную плавучку. Это совсем другой случай. Те же 0.1 + 0.2 в варианте в сотых долях просто превратились бы в 10+20 и вычислились бы абсолютно точно и беспроблемно.
А вот (частично повторю соседний комментарий) 99999.99 + 0.02 при вычислении в decfloat32 окажутся 100000 вместо верного, но уже не представимого, 100000.01. И только inexact exception может свистнуть вам о проблеме, но кто на него обращает внимание?nikolayv81
04.07.2017 19:30В таких случаях хорошо бы использовать что-то типа numeric(n,m) используемых в БД.
netch80
04.07.2017 22:33Да, но при жёсткой дисциплине на их использование. Например, сложение-вычитание с разным m недопустимо; при умножении и делении надо уточнять m результата; присвоение с другим m тоже недопустимо без явной конверсии с указанием правила округления… В сумме таких правил накапливается столько, что часто легче явно вести в целых, а масштаб (то самое m) подразумевать контекстом.
mbait
28.06.2017 21:52+6Я заметил, что в последнее время стали всё чаще появляться такие вот статьи в стиле "я вам тут щас всё опясню, пацаны". Видимо, теория "в XXI веке можно нагуглить всё" работает не полностью, потому что поверхностные знания не успевают осесть, отфильтроваться и получить практическое подкрепление.
точность примерно 24 бита
Ну да, где-то +- бит, в зависимости от температуры процессора.
Итак, если вы измеряете размер квартиры, то достаточно float. Но если хотите представить координаты GPS с точностью менее метра, то понадобится double.
Тут я не уверен, но что-то мне подсказывает, что координаты GPS имеют какой-то фиксированные формат. Или автор решил, что как можно сделать, как в той на картинке из CSI?
Если у вас много оперативной памяти, а скорость выполнения и расход аккумулятора не являются проблемой — вы можете прямо сейчас прекратить чтение и использовать double.
Смешались в кучу кони, люди. Быстродействие, размер памяти и расход батареи сильно зависят от модели процессора, архитектуры компюьтера, алгоритма и вообще от множества параметров. На некоторых процессорах double вообще не поддерживается аппаратно.
Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив double на float. Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float и double маленькая, а на ARM Cortex-A9 разница большая.
Снова кони и люди. Ни слова о том, что, например, у Intel в FPU всегда используется 80-bit precision, а результат потом просто округляется до нужного.
Исчерпывающие результаты тестов см. здесь.
Цикл вида
C = А [operator] B
. Супер-исчерпывающе. Давно известные тесты производительности? Ерунда, возьмём цикл и нарисуем умные графики. Видимо, автору намекнули про это, потому что вверху статьи он пишет: "IMPORTANT: Useful feedback revealed that some of these measures are seriously flawed. A major update is on the way."
Причина в том, что точность теряется при сложении больших и маленьких чисел
Если требуется складывать много больших и маленьких чисел, то это большая проблема алгоритма, а не выбора между форматами чисел. Маленькие надо складывать с маленькими, а большие с большими.
Скорее всего, этот код будет работать так же быстро, как и первый, но при этом не будет теряться точность
Можно подобрать числа так, что и этот код будет терять точность. Опят же — надо смотреть алгоримт.
Что в сухом остатке? Сомнительная статья с ошибками и неопределённостями. Читайте лучше классику.
marsianin
28.06.2017 22:34+1В Intel 80-битная арифметика — это legacy 1980 года издания. Если процессор поддерживает наборы инструкций SSE и SSE2 (вообще все процессоры Intel начиная с Pentium 4), single и double precision floating point там считается гораздо быстрее, чем в формате 80bit x87. И расчёты в SSE/SSE2 происходят именно в 32-битном и 64-битном форматах. Почитайте что ли архитектурную документацию на досуге.
mbait
28.06.2017 22:40Я что-то говорил про SSE? SIMD это отдельная тема для разговоров. Выше уже упомянули, что на GPU вообще всё по-другому. В этом-то как раз и проблема статьи — всё в кучу, без привязки к конкретным алгоритмам и устройствам.
UPD: А, я понял. Вас смутил "хорошо подогнанный конвейер с использованием SIMD". Я же хотел сказать, что нельзя в кучу смешивать FPU, SIMD и разные процессорные архитектуры.
marsianin
28.06.2017 22:47+3Насколько я помню, SSE — это не только про SIMD, там есть и скалярные операции. Поэтому, собственно, нормальные компиляторы и не генерируют кода с инструкциями x87, если без этого можно обойтись.
khim
29.06.2017 01:18Поэтому, собственно, нормальные компиляторы и не генерируют кода с инструкциями x87, если без этого можно обойтись.
Только в 64-битном режиме. В x86-64 SSE2 (как минимум) присутствует всегда и используется, в частности, для передачи параметров (неlong double
) в функции. В 32-битных легаси системах по прежнему используется в разы более медленный FPU — но кому они сейчас интересны?
khim
29.06.2017 01:15Я же хотел сказать, что нельзя в кучу смешивать FPU, SIMD и разные процессорные архитектуры.
Разные архитектуры смешиваете вы.
Рассмотрим простейшую программу:
#include <iostream> FLOAT __attribute__((noinline)) incld(FLOAT x) { return x + 1; } int main() { FLOAT x = 0; for (long int i=0;i<1000000000;i++) { x = incld(x); } std::cout << x << std::endl; }
И запустим её:
$ g++ -O3 -DFLOAT="float" test.cc $ ./test 1.67772e+07 0m02.25s real 0m02.19s user 0m00.00s system $ g++ -O3 -DFLOAT=double test.cc -o test $ ./test 1e+09 0m02.22s real 0m02.19s user 0m00.00s system $ g++ -O3 -DFLOAT="long double" test.cc -o test $ ./test 1e+09 0m10.60s real 0m10.41s user 0m00.00s system $ cat /proc/cpuinfo | grep model model : 90 model name : Intel(R) Atom(TM) CPU Z3560 @ 1.00GHz model : 90 model name : Intel(R) Atom(TM) CPU Z3560 @ 1.00GHz model : 90 model name : Intel(R) Atom(TM) CPU Z3560 @ 1.00GHz model : 90 model name : Intel(R) Atom(TM) CPU Z3560 @ 1.00GHz $ gcc --version gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4 Copyright (C) 2013 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Как видим 80-битная арифметика сасем-сасем чут-чут процесс замедляет. В четыре-пять раз всего-навсего. Если для вас это неважно — можете использовать 80-битную арифметику, кто ж вам запретит?
P.S. Для справки: в современных операционках FPU уже давно никто не использует и реализовано оно во многих современных процессорах «на отвяжись». Отсюда такие результаты. Если уж вы хотите кого-то критиковать — то стоило бы самому «изучить матчасть».mbait
29.06.2017 01:50Во-первых, такими циклами производительности никто не меряет. Во-вторых, я действительно не знал, что x87 не используется сейчас, но моя критика почти никак не затрагивает этот факт. Я просто привёл конкретный пример, где выбор между float и double никак не влияет на производительность. Можно привести пример другой полярности, и моя позиция будет ровно такая же.
khim
29.06.2017 03:43Во-первых, такими циклами производительности никто не меряет.
А какими меряет?
Я просто привёл конкретный пример, где выбор между float и double никак не влияет на производительность.
А давайте на ваш комментарий посмотрим, а?
Так что же авторы статьи напутали? Скрыли от читателей «необычайно важный факт», который так же интересен в современном мире, как какая-нибудь PDP-10 с 36-битными и 72-битными числами?Если у вас хорошо подогнанный конвейер с использованием SIMD, то вы сможете удвоить производительность FLOPS, заменив double на float. Если нет, то разница может быть гораздо меньше, но сильно зависит от вашего CPU. На процессоре Intel Haswell разница между float и double маленькая, а на ARM Cortex-A9 разница большая.
Снова кони и люди. Ни слова о том, что, например, у Intel в FPU всегда используется 80-bit precision, а результат потом просто округляется до нужного.
Можно привести пример другой полярности, и моя позиция будет ровно такая же.
Ваша позиция, как я понял, заключается в том, что статьи, позволяющие за полчаса сделать выбор в 90% случаев «никому не нужны», а нужны фундаментальные статьи, которые позволяют сделать такой выбор правильно в 99% случаев, но требуют на изучение месяц.
Но это — не очень конструктивно. Разработчику какой-нибудь MMORPG просто никто не даст изучать месяц фундаментальную литературу — ему нужно принять решение «здесь и сейчас»… ну хорошо — может до завтра есть ещё время подумать…
Refridgerator
29.06.2017 06:21Здесь ускорение достигнуто исключительно за счёт параллелизации. В этом легко убедиться, если посмотреть ассемблерный код. В тех алгоритмах, где автоматическое распараллеливание затруднено или невозможно, такого выигрыша даже и близко не будет. В этом также можно убедиться на более сложных и нелинейных алгоритмах.
netch80
29.06.2017 09:11Параллелизацию можно остановить, если пометить x как volatile (это вообще надо было с самого начала сделать). Но и с этим я вижу 4-кратную разницу (на подручном AMD E1-1200 — так что это не только Intel).
Насколько синтетичность теста влияет — вопрос более серьёзный — тут надо таки взять что-то образцовое (ну, linpack, наверно, overkill, но что-то в эту сторону).khim
29.06.2017 13:36Насколько синтетичность теста влияет — вопрос более серьёзный — тут надо таки взять что-то образцовое (ну, linpack, наверно, overkill, но что-то в эту сторону).
Как раз изучать явление лучше на простых, синтетических тестах. Вот оценивать насколько ваше ускорение какой-нибудь мелочи влияет на реальную программу — вот тут да, linpack, SPEC CPU и прочие.
khim
29.06.2017 13:34Здесь ускорение достигнуто исключительно за счёт параллелизации.
Вы это сейчас серьёзно?
В этом легко убедиться, если посмотреть ассемблерный код.
Ну если бы там был векторизованный цикл, то да. Но у вас тут, извините, Аристотелева муха получилась. Вы реально это сделать не пробовали? Попробуйте! Hint:__attribute__((noinline))
там не зря стоит, ох не зря.
В тех алгоритмах, где автоматическое распараллеливание затруднено или невозможно, такого выигрыша даже и близко не будет.
Как раз будет. Может и больше будет, та как тут на каждое вычисление вызов функции всунут.
netch80
29.06.2017 09:37+3> Разные архитектуры смешиваете вы.
Ну я его тоже понял так, что SIMD это включая GPU.
> Как видим 80-битная арифметика сасем-сасем чут-чут процесс замедляет. В четыре-пять раз всего-навсего.
В вашем тесте основная проблема в конвенции вызова. Компиляторы (gcc, clang) отказываются инлайнить эту incld(). single, double идут только через xmm регистры, а long double — передачи fp в обе стороны через стек. Это и даёт основное замедление. Ваше явное noinline тут резко нечестно — портит результаты именно на вызовах.
Вот FreeBSD 10.3, процессор Intel i3-4170 (Haswell), компилятор clang 4.2; noinline я явно убрал: во всех 9 комбинациях ('float', 'double', 'long double') * ('-m32 -mfpmath=387', '-m32 -msse -msse2 -mfpmath=sse', '-m64') — время одинаково с точностью до лёгких плаваний — 0.82 секунды. Это означает, что в плотных вычислениях разницы между FPU и SSE разницы нет.
Что именно тормозит процессор тут в случае передачи через стек (при вашем noinline) — надо копать глубже. Но в варианте с SSE в 32 битах, тест с long double быстрее в 2 раза чем с double (!) — видимо, настолько дорогие у него движения типа «записать из FPU, прочитать для SSE». Что показывает ещё одну сторону некорректности теста.
Для полезного теста таки надо брать реальную задачу (какие-нибудь СЛАУ или Рунге-Кутты) и сравнивать на ней. Вся эта синтетика не годится ни к чёрту. Вернусь в город — попробую.Refridgerator
29.06.2017 11:10Я в своё время делал кучу таких сравнений непосредственно на ассемблере, и результат тот же — идентичны. Команды SSE (неупакованные) могут давать слегка лучший результат, потому что:
1) иногда параллелятся в конвейерах (команды FPU не параллелится из-за стековой архитектуры),
2) чуть более эффективный код из-за возможности произвольного доступа к регистрам.
Также где-то в документации Intel встречалось упоминание, что и SSE, и FPU используют один и тоже мат.процессор (архитектурно).khim
29.06.2017 13:48Я в своё время делал кучу таких сравнений непосредственно на ассемблере, и результат тот же — идентичны.
«В своё время» — это как раз ключевое. «В своё время» программы были 32-битными и использовали FPU и в хвост и в гриву. Потому скорость его работы была такая же (и иногда и быстрее), чем у SSE.
А вот на Atom'ах и Bobcat'ах — да, FPU уже начали резать. Интересно узнать что там на Ryzen'е — не удивлюсь, что у него FPU уже тоже работает в режиме «заглушки».
Также где-то в документации Intel встречалось упоминание, что и SSE, и FPU используют один и тоже мат.процессор (архитектурно).
В каком-нибудь Pentium4 — возможно, в современных процессорах — нет. Именно вследствии векторизации. AVX требует кучи ALU — и их глупо делать 80-битными, если только один из восьми будет использоваться в таком режиме. Потому на процессорах с поддержкой AVX FPU — это уже отдельный модуль, а дальше возникает вопрос: а нафига ему быть таким большим и сложным, если он, по факту, никем и ни для чего не используется?netch80
30.06.2017 07:31> А вот на Atom'ах и Bobcat'ах — да, FPU уже начали резать.
Если это «уже» относится к тому, что они они планшетные — ok, соглашусь (таких зверей под рукой удобно нет). Если к развитию архитектуры чего-то уровня хотя бы настольника — не пойдёт, в тех объёмах FPU уже ничего существенно не значит.
> Интересно узнать что там на Ryzen'е — не удивлюсь, что у него FPU уже тоже работает в режиме «заглушки».
Скоро у нас, похоже, такие будут, проверю. Но — заранее полагаю, что в нём не будут делать такую глупость. AMD всегда заботилась об FPU лучше, чем Intel.
marsianin
29.06.2017 21:42Не может публично доступная документация Intel говорить о том, что SSE и x87 реализованы на одном и том же исполнительном устройстве. Это детали реализации, которые не раскрывает ни один разработчик CPU. Архитектурная документация описывает только то, каким будет результат вычислений, а не то, как он будет достигнут в железе.
netch80
30.06.2017 07:22Откройте документ «Intel® 64 and IA-32 Architectures Optimization Reference Manual» и убедитесь, что для конкретных семейств процессоров расписано: количество исполнительных портов, какие команды в них поступают в каком случае, какие задержки (в тактах) на вход конкретной команды, как они увеличиваются при специфическом сочетании с предыдущими командами.
Например, для SandyBridge там сказано, что все умножения (кроме чисто целочисленных) и деления в SSE поступают в порт 0, а сложения плавучих и целочисленные умножения — в порт 1 (таблица 2-14).
Видимо, Intel имеет несколько отличное от Вашего мнение, что можно публиковать, они не боятся давать такие подробности ;)marsianin
30.06.2017 13:11Согласен, Intel публикует некоторые микроархитектурные данные. Только приведённая Вами таблица ничего не говорит об исполнительных устройствах. Она говорит, на какие порты scheduler может отправить микрооперацию конкретной группы. То есть, если x87 и AVX FP обрабатываются портом 0, это не значит, что за это отвечает одна и та же логика.
И если посмотреть на что-нибудь свежее, типа Skylake (схема 2-1 приведённого Вами документа), то там вообще не говорится про x87 и MMX. Что может говорить о том, что обработка этих инструкций вытащена из общего пайплайна и лежит где-то сбоку. Это, как вы понимаете, не способствует производительности этих инструкций.netch80
30.06.2017 16:09> То есть, если x87 и AVX FP обрабатываются портом 0, это не значит, что за это отвечает одна и та же логика.
В общем случае — да. Но с учётом обычной прямой логики (насколько она применима к Intel) и сложности такого блока — это уже ближе к конспирологии.
Не буду дальше вглубляться — у нас ни у кого точных данных нет — закроем на этом.
> И если посмотреть на что-нибудь свежее, типа Skylake (схема 2-1 приведённого Вами документа), то там вообще не говорится про x87 и MMX.
Skylake у меня есть, мерил — в соседнем комментарии. Максимум найденной разницы — около 1.4 раза. Могли усложнить их выборку — факт. Но это ещё даже не разы. Посмотрим на следующие версии…
marsianin
30.06.2017 13:30Да, и если уж речь зашла про Optimization reference manual, там тоже написано, что x87 может работать медленнее:
Assembly/Compiler Coding Rule 63. (M impact, M generality) Use Streaming SIMD Extensions 2 or Streaming SIMD Extensions unless you need an x87 feature. Most SSE2 arithmetic operations have shorter latency then their X87 counterpart and they eliminate the overhead associated with the management of the X87 register stack.
netch80
30.06.2017 16:14Стек — да, безусловно (какой безумец его ввёл в x87?)
Latency — да, там в таблицах видно, что обычно на такт-два больше.
Ещё они могли тут иметь в виду проблемы с денормализованными. SSE тормозит на заметно меньшем количестве случаев с денормализованными, чем FPU; вообще же это дикий позор, что за 30+ лет с KCS реализации это не исправили.
Но в среднем это всё-таки даже не разы.marsianin
30.06.2017 16:33На SSE есть ещё способ поднять performance — отказаться от обработки denormal, включив FTZ и DAZ, что заставит все denormal числа интерпретировать как нули. Как я понимаю, в x87 такой возможности нет.
netch80
30.06.2017 17:46Вот просто потрясающий пример, как можно из одних и тех же данных делать противоположные выводы. ;)
Денормализованные — это не ужас, и не повод тратить тысячи тактов на ерунду. Алгоритмы для этого отработаны уже годами, максимум потерь в аппаратной реализации — два такта на результат. И если у SSE, сделанного через 20 лет после FPU, есть проблема с денормализованными — это значит, что внутри там взята всё та же тупая реализация времён судорожного клепания K-C-S.
А то, что Вы видите флаг справляться с ними в случае SSE, и не видите в случае FPU — это уже фирменный стиль Intel, который просто отказался помочь пользователям FPU, с мотивацией типа «нефиг, пусть быстрее сбегают на SSE».marsianin
30.06.2017 22:28В том и вопрос: производитель CPU совершенно открыто говорит: x87 это legacy. Не нужно его использовать без крайней необходимости.
И да, я нигде не говорил о том, что x87 в разы медленнее, не надо мне приписывать чужих слов. Я говорил, что он просто медленнее, вы и сами это признали.netch80
01.07.2017 08:42> И да, я нигде не говорил о том, что x87 в разы медленнее
OK, у Вас было «весьма медленнее». Это эмоциональная оценка, но по-моему таки «весьма» это «не менее чем в разы» (иначе это несущественно по сравнению с прочими обстоятельствами).
> Я говорил, что он просто медленнее, вы и сами это признали.
Более точно — что уже начался период, что он хоть и немного, но медленнее.
Далее khim@ настаивает, что это будет нарастать, а я неизбежно соглашаюсь ;(marsianin
01.07.2017 09:42Ну, не знаю, для меня 1.4 раза — это уже «весьма», особенно когда я наблюдаю, как народ борется за лишние 5% перфоманса.
nikolayv81
04.07.2017 23:33|| какой безумец его ввёл в x87?
x87 был отдельным сопроцессором ( физически отдельная микросхема ) без стека там, вероятно, сложно было…netch80
05.07.2017 09:11Control & status передаются в FPU и обратно без всякого стека. Значит, могли, если хотели.
Тут всё-таки идеологические соображения. Но непонятно, какие именно.nikolayv81
06.07.2017 00:11Не стану настаивать, но насколько помню была проблема в т.ч. с количеством ног. 8086 у меня не было но на z80 их уже не хватало, и в т.ч. поэтому было мультиплекирование (И ИМХО существовал 8088, по крайней мере это было бы логичным поводом для его выпуска)
В итоге, т.к. два чипа висели на одном потоке команд нужно было как-то данные сохранять в сопроцессоре для обработке (там же ещё операции долгие и основной поток команд мог выполнятся параллельно на основном процессоре).
После выхода 486 такое поведение стало ненужным но осталось, а потом intel решил постепенно заменять сопроцессор на наборы инструкций для основного ядра mmx и т.п. в порчдке их востребованности и возможностей по реализации.
khim
29.06.2017 13:42Вот FreeBSD 10.3, процессор Intel i3-4170 (Haswell)
Выкидываем. На Haswell'е ещё приличный FPU.
Но в варианте с SSE в 32 битах, тест с long double быстрее в 2 раза чем с double (!) — видимо, настолько дорогие у него движения типа «записать из FPU, прочитать для SSE».
Да, пересылки между FPU и SSE весьма и весьма дороги. Это отдельные модули. Но мой тест был не об этом.
Для полезного теста таки надо брать реальную задачу (какие-нибудь СЛАУ или Рунге-Кутты) и сравнивать на ней.
В реальной задаче как раз «затыки» сложнее увидеть. Хотя для «оценки масштабов бедствия» — да, нужна реальная задача…netch80
30.06.2017 08:33> Выкидываем. На Haswell'е ещё приличный FPU.
По сравнению с чем?
Если с супермелкими изделиями вроде Atom — ok, может быть (приму пока сам не померю).
Если с более новыми поколениями — «меня терзают смутные сомнения» (tm)
Вот есть лаптоп с «Intel® Pentium® CPU 4405U @ 2.10GHz» (Skylake, но урезанный до мобильной версии, SSE весь есть, AVX отсутсвует).
Решение СЛАУ по Гауссу, прямолинейное; матрица 1000*1000 случайных чисел; Ubuntu 16.04; gcc5; -O3.
32 бита, FPU, double: 3.6 сек.
32 бита, FPU, long double: 5.5 сек (явно, затраты на cache misses)
32 бита, SSE, double: 3.2 сек. (код показывает частичную векторизацию)
64 бита, SSE, double: 2.3 сек.
64 бита, FPU, long double: 4.6 сек.
Как-то всё равно не укладывается в Вашу концепцию «FPU стал в разы медленнее», максимум подтверждаемого именно для отношения SSE/FPU это где-то 1.4 раза, при тщательном исследовании наверняка будет ещё меньше.
И я уверен, что для всех уровней процессоров от хорошего лаптопа и выше это сохранится ещё долго.
netch80
29.06.2017 09:54> Тут я не уверен, но что-то мне подсказывает, что координаты GPS имеют какой-то фиксированные формат. Или автор решил, что как можно сделать, как в той на картинке из CSI?
Я уверен, имелись в виду координаты в виде «50.44467/35.21212», как на maps-сайтах.khim
29.06.2017 13:49Там всё ещё веселее. GPS — это ж не машинка из которой координаты вылезают. Из железки только время до спутника получается, остальное — нужно считать. Триангуляция, поправки разные, вот это вот всё.
Refridgerator
29.06.2017 11:20За пределы точности double выйти намного проще, чем кажется. Например, аппроксимацией методом наименьших квадратов достаточно большого количества точек (несколько тысяч). При использовании трансцендентных функций погрешности растут ещё быстрее, и с ними можно столкнуться и без всяких накопительных вычислений — просто при вычислении сложной формулы.
khim
29.06.2017 13:54Вот тут — вы правы, конечно. Именно за счёт __float128 POWER'ы до сих пор из Top500 не пропали.
Но такой большой и сложный тип нужно применять осмысленно, а не из соображений «на всякий случай, пусть будет».Refridgerator
29.06.2017 17:37Я освоил технологию double-double и живу счастливо. И кстати, при её реализации на ассемблере 80-битный real оказался весьма полезным)
vadimr
29.06.2017 21:28-1Забудьте про существование типа float и не парьте себе мозг.
marsianin
29.06.2017 22:11Эммм…
А что вы предлагаете использовать вместо float?vadimr
29.06.2017 22:11double
netch80
30.06.2017 16:15+1Почему не сразу quad?
vadimr
30.06.2017 16:2017 цифр достаточно для большинства применений. В двойной точности работает большинство численных библиотек. С другой стороны, 64 бита – удобный размер для выравнивания в памяти.
Четырёхбайтовые вещественные числа – реликт времён, когда память была ограничена 64 килобайтами.marsianin
30.06.2017 16:29Что ж тогда ARM вообще half precision вычисления вводит в ARMv8.1? Они-то вообще на 16-битных числах.
vadimr
30.06.2017 16:33Встроенные системы не имею в виду. Статья о научных вычислениях, как следует из её первых слов.
marsianin
30.06.2017 16:35+1Ага, 64-битная архитектура ARMv8.1 с аппаратной виртуализацией — это очень встроенные решения. Осталось только понять, куда они встроены (-:
vadimr
30.06.2017 16:36В мобильные телефоны?
В научных расчётах half precision точно не имеет никакого применения.marsianin
30.06.2017 16:48+2Пардон, ошибся, не ARMv8.1, а ARMv8.2.
Только судя по архитектурной документации, там уже не мобильные фичи, а вполне себе серверные, как то RAS, виртуализация и прочие.
Я, конечно, не специалист в вычислениях, но в интернетах пишут, что half precision арифметика вполне неплохо показывает себя в задачах, связанных с deep learning. Так что, может быть не стоит считать, что нужны только double precision?vadimr
30.06.2017 16:58Давно известны строгие математические оценки на этот счёт, с которыми автор статьи не дал себе труда познакомиться, прежде чем заявлять о преимуществе float (или, тем более, half) для экономии памяти. Предположим, у нас есть 4 гигабайта памяти, то есть миллиард чисел типа single. Чтобы получить из анализа всех этих данных какой-то практический результат, мы должны произвести, как минимум, миллиард последовательных операций над ними (иначе либо мы используем не все данные, либо цепочка операций не сойдётся к одному результату). При погрешности полбита на операцию, мы получаем в результате потерю точности не менее полумиллиарда цены младшего разряда, или 9 десятичных цифр, что уже превосходит всю разрядную сетку float. Вот и весь лёнинг.
Это не относится к параллельным преобразованиям из матрицы в матрицу. Но матрица из миллиарда чисел не может непосредственно восприниматься человеком и потому не является конечным результатом.khim
30.06.2017 19:50+1Но матрица из миллиарда чисел не может непосредственно восприниматься человеком и потому не является конечным результатом.
Это кто вам такую чушь сказал? DCI 4K, 60FPS, три компоненты изображения — вот вам уже больше полутора миллиардов чисел.
Или вы хотите сказать, что человек неспособен видеоизображение воспринимать?
При погрешности полбита на операцию, мы получаем в результате потерю точности не менее полумиллиарда цены младшего разряда, или 9 десятичных цифр, что уже превосходит всю разрядную сетку float.
Рекомендую вам на досуге почитать про нейронные сети. Узнаете для себя много нового. В частности узнаете как можно с пользой использовать миллиард чисел так, чтобы во время практического использования алгоритма между собой сранивался десяток или около того.
Чтобы получить из анализа всех этих данных какой-то практический результат, мы должны произвести, как минимум, миллиард последовательных операций над ними
Зачем? Подумайте о компьютере, работающем на частоте 500 герц и порождающем ответ за 1/10 секунды, опираясь на матрицу из миллиардов чисел! Довольно очевидно, что этот компьютер не производит миллиардов последовательных вычислений — что не мешает ему быть весьма и весьма полезным в разных случаях… Или вы не верите в существование такого?
Посмотрите в зеркало!vadimr
30.06.2017 20:09Видеоизображение состоит из целых чисел, точность которых может не уменьшаться при операциях с ними. То же самое с программной памятью компьютера, которая, к тому же, используется далеко не полностью каждую секунду, а очень выборочно. То же с нейронной сетью.
khim
30.06.2017 20:25Видеоизображение состоит из целых чисел, точность которых может не уменьшаться при операциях с ними.
Почитайте хотя бы википедию, если ни на что другое времени нет: реальные сцены часто имеют динамический диапазон яркости в 1 000 000:1 и выше, при этом и в тенях и в свете глаз способен (из-за световой адаптации к яркости) различить детали
«из-за световой адаптации к яркости» — это в чистом виде плавучка и есть. При этом ни о каких double'ах или даже float'ах и речи не идёт.
То же самое с программной памятью компьютера, которая, к тому же, используется далеко не полностью каждую секунду, а очень выборочно. То же с нейронной сетью.
И, тем не менее, на протяжении столетий только этот компьютер и использовался в научных вычислениях. А теперь вы вдруг говорите, что такого в принципе не может быть.
Hint: наука, как бы, весьма многообразна и при каком-нибудь анализа ДНК могут применяться самые разнообразные алгоритмы, совершенно не сводящиеся к любимым вами манипуляциям над матрицами миллиард на миллиард.vadimr
30.06.2017 20:37Световая адаптация к яркости — это чисто механическое изменение площади зрачка, не имеющее никакого отношения к обработке сцены в мозге. Просто диафрагмирование.
khim
30.06.2017 21:07Наважно как именно это сделано. Важно что для генерации реалистичной картинки вам нужно обрабатывать как очень яркие, так и очень тёмные области одновременно если вы не знаете зарание — какую часть избражения вам придётся показать в сгенерированной в конечном итоге картинке.
vadimr
30.06.2017 21:26Вы сейчас говорите про обработку в DCI 4K, 60FPS или в человеческом мозге?
Кадр DCI 4K – это просто 8 миллионов целых чисел. Как целые числа, они не требуют вещественной арифметики. Динамический диапазон такого кадра равен динамическому диапазону устройства отображения, независимо от того, какие там использованы сцены.
Если речь идёт о мозге, то он не обрабатывает одновременно очень яркие и очень тёмные области. Он сначала смотрит на яркую область с расширенным зрачком, её распознаёт и запоминает результат, а потом смотрит на светлую область с суженным зрачком, её распознаёт и тоже запоминает результат. Потом умозрительно сводит эти два распознанных результата (уже имеющих очень малый информационный объём) вместе.khim
30.06.2017 22:31Кадр DCI 4K – это просто 8 миллионов целых чисел.
Да — но это результат. Чтобы его получить вам вполне может потребоваться обрабатывать совершенно разные вещи, при этом вы не будете знать — какая часть попадёт в конечное изображение.
Кто тут говорил что точность для промежуточных представлений нужна большая, чем для результата? Вот HDR — это то же самое, только с динамическим диапазоном. Если в одном кадре у вас ярко светит солнце, а в следующем — оно чем-то загорожено, то вам потребуется резко «перескочить» в другой динамический диапазон.vadimr
30.06.2017 23:04Промежуточные представления чего? Если это реальная видеосъёмка, то там такая же ограниченная в разрядности КМОП-матрица на входе. А если анимация, то ей незачем иметь солнце реальной огромной яркости.
Безусловно, всё можно сделать с огромным диапазоном, если считать тупо в лоб. Но я-то говорю как раз о том, что это низачем не нужно для потребного человеку скромного результата.khim
30.06.2017 23:19Если это реальная видеосъёмка, то там такая же ограниченная в разрядности КМОП-матрица на входе.
Во-первых может быть не одна. Во-вторых — можно синтезировать из нескольких снимков изображение. В третьих — его можно рассчитать.
А если анимация, то ей незачем иметь солнце реальной огромной яркости.
С чего вы взяли? Многие вполне себе наблюдаемые эффекты без этого нормально не рассчитать.
Но я-то говорю как раз о том, что это низачем не нужно для потребного человеку скромного результата.
В том-то и дело, что нужно. Результаты, полученные с применением HDR заметно оличаются от тех, где HDR не применяется.
Можно, конечно, упираться и говорить что640K,VGA… Atari 2600 — достаточно для щастья, но это уже чистой воды волюнтаризм: да, в игры для Atari 2600 вполне можно играть, но это не значит что FP16 не нужен!
vadimr
30.06.2017 20:16Собственно, компьютер производит именно миллиарды последовательных вычислений, результатом которых является состояние регистров процессора. Но так как это цепочка логических операций, а не вещественных (с точки зрения работы самого компьютера), то она выполняется без погрешности.
khim
30.06.2017 20:27Но так как это цепочка логических операций, а не вещественных (с точки зрения работы самого компьютера), то она выполняется без погрешности.
Так можно дойти до того, чтобы обьявить что никаких чисел с плавающей точкой в природе вообще нет — это ж фикция! Там просто два целых числа, фигли мы вокруг этого какие-то теории разводим?vadimr
30.06.2017 20:33С точки зрения микропроцессора, никаких вещественных чисел нет. Он оперирует целыми битами в регистрах, абсолютно детерминированно. Но когда вы начинаете интерпретировать эти биты, как приближение идеальных математических вещественных чисел, в результате этого приближения и появляются все те проблемы с точностью вычислений, которые обсуждаются.
vadimr
30.06.2017 20:24Что касается человеческого «компьютера». Если вы запомните хотя бы матрицу 10*10 вещественных чисел, то уже можете считать, что у вас хорошая память. Никаких миллиардов чисел там и близко нет. Есть электрохимические потенциалы нейронов, имеющие, впрочем, очень высокую точность по сравнению с точностью результата. Достаточно их чуть-чуть поменять, например, гормонами — и результат работы мозга меняется радикально.
khim
30.06.2017 20:30Есть электрохимические потенциалы нейронов, имеющие, впрочем, очень высокую точность по сравнению с точностью результата.
Ну то есть всё точно так же, как с алгоритмами, которые используют числа с половинной точностью. И которые вы обьявили заочно «никому не нужной фигнёй».vadimr
30.06.2017 20:39Ну если вас устраивает алгоритм, который в зависимости от случайных погрешностей может найти корни уравнения, а может решить пойти по бабам, то в этом смысле низкая точность подходит. Но это не то, что принято называть научными расчётами.
khim
30.06.2017 21:10Но это не то, что принято называть научными расчётами.
Серьёзно? Методы Монте-Карло уже обьявлены лже-наукой? И выделение генов с помощью нейронных сетей — тоже?
Очень узкая у вас получается наука: подавляющее большинство вычислений, имеющих практический «выхлоп» окажется «за бортом».
iburyl
Какой-то субурный набор наблюдений про числа с плавающей точкой.
Самые большие относительные ошибки внезапно лезут не при сложении, а при вычитании близких значений.
3.1415 — 3.1414 -> В результате этой операции в мантиссе младшие 4 знака заполнены нулями, т.е. мусором.
На импортном языке называется catastrophic cancellation.
В общем и среднем по архитектурам, можно ожидать, что векторизованный код должен отличатся float vs. double по производительности в 2x для арифметических операций ±/*. С делилкой уже не так, с корнями, экспонентами и прочими синусами разрыв больше.
В выводах написано — много складываете, то складывайте в даблах. А что делать, если я умножаю много — float нормально?
f1inx
Возьмите сначала логарифм, тогда задача сведется к складыванию :)