Чому поплавці все ще є частиною мови Java, коли замість них рекомендується здвоєння?


84

У кожному місці, куди я дивився, написано, що doubleце floatмайже у всіх сенсах. floatбув застарілий doubleв Java, так чому він досі використовується?

Я багато програмую з Libgdx, і вони змушують вас використовувати float(deltaTime тощо), але мені здається, що doubleпросто просто працювати з точки зору пам’яті та пам’яті.

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

Чи є якась причина, чому люди наполягають на використанні поплавців, хоча це насправді вже не має жодних переваг? Чи просто занадто багато роботи, щоб все це змінити?



58
Як у світі ви зробили висновок, що "плавати дійсно добре лише для чисел з великою кількістю цифр після десяткової крапки" з відповідей на це запитання ?! Кажуть, прямо навпаки !
Звичайний

20
@Eames Зверніть увагу на те, як написано "цифри", а не "цифри". Поплавки гірші, коли потрібна точність або дальність, вони кращі, коли потрібно багато і не дуже точних даних. Ось що кажуть ці відповіді.
Звичайний

29
Чому у нас byteі shortі intколи є long?
іммібіс

15
Набагато придатніше питання - "чому б ви видалили ключове слово та примітивний тип даних з мови з десятиліттям коду, який би просто зламався без жодної причини"?
сара

Відповіді:


169

LibGDX - це фреймворк, який здебільшого використовується для розробки ігор.

У розробці ігор, як правило, доводиться робити в режимі реального часу велику кількість стискань чисел, і будь-яка продуктивність може мати справу. Ось чому розробники ігор зазвичай використовують float, коли точність поплавця є достатньою.

Розмір реєстрів FPU в процесорі - не єдине, що потрібно враховувати в цьому випадку. Насправді більшість суттєвих скорочень чисельності в розробці ігор здійснюється графічним процесором, а графічні процесори, як правило, оптимізовані для поплавців, а не для подвоєнь .

А ще є:

  • Пропускна здатність шини пам’яті (як швидко можна переміщувати дані між оперативною пам’яттю, процесором та графічним процесором)
  • Кеш процесора (що робить попередній менш необхідним)
  • ОЗП
  • VRAM

які всі дорогоцінні ресурси, з яких ви отримуєте вдвічі більше, коли ви використовуєте 32-бітний флоат, а не 64-бітний подвійний.


2
Дякую! Це дійсно допомогло, тому що ви поглибили глибину того, що змінилося використання пам’яті, і чому
Імзе

7
Також для операцій SIMD 32-бітні значення можуть мати подвійну пропускну здатність. Як вказується у відповіді 8bittree , GPU мають ще більшу ефективність покарання з подвійною точністю.
Пол А. Клейтон

5
Багато графічних конвеєрів навіть підтримують 16-бітні напівплавки для підвищення продуктивності там, де достатня точність.
Аді Шавіт

22
@phresnel Усі є. Ви повинні переміщувати позиції, оновлювати дані, а що ні. І це проста частина. Тоді вам доведеться рендерувати (= читати, обертати, масштабувати та перекладати) текстури, відстані, переводити їх у формат екранів ... Є багато чого зробити.
Себб

8
@phresnel як колишній VP-оператор підприємства з розробки ігор, запевняю вас, що майже в кожній грі є багато крихти кількості. Зауважте, що він зазвичай міститься в бібліотеках і на 100% віддалений від інженера, я би сподівався, що вони зрозуміють і поважають, що все це хрускіт відбувається. Чарівний зворотний квадратний корінь, хто-небудь?
corsiKa

57

Поплавці використовують вдвічі більше пам’яті, ніж удвічі.

Вони можуть мати меншу точність, ніж удвічі, але багато додатків не вимагають точності. Вони мають більший діапазон, ніж будь-який формат фіксованої точки аналогічного розміру. Тому вони заповнюють нішу, яка потребує широкого діапазону чисел, але не потребує високої точності і де важливе використання пам'яті. У минулому я, наприклад, використовував їх для великих нейронних мереж.

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


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


1
зауважте, що продуктивність для 64-бітної плаваючої точки зменшується відносно 32-бітної продуктивності через все більш оптимізовані 32-бітні інструкції, а не тому, що фактична 64-бітна продуктивність знижується. це також залежить від фактичного використовуваного еталону; Цікаво, чи пов'язаний 32-розрядний дефіцит продуктивності в цих орієнтирах пов'язаний із проблемами пропускної здатності пам'яті, а також фактичною швидкістю обчислень
sig_seg_v

Якщо ви збираєтесь говорити про продуктивність DP у відеокартах, то обов'язково слід згадати Titan / Titan Black. Обидва модні функції, які дозволяють картці досягти 1/3 продуктивності, ціною одноточної продуктивності.
СГР

@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

Атомні операції

Окрім того, що вже говорили інші, недоліком double(та long) для Java є те, що призначення 64-бітових примітивних типів не гарантується атомним . З специфікації мови Java, Java SE 8 Edition , сторінка 660 (наголос додано):

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

Для цілей моделі пам'яті мови програмування Java одне записування до енергонезалежного longабо doubleзначення трактується як два окремих запису: по одному на кожну 32-бітну половину. Це може призвести до ситуації, коли нитка бачить перші 32 біти 64-бітного значення з одного запису, а другий 32 біт з іншого запису.

Гидота.

Щоб цього уникнути, вам слід оголосити 64-бітну змінну за допомогоюvolatile ключового слова або скористатися якоюсь іншою формою синхронізації навколо призначення.


2
Чи вам не потрібно синхронізувати паралельний доступ до ints та floats, щоб запобігти втраченим оновленням та зробити їх мінливими, щоб запобігти кеш-керуванню? Я помиляюся, думаючи, що єдине, що запобігає атомності int / float - це те, що вони ніколи не можуть містити "змішаних" значень, яких вони не повинні були мати?
Traubenfuchs

3
@Traubenfuchs Це справді те, що там гарантовано. Термін, який я чув, що вживається для нього, - це «сльозотеча», і я думаю, що це дуже добре фіксує ефект. Модель мови програмування Java гарантує, що 32-бітні значення при читанні матимуть значення, яке було їм записано в якийсь момент. Це напрочуд цінна гарантія.
Корт Аммон

Цей пункт про атомність є надзвичайно важливим. Нічого собі, я забув про цей важливий факт. Протиінтуїтивні, як ми можемо схильні вважати примітивів атомарними за своєю природою. Але не атомний в цьому випадку.
Василь Бурк

3

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

Підсумок міркувань щодо ефективності роботи

  • float може бути швидшим на певних процесорах (наприклад, на певних мобільних пристроях).
  • float використовує менше пам'яті, тому у величезних наборах даних це може істотно зменшити загальну необхідну пам'ять (жорсткий диск / оперативну пам’ять) та споживану пропускну здатність.
  • float може призвести до того, що ЦП споживає менше енергії (я не можу знайти посилання, але, якщо це неможливо, принаймні здається правдоподібним) для одноточних обчислень порівняно з обчисленнями подвійної точності.
  • float споживає меншу пропускну здатність, а в деяких додатках це важливо.
  • SIMD-архітектури можуть обробляти стільки ж, скільки разів удвічі більше, ніж зазвичай.
  • float використовує стільки як половину кеш-пам'яті порівняно з подвійною.

Підсумок точності міркувань

  • У багатьох додатках floatдостатньо
  • double все одно має набагато більшу точність

Міркування щодо сумісності

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

Загальні поради

  • Для сучасних настільних процесорів (і, можливо, непогана кількість мобільних процесорів) ви можете припустити, що використання тимчасових doubleзмінних на стеку дає додаткову точність безкоштовно (додаткова точність без штрафних санкцій).
  • Ніколи не використовуйте більше точності, ніж вам потрібно (ви можете не знати, яка точність вам справді потрібна).
  • Іноді вас просто примушує діапазон значень (деякі значення будуть нескінченними, якщо ви використовуєте float, але можуть бути обмежені значення, якщо ви використовуєте double)
  • Використання лише floatабо лише doubleзначно допомагає компілятору виконувати SIMD-ify інструкції.

Дивіться коментарі від PeterCordes нижче для отримання більш детальної інформації.


1
doubleтимчасові файли вільні лише на x86 із FPU x87, а не з SSE2. Автовекторизація циклу з doubleтимчасовими floatданими означає розпакування double, що вимагає додаткової інструкції, і ви обробляєте половину більше елементів на вектор. Без автоматичної векторизації перетворення зазвичай може відбуватися під час завантаження або зберігання, але це означає додаткові вказівки, коли ви змішуєте плавці та подвійні вирази.
Пітер Кордес

1
У сучасних процесорах x86, div та sqrt швидше плавають, ніж подвійні, але інші речі мають однакову швидкість (не рахуючи проблеми ширини вектора 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 бар.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.