Dlaczego zmiennoprzecinkowe są nadal częścią języka Java, kiedy zamiast tego zaleca się dublowanie?

84

W każdym miejscu, w którym spojrzałem, jest napisane, że doublejest lepsze niż floatpod każdym względem. floatzostał przestarzały doublew Javie, więc dlaczego jest nadal używany?

Dużo programuję w Libgdx, a one zmuszają cię do korzystania float(deltaTime itp.), Ale wydaje mi się, że doublełatwiej jest pracować z pamięcią i pamięcią.

Przeczytałem również Kiedy używaszfloat liczby zmiennoprzecinkowej, a kiedy używasz podwójnej , ale jeśli tak naprawdę jest dobry tylko dla liczb z dużą liczbą cyfr po przecinku, to dlaczego nie możemy po prostu użyć jednej z wielu odmian double?

Czy jest jakiś powód, dla którego ludzie upierają się przy użyciu pływaków, mimo że tak naprawdę nie ma to już żadnych zalet? Czy to zbyt dużo pracy, żeby to wszystko zmienić?

Eames
źródło
8
Możliwy duplikat Kiedy używasz pływaka, a kiedy używasz podwójnie
Vincent Savard
58
Jak, do cholery, wywnioskowałeś, że „liczba zmiennoprzecinkowa jest naprawdę dobra tylko dla liczb z dużą liczbą cyfr po przecinku” na podstawie odpowiedzi na to pytanie ?! Mówią wprost przeciwnie !
Ordous 26.04.16
20
@Eames Zwróć uwagę, jak to się mówi „liczby”, a nie „cyfry”. Pływaki są gorsze, gdy potrzebujesz precyzji lub zasięgu, są lepsze, gdy potrzebujesz wielu niezbyt dokładnych danych. Tak mówią te odpowiedzi.
Ordous
29
Dlaczego mamy bytei shorta intkiedy nie long?
immibis
15
O wiele bardziej trafnym pytaniem jest „dlaczego miałbyś usunąć słowo kluczowe i prymitywny typ danych z języka z dziesięcioleciami kodu, który po prostu łamałby się bez powodu”?
sara

Odpowiedzi:

169

LibGDX to framework wykorzystywany głównie do tworzenia gier.

Podczas opracowywania gier zwykle musisz wykonywać wiele zgniatania liczb w czasie rzeczywistym, a każda wydajność, jaką możesz uzyskać, ma znaczenie. Dlatego twórcy gier zwykle używają liczby zmiennoprzecinkowej, gdy precyzja zmiennoprzecinkowa jest wystarczająca.

Rozmiar rejestrów FPU w CPU nie jest jedyną rzeczą, którą należy wziąć pod uwagę w tym przypadku. W rzeczywistości większość poważnych kryzysów w tworzeniu gier odbywa się za pomocą GPU, a procesory graficzne są zwykle zoptymalizowane pod kątem liczby zmiennoprzecinkowej, a nie podwójnej .

A potem jest jeszcze:

  • przepustowość magistrali pamięci (jak szybko można przeszukiwać dane między RAM, CPU i GPU)
  • Pamięć podręczna procesora (co sprawia, że ​​poprzednie jest mniej konieczne)
  • Baran
  • VRAM

które są cennymi zasobami, które otrzymujesz dwa razy więcej, gdy używasz 32-bitowego float zamiast 64-bitowego double.

Philipp
źródło
2
Dziękuję Ci! To naprawdę pomogło, ponieważ szczegółowo zapoznałeś się z tym, co zmieniło się użycie pamięci i dlaczego
Eames
7
Ponadto w przypadku operacji SIMD wartości 32-bitowe mogą mieć dwukrotnie większą przepustowość. Jak wskazuje odpowiedź 8bittree , procesory graficzne mają jeszcze większą karę wydajności z podwójną precyzją.
Paul A. Clayton
5
Wiele potoków graficznych obsługuje nawet 16-bitowe półpływy, aby zwiększyć wydajność tam, gdzie wystarczająca jest precyzja.
Adi Shavit
22
@phresnel Wszystkie są. Musisz przenosić pozycje, aktualizować dane, a co nie. I to jest prosta część. Następnie musisz renderować (= czytać, obracać, skalować i tłumaczyć) tekstury, odległości, doprowadzić do formatu ekranu ... Jest wiele do zrobienia.
Sebb
8
@phresnel jako były wiceprezes ds. rozwoju gier komputerowych, zapewniam cię, że w niemal każdej grze jest mnóstwo zaskakujących liczb. Zauważ, że zwykle jest to zawarte w bibliotekach i w 100% wyabstrahowane od inżyniera. Mam nadzieję, że rozumieją i szanują to, że dzieje się to wszystko. Magiczny odwrotny pierwiastek kwadratowy, ktoś?
corsiKa
57

Pływaki zużywają o połowę mniej pamięci niż podwójne.

Mogą mieć mniejszą precyzję niż podwójne, ale wiele aplikacji nie wymaga precyzji. Mają większy zasięg niż jakikolwiek inny format stałego punktu o podobnej wielkości. Dlatego wypełniają niszę, która wymaga szerokiego zakresu liczb, ale nie wymaga wysokiej precyzji i gdzie ważne jest użycie pamięci. Na przykład używałem ich w dużych systemach sieci neuronowych.

Wychodząc poza Javę, są również szeroko stosowane w grafice 3D, ponieważ wiele procesorów graficznych używa ich jako podstawowego formatu - poza bardzo drogimi urządzeniami NVIDIA Tesla / AMD FirePro, zmiennoprzecinkowe podwójnej precyzji jest bardzo wolne na GPU.

Jules
źródło
8
Mówiąc o sieciach neuronowych, CUDA obsługuje obecnie zmienne zmiennoprzecinkowe o połówkowej precyzji (16-bitowe), jeszcze mniej precyzyjne, ale z jeszcze mniejszymi śladami pamięci, ze względu na zwiększone wykorzystanie akceleratorów do pracy z uczeniem maszynowym.
JAB
A kiedy programujesz układy FPGA, zwykle za każdym razem ręcznie wybierasz liczbę bitów zarówno dla mantysy, jak i wykładnika: v
Sebi
48

Kompatybilność wsteczna

Jest to najważniejszy powód zachowania zachowania w już istniejącym języku / bibliotece / ISA / itp.

Zastanów się, co by się stało, gdyby zabrali spławiki z Javy. Libgdx (i tysiące innych bibliotek i programów) nie działałby. Będzie to wymagało wiele wysiłku, aby zaktualizować wszystko, a być może nawet lata dla wielu projektów (wystarczy spojrzeć na przechodzące wstecz Python 2 na Python 3, przechodzące pod kątem zgodności). I nie wszystko zostanie zaktualizowane, niektóre rzeczy zostaną zerwane na zawsze, ponieważ opiekunowie porzucili je, być może wcześniej, niż by to zrobili, ponieważ wymagałoby to więcej wysiłku, niż chcieliby zaktualizować, lub ponieważ nie jest już możliwe osiągnięcie tego, co ich oprogramowanie powinno było do zrobienia.

Występ

Podwójne wersje 64-bitowe zajmują dwa razy więcej pamięci i są prawie zawsze wolniejsze w przetwarzaniu niż zmiennoprzecinkowe zmiennoprzecinkowe 32-bitowe (bardzo rzadkimi wyjątkami są sytuacje, w których oczekuje się, że 32-bitowa zmiennoprzecinkowa będzie używana tak rzadko lub wcale, że nie podjęto żadnych wysiłków w celu ich zoptymalizowania O ile nie opracowujesz specjalistycznego sprzętu, nie doświadczysz tego w najbliższej przyszłości.)

Libgdx jest szczególnie odpowiednią dla Ciebie biblioteką gier. Gry mają tendencję do większej wrażliwości na wydajność niż większość programów. Karty graficzne do gier (tj. AMD Radeon i NVIDIA Geforce, a nie FirePro i Quadro) mają zazwyczaj bardzo słabą wydajność zmiennoprzecinkową w wersji 64-bitowej. Dzięki uprzejmości Anandtech, oto jak wydajność podwójnej precyzji porównuje się do wydajności pojedynczej precyzji w niektórych z najlepszych dostępnych kart do gier AMD i NVIDIA (na początku 2016 r.)

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

Należy pamiętać, że R9 Fury i GTX 900 są nowsze niż R9 200 i GTX 700, więc względna wydajność 64-bitowego zmiennoprzecinkowego spada. Cofnij się wystarczająco daleko, a znajdziesz GTX 580, który miał stosunek 1/8 jak seria R9 200.

1/32 wydajności jest dość dużą karą do zapłacenia, jeśli masz ścisłe ograniczenie czasowe i nie zyskujesz dużo, używając większego podwójnego.

8bittree
źródło
1
zwróć uwagę, że wydajność 64-bitowej liczby zmiennoprzecinkowej spada w stosunku do wydajności 32-bitowej z powodu coraz bardziej wysoce zoptymalizowanych instrukcji 32-bitowych, a nie dlatego, że rzeczywista wydajność 64-bitowa spada. zależy to również od faktycznego zastosowanego wskaźnika; Zastanawiam się, czy 32-bitowy deficyt wydajności uwidoczniony w tych testach wynika z problemów z przepustowością pamięci, a także z faktyczną prędkością obliczeniową
sig_seg_v 26.04.16
Jeśli chcesz porozmawiać o wydajności DP na kartach graficznych, zdecydowanie powinieneś wspomnieć o Titan / Titan Black. Oba mają modyfikatory, które pozwalają karcie osiągnąć wydajność 1/3, kosztem wydajności pojedynczej precyzji.
SGR
@ sig_seg_v Zdecydowanie są przynajmniej niektóre przypadki, w których wydajność 64-bitowa spada absolutnie, nie tylko względnie. Zobacz te wyniki podwójnie precyzyjnego testu porównawczego Folding @ Home, w którym GTX 780 Ti bije zarówno GTX 1080 (kolejna karta w stosunku 1/32), jak i 980 Ti, a po stronie AMD 7970 (karta o współczynniku 1/4) , podobnie jak R9 290 i R9 290X, wszystkie pokonały serię R9 Fury. Porównaj to z pojedynczą, precyzyjną wersją testu porównawczego , w której wszystkie nowsze karty znacznie przewyższają swoich poprzedników.
8bittree,
36

Operacje atomowe

Oprócz tego, co już powiedzieli inni, wadą specyficzną dla Javy double(i long) jest to, że przypisania do 64-bitowych pierwotnych typów nie są gwarantowane jako atomowe . Ze specyfikacji języka Java, Java SE 8 Edition , strona 660 (wyróżnienie dodane):

17.7 Leczenie bezatomowe doubleilong

Na potrzeby modelu pamięci języka programowania Java pojedynczy zapis do nieulotnej longlub doublewartości jest traktowany jako dwa oddzielne zapisy: po jednym na każdą 32-bitową połowę. Może to spowodować sytuację, w której wątek widzi pierwsze 32 bity 64-bitowej wartości z jednego zapisu, a drugie 32 bity z innego zapisu.

Fuj

Aby tego uniknąć, musisz zadeklarować zmienną 64-bitową za pomocąvolatile słowa kluczowego lub użyć innej formy synchronizacji wokół przypisań.

Kevin J. Chase
źródło
2
Czy nie musisz synchronizować równoczesnego dostępu do int i float, aby zapobiec utracie aktualizacji i uczynić je niestabilnymi, aby zapobiec nadmiernemu buforowaniu? Czy mylę się, sądząc, że jedyną rzeczą, której zapobiega atomowość int / float, jest to, że nigdy nie mogą zawierać „mieszanych” wartości, których nie powinni byli trzymać?
Traubenfuchs
3
@Traubenfuchs To jest rzeczywiście to, co tam jest gwarantowane. Termin, o którym słyszałem, to „łzawienie” i myślę, że całkiem dobrze oddaje efekt. Model języka programowania Java gwarantuje, że 32-bitowe wartości podczas odczytu będą miały wartość zapisaną w pewnym momencie. To zaskakująco cenna gwarancja.
Cort Ammon
Ta kwestia atomowości jest bardzo ważna. Wow, zapomniałem o tym ważnym fakcie. Przeciwnie intuicyjne, ponieważ możemy myśleć o prymitywach jako z natury atomowych. Ale w tym przypadku nie atomowy.
Basil Bourque,
3

Wydaje inne odpowiedzi brakowało jeden ważny punkt: SIMD architekturach może przetworzyć mniej / więcej danych w zależności czy działają one na doublelub floatelemencie (na przykład osiem wartości pływak na raz lub cztery podwójne wartości na raz).

Podsumowanie rozważań dotyczących wydajności

  • float może być szybszy na niektórych procesorach (na przykład na niektórych urządzeniach mobilnych).
  • float zużywa mniej pamięci, więc w dużych zestawach danych może znacznie zmniejszyć całkowitą wymaganą pamięć (dysk twardy / RAM) i zużyte pasmo.
  • float może powodować, że procesor zużywa mniej energii (nie mogę znaleźć odniesienia, ale jeśli nie jest to możliwe, przynajmniej wydaje się prawdopodobne) do obliczeń o pojedynczej precyzji w porównaniu do obliczeń o podwójnej precyzji.
  • float zużywa mniej pasma, a w niektórych aplikacjach ma znaczenie.
  • Architektury SIMD mogą przetwarzać nawet dwukrotnie taką samą ilość danych, ponieważ zwykle.
  • float zużywa aż połowę pamięci podręcznej w porównaniu do podwójnej.

Podsumowanie względów dokładności

  • W wielu aplikacjach floatwystarczy
  • double i tak ma znacznie większą precyzję

Zagadnienia dotyczące zgodności

  • Jeśli dane muszą zostać przesłane do GPU (na przykład w przypadku gry wideo z OpenGL lub innym interfejsem API renderowania), format zmiennoprzecinkowy jest znacznie szybszy niż double(to dlatego, że producenci GPU starają się zwiększyć liczbę rdzeni graficznych, a dlatego starają się zaoszczędzić jak najwięcej obwodów w każdym rdzeniu, więc optymalizacja floatpozwala na tworzenie układów GPU z większą liczbą rdzeni w środku)
  • Stare GPU i niektóre urządzenia mobilne po prostu nie mogą zaakceptować doubleformatu wewnętrznego (do operacji renderowania 3D)

Ogólne wskazówki

  • Na nowoczesnych procesorach stacjonarnych (i prawdopodobnie dużej liczbie procesorów mobilnych) można zasadniczo założyć, że użycie doublezmiennych tymczasowych na stosie daje dodatkową precyzję za darmo (dodatkowa precyzja bez obniżenia wydajności).
  • Nigdy nie używaj większej precyzji niż potrzebujesz (możesz nie wiedzieć, ile precyzji naprawdę potrzebujesz).
  • Czasami jesteś po prostu zmuszony przez zakres wartości (niektóre wartości byłyby nieskończone, jeśli używasz float, ale mogą być ograniczone wartości, jeśli używasz double)
  • Używanie tylko floatlub tylko doublebardzo pomaga kompilatorowi w SIMD-ify instrukcje.

Aby uzyskać więcej informacji, zobacz poniższe komentarze PeterCordes.

Twórca gier
źródło
1
doubletymczasowe jest bezpłatne tylko na x86 z FPU x87, nie z SSE2. Automatyczne wektoryzowanie pętli z doubletymczasowymi oznacza rozpakowanie floatdo double, co wymaga dodatkowej instrukcji, a ty przetwarzasz o połowę mniej elementów na wektor. Bez automatycznej wektoryzacji konwersja zwykle może odbywać się w locie podczas ładowania lub przechowywania, ale oznacza to dodatkowe instrukcje podczas mieszania liczb zmiennoprzecinkowych i podwójnych w wyrażeniach.
Peter Cordes
1
Na współczesnych procesorach x86 div i sqrt są szybsze dla float niż double, ale inne rzeczy mają tę samą prędkość (nie licząc problemu z szerokością wektora SIMD lub oczywiście przepustowości pamięci / śladu pamięci podręcznej).
Peter Cordes
@PeterCordes dzięki za rozszerzenie niektórych punktów. Nie wiedziałem o
różnicach
0

Oprócz innych wymienionych powodów:

Jeśli masz dane pomiarowe, czy to ciśnienia, przepływy, prądy, napięcia, czy cokolwiek innego, często dzieje się tak w przypadku sprzętu wyposażonego w ADC.

ADC zazwyczaj ma 10 lub 12 bitów, 14 lub 16 bitów są rzadsze. Ale trzymajmy się 16-bitowym - jeśli mierzysz w pełnej skali, masz dokładność 1/65535. Oznacza to, że zmiana z 65534/65535 na 65535/65535 to tylko ten krok - 1/65535. To mniej więcej 1,5E-05. Dokładność pływaka wynosi około 1E-07, więc jest o wiele lepsza. Oznacza to, że niczego nie tracisz, używając floatdo przechowywania tych danych.

Jeśli wykonujesz nadmierne obliczenia za pomocą pływaków, wykonujesz nieco gorsze wyniki niż doublespod względem dokładności, ale często nie potrzebujesz tej dokładności, ponieważ często nie przejmujesz się, czy właśnie zmierzyłeś napięcie 2 V lub 2,00002 V. Podobnie , jeśli przekształcisz to napięcie w ciśnienie, nie przejmujesz się, czy masz 3 bary lub 3.00003 bary.

glglgl
źródło