Почему числа с плавающей запятой по-прежнему являются частью языка Java, когда вместо них чаще всего используются двойные числа?

84

В каждом месте, где я смотрел, написано, что doubleоно превосходит floatпочти во всех отношениях. floatбыл сделан устаревшим doubleв Java, так почему он все еще используется?

Я много программирую на Libgdx, и они вынуждают вас использовать float(deltaTime и т. Д.), Но мне кажется, что с ними doubleпроще работать с точки зрения хранения и памяти.

Я также читаю Когда вы используете float и когда вы используете double , но если floatэто действительно хорошо только для чисел с большим количеством цифр после десятичной точки, то почему мы не можем просто использовать один из многих вариантов double?

Есть ли какая-то причина, по которой люди настаивают на использовании поплавков, хотя у них больше нет преимуществ? Не слишком ли много работы, чтобы все это изменить?

Эймс
источник
58
Как в мире вы пришли к выводу, что «число с плавающей запятой действительно хорошо только для чисел с большим количеством цифр после запятой» из ответов на этот вопрос ?! Они говорят прямо противоположное !
Ордос
20
@Eames Обратите внимание, как написано «цифры», а не «цифры». Поплавки хуже, когда вам нужна точность или диапазон, они лучше, когда вам нужно много и много не очень точных данных. Вот что говорят эти ответы.
Ордос
29
Почему у нас byteи shortи intкогда есть long?
user253751
15
Гораздо более подходящий вопрос: «почему вы удалили ключевое слово и примитивный тип данных из языка с десятилетиями кода, который просто ломался без причины»?
Сара

Ответы:

169

LibGDX - это фреймворк, в основном используемый для разработки игр.

При разработке игр вам, как правило, приходится выполнять множество вычислений в реальном времени, и любая производительность, которую вы можете получить, имеет значение. Вот почему разработчики игр обычно используют float, когда точность float достаточно хороша.

Размер регистров FPU в CPU - не единственное, что вам нужно учитывать в этом случае. Фактически, большая часть значительного сокращения чисел в разработке игр выполняется графическим процессором, а графические процессоры обычно оптимизированы для операций с плавающей запятой, а не для удвоения .

И тогда есть также:

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

которые являются всеми ценными ресурсами, которые вы получаете вдвое больше, когда используете 32-битный float вместо 64-битного double.

Philipp
источник
2
Спасибо! Это действительно помогло, потому что вы углубились в то, что изменилось использование памяти и почему
Eames
7
Кроме того, для операций SIMD 32-битные значения могут иметь удвоенную пропускную способность. Как указывает ответ 8bittree , графические процессоры имеют еще большую потерю производительности с двойной точностью.
Пол А. Клейтон,
5
Многие графические конвейеры даже поддерживают 16-битные полуплавающие числа, чтобы повысить производительность там, где достаточно точности.
Ади Шавит
22
@phresnel Все есть. Вы должны перемещать позиции, обновлять данные, а что нет. И это простая часть. Затем вы должны отобразить (= прочитать, повернуть, масштабировать и перевести) текстуры, расстояния, получить их в формате экранов ... Есть много дел.
Себб
8
@phresnel в качестве бывшего вице-президента по операциям на предприятии по разработке игр, я уверяю вас, что почти в каждой игре есть множество проблем. Обратите внимание, что он обычно содержится в библиотеках и на 100% отделен от инженера, я надеюсь, что они понимают и уважают все происходящее. Волшебный обратный квадратный корень, кто-нибудь?
corsiKa
57

Float использует вдвое больше памяти, чем удваивается.

Они могут иметь меньшую точность, чем двойные, но многие приложения не требуют точности. Они имеют больший диапазон, чем любой аналогичный формат с фиксированной точкой. Таким образом, они занимают нишу, которая нуждается в широком диапазоне чисел, но не требует высокой точности и где важно использование памяти. Например, в прошлом я использовал их для больших нейронных сетей.

Выходя за пределы Java, они также широко используются в трехмерной графике, поскольку многие графические процессоры используют их в качестве основного формата - за исключением очень дорогих устройств NVIDIA Tesla / AMD FirePro, с плавающей запятой двойной точности очень медленно на графических процессорах.

Жюль
источник
8
Говоря о нейронных сетях, CUDA в настоящее время поддерживает полуточные (16-битные) переменные с плавающей запятой, даже менее точные, но с еще меньшим объемом памяти, из-за более широкого использования ускорителей для машинного обучения.
JAB
И когда вы программируете FPGA, вы склонны каждый раз вручную выбирать количество бит для мантиссы и экспоненты: v
Sebi
48

Обратная совместимость

Это причина номер один для сохранения поведения на уже существующем языке / библиотека / ISA / и т. Д.

Подумайте, что случится, если они заберут поплавки из Java. Libgdx (и тысячи других библиотек и программ) не будут работать. Потребуется много усилий, чтобы обновить все, возможно, годы для многих проектов (просто посмотрите на переход с обратной совместимости с Python 2 на Python 3). И не все будет обновлено, некоторые вещи будут сломаны навсегда, потому что сопровождающие отказались от них, возможно, раньше, чем они бы сделали, потому что потребовалось бы больше усилий, чем они хотят обновить, или потому что больше не возможно выполнить то, что предполагалось их программным обеспечением сделать.

Представление

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

Libgdx - это библиотека игр. Игры имеют тенденцию быть более чувствительными к производительности, чем большинство программного обеспечения. И игровые видеокарты (то есть AMD Radeon и NVIDIA Geforce, а не FirePro или Quadro), как правило, имеют очень слабую 64-битную производительность с плавающей запятой. Благодаря Anandtech, вот как производительность с двойной точностью сравнивается с производительностью с одинарной точностью на некоторых топовых игровых картах AMD и NVIDIA (по состоянию на начало 2016 года)

AMD
Card    R9 Fury X      R9 Fury       R9 290X    R9 290
FP64    1/16           1/16          1/8        1/8

NVIDIA
Card    GTX Titan X    GTX 980 Ti    GTX 980    GTX 780 Ti
FP64    1/32           1/32          1/32       1/24

Обратите внимание, что серии R9 Fury и GTX 900 являются более новыми, чем серии R9 200 и GTX 700, поэтому относительная производительность для 64-разрядных чисел с плавающей запятой уменьшается. Вернитесь достаточно далеко, и вы найдете GTX 580, который имел соотношение 1/8, как серия R9 200.

1/32 производительности - это довольно большое наказание, если у вас жесткие временные ограничения и вы мало выигрываете, используя больший дубль.

8bittree
источник
1
обратите внимание, что производительность для 64-разрядной плавающей запятой уменьшается по сравнению с 32-разрядной производительностью из-за все более высоко оптимизированных 32-разрядных инструкций, а не потому, что фактическая 64-разрядная производительность уменьшается. это также зависит от фактического используемого теста; Интересно, вызван ли недостаток 32-разрядной производительности, выделенный в этих тестах, проблемами с пропускной способностью памяти, а также фактической скоростью вычислений
sig_seg_v
Если вы собираетесь поговорить о производительности DP в видеокартах, вам определенно стоит упомянуть Titan / Titan Black. Обе функциональные возможности позволяют карте достигать 1/3 производительности за счет производительности с одинарной точностью.
SGR
@sig_seg_v Определенно есть, по крайней мере, некоторые случаи, когда 64-битная производительность снижается абсолютно, а не только относительно. Посмотрите эти результаты для теста точности Folding @ Home с двойной точностью, где GTX 780 Ti превосходит GTX 1080 (еще одна карта с соотношением 1/32) и 980 Ti, а со стороны AMD - 7970 (карта с соотношением 1/4) , а также R9 290 и R9 290X побеждают в серии R9 Fury. Сравните это с версией эталонного теста с одинарной точностью , где все новые карты превосходят своих предшественников.
8bittree
36

Атомные операции

В дополнение к тому, что уже сказали другие, специфический для Java недостаток doublelong) состоит в том, что присвоение 64-битным примитивным типам не гарантируется как атомарное . Из спецификации языка Java, Java SE 8 Edition , стр. 660 (выделение добавлено):

17.7 Неатомарная обработка doubleиlong

Для целей модели памяти языка программирования Java одна запись в энергонезависимое значение longили doubleзначение рассматривается как две отдельные записи: по одной на каждую 32-битную половину. Это может привести к ситуации, когда поток видит первые 32 бита 64-битного значения из одной записи и вторые 32 бита из другой записи.

Тьфу.

Чтобы избежать этого, вы должны объявить 64-битную переменную сvolatile ключевым словом или использовать другую форму синхронизации вокруг назначений.

Кевин Дж. Чейз
источник
2
Разве вам не нужно синхронизировать одновременный доступ к ints и float, чтобы предотвратить потерю обновлений и сделать их нестабильными для предотвращения чрезмерного кэширования? Я ошибаюсь, думая, что единственное, что предотвращает атомарность int / float, это то, что они никогда не могут содержать «смешанные» значения, которые они не должны были содержать?
Traubenfuchs
3
@Traubenfuchs Это действительно то, что там гарантировано. Термин, который я слышал, использовал его как «разрыв», и я думаю, что он довольно хорошо отражает эффект. Модель языка программирования Java гарантирует, что 32-битные значения при чтении будут иметь значение, которое им было записано в какой-то момент. Это удивительно ценная гарантия.
Корт Аммон
Этот пункт об атомарности очень важен. Вау, я забыла об этом важном факте. Как ни странно, мы можем думать о примитивах как об атомных по своей природе. Но не атомарный в этом случае.
Василий Бурк
3

Кажется, что в других ответах пропущен один важный момент: архитектуры SIMD могут обрабатывать меньше / больше данных в зависимости от того, работают ли они doubleили floatструктурируются (например, восемь значений с плавающей запятой за раз или четыре двойных значения за раз).

Резюме соображений производительности

  • float может быть быстрее на определенных процессорах (например, на определенных мобильных устройствах).
  • float использует меньше памяти, поэтому в больших наборах данных это может существенно уменьшить общую требуемую память (жесткий диск / RAM) и потребляемую пропускную способность.
  • float может заставить процессор потреблять меньше энергии (я не могу найти ссылку, но, если это невозможно, по крайней мере, кажется правдоподобным) для вычислений с одинарной точностью по сравнению с вычислениями с двойной точностью.
  • float потребляет меньше пропускной способности, а в некоторых приложениях это имеет значение.
  • SIMD-архитектуры могут обрабатывать вдвое больше одинакового объема данных, чем обычно.
  • float использует до половины кэш-памяти по сравнению с двойной.

Резюме соображений точности

  • Во многих приложениях floatдостаточно
  • double в любом случае имеет гораздо больше точности

Вопросы совместимости

  • Если ваши данные должны быть отправлены в графический процессор (например, для видеоигры, использующей OpenGL или любой другой API рендеринга), формат с плавающей запятой значительно быстрее, чем double(это потому, что производители графических процессоров пытаются увеличить количество графических ядер, и таким образом, они пытаются сохранить как можно больше схем в каждом ядре, поэтому оптимизация floatпозволяет создавать графические процессоры с большим количеством ядер внутри)
  • Старые графические процессоры и некоторые мобильные устройства просто не могут принять doubleв качестве внутреннего формата (для операций 3D-рендеринга)

Общие советы

  • В современных процессорах для настольных ПК (и, вероятно, в большом количестве мобильных процессоров) вы можете предположить, что использование временных doubleпеременных в стеке дает дополнительную точность бесплатно (дополнительную точность без потери производительности).
  • Никогда не используйте больше точности, чем вам нужно (вы можете не знать, сколько точности вам действительно нужно).
  • Иногда вы просто вынуждены диапазон значений (некоторые значения будут бесконечными, если вы используете float, но могут быть ограниченные значения, если вы используете double)
  • Использование только floatили только doubleочень помогает компилятору SIMD-инструкции.

Смотрите комментарии ниже от PeterCordes для получения дополнительной информации.

Разработчик игр
источник
1
doubleВременные программы доступны только на x86 с x87 FPU, но не с SSE2. Автоматическая векторизация цикла с doubleвременными значениями означает распаковку floatв double, что требует дополнительной инструкции, и вы обрабатываете вдвое меньше элементов на вектор. Без автоматической векторизации преобразование может обычно происходить на лету во время загрузки или хранения, но это означает дополнительные инструкции, когда вы смешиваете числа с плавающей запятой и удваивает в выражениях.
Питер Кордес
1
На современных процессорах x86 div и sqrt быстрее для float, чем double, но другие вещи имеют одинаковую скорость (не считая проблему ширины вектора SIMD или, конечно, пропускной способности памяти / объема кэша).
Питер Кордес
@PeterCordes спасибо за расширение некоторых пунктов. Я не знал о несоответствии div и sqrt
GameDeveloper
0

Помимо других причин, которые были упомянуты:

Если у вас есть данные измерений, будь то давления, потоки, токи, напряжения или что-то еще, это часто делается с помощью оборудования, имеющего АЦП.

АЦП обычно имеет 10 или 12 бит, 14 или 16 бит реже. Но давайте остановимся на 16-битной единице - при измерении по полной шкале точность составляет 1/65535. Это означает, что переход от 65534/65535 к 65535/65535 - именно этот шаг - 1/65535. Это примерно 1.5E-05. Точность поплавка составляет около 1E-07, так что намного лучше. Это означает, что вы ничего не потеряете, используя floatдля хранения этих данных.

Если вы выполняете чрезмерные вычисления с плавающими значениями, вы выполняете немного хуже, чем с doublesточки зрения точности, но часто вам не нужна эта точность, поскольку вам часто все равно, измеряете ли вы напряжение 2 В или 2,00002 В. Аналогично Если вы преобразуете это напряжение в давление, вам все равно, если у вас 3 бара или 3,00003 бара.

glglgl
источник