Wątek przeniesiony 2020-04-16 11:11 z Edukacja przez cerrato.

Czy C++ desktop upadnie?

0

Wszystko naklepałem w python

Qt, OpenCV i Tensorflow (czy co tam wykorzystywałeś pod spodem) są napisane w C/C++, zatem w Pythonie najwyżej spiąłeś wysokopoziomową logikę, a nie "klepnąłeś wszystko".

To trochę jak gdyby stwierdzić, że w Batchu można tworzyć gry komputerowe, bo https://gynvael.coldwind.pl/?id=127 :-p

1
bilborrd napisał(a):

Niezłą dyskusję wywołałem :) widzę, że przewija się czasem twierdzenie, że są języki szybsze niż C/C++. Nie do końca rozumiem to stwierdzenie. Są to języki kompilowane do kodu maszynowego nie może być szybszych języków. Oczywiście, dużo zależy od umiejętności programisty ale teoretycznie, skoro nie mamy żadnej warstwy abstrakcji, mamy maksymalne możliwości optymalizacji i inne języki mogą być tylko równie szybkie. Może się mylę?

Chyba, że chodzi wam o to, że pewne domyślne rozwiązania jak np. polimorfizm, są lepiej zrealizowane w innych.

Np w algorytmach intensywnie alokujących (duża ilość "sztuk") Java może być szybsza *)
jest w Apache taki serwer Qpid, który ma dwie implementacje w Javie i C++. Wiem że przy C++ wisiała tak notatka "przepraszamy, ale wersja Javowa jest szybsza".

*) potoczne słowo szybkość wymaga profesjonalnych rozróżnień: throughput, maks czas reakcji, mediana, percentyl i wiele innych

hauleth napisał(a):

Czasami ze względu na konstrukcję języków może się okazać, że niektóre rzeczy kompilator może założyć, że są spełnione.

Ważne stwierdzenie.

na uK miałem przygodę (Atmega 8 to dość małe gówienko).
Po podniesieniu wersji IDE (do AS 7 już fanie robiącej C++) przeportowałem C do C++. Kompilator wyoptymalizował pola prywatne, o których miał wiedzę, ze nie są używane.
Właśnie na tej zasadzie: więcej wiedział.

1
bilborrd napisał(a):

Niezłą dyskusję wywołałem :) widzę, że przewija się czasem twierdzenie, że są języki szybsze niż C/C++. Nie do końca rozumiem to stwierdzenie. Są to języki kompilowane do kodu maszynowego nie może być szybszych języków. Oczywiście, dużo zależy od umiejętności programisty ale teoretycznie, skoro nie mamy żadnej warstwy abstrakcji, mamy maksymalne możliwości optymalizacji i inne języki mogą być tylko równie szybkie. Może się mylę?

Mnósto języków jest kompilowane do kodu maszynowego, ale taka java dopiero w trakcie działania programu, przez co kompilator np. w jvm ma dużo więcej wiedzy (statystyki) o tym jak program działa. Dodatkowo dość jasno określone zasady działania maszyny powodują, że nie ma praktycznie UB - kompilator może sobie pozwolić na dużo większe żonglowanie kodem niż w C/C++. Klasyka: już 20 lat temu jvm potrafił inlinować metody wirtualne (czyli absurd z punktu widzenia c++). Czy np. semafory (synchronized) - jeśli tylko jeden wątek coś lockuje to jvm wycina kod lock/free.

To, że przez te 20 lat jvm totalnie nie zdeklasował C/C++ w szybkości programów nawet mnie zadziwia, ale są konkretne powody (np. never ending story projektu valhalla). Taki jvm ma ciągle duży potencjał na optymalizacje, gdzie C raczej daleko nie zajdzie (C++ może ma większe szanse - jeśli nałożyć pewne ograniczenia na kod).
Natomiast jvmy i inne vm pewnie będą zawsze obsysały w zużyciu pamięci - duża część z optymalizacji wymaga przechowywania dodatkowych danych i ta ilość raczej rośnie ciągle.

4

To jest ładna teoria. Szkoda tylko, że moje 10+ lat doświadczenia z optymalizacja kodu w Javie tego nie potwierdza. JVM ma nieco więcej informacji o wykonaniu kodu ale ta informacja jest średnio użyteczna i w praktyce zysk jest mizerny - zwykle kilka %.
Przy kompilacji natywnej też można zresztą zrobić PGO. Zwykle daje od -5% do +10% wydajności, dlatego mało kto się w to bawi.

Podejrzewam, że wynika to głównie z postępu w rozwoju procesorów. Obecnie CPU sam w sobie jest taką małą maszyną wirtualną i sam robi mnóstwo "optymalizacji". Np. kompilatory nie muszą dawać hintów do przewidywania skoków, bo CPU i tak zrobi po swojemu.

Natomiast z drugiej strony JVM ma bardzo mało czasu na optymalizację a sam język narzuca pewne rozwiązania, które trudno optymalizować - np. konieczność alokacji na stercie niemal wszystkiego, konieczność obsługi metod wirtualnych, locki na każdym obiekcie, GC itp.

1

Sztuczki z JVMa jak na razie przede wszystkim pomagają zbić narzut wysokopoziomowego kodu oraz koszt obsługi leniwego i dynamicznego ładowania kodu. Dynamiczne ładowanie kodu prowadzi do https://en.wikipedia.org/wiki/Open-world_assumption a więc trzeba się zabezpieczyć przed zmianą sytuacji - np klasy są ładowane w trakcie wykonywania kodu, a więc nie można na pałę założyć, że inline'ujemy wszystko bez dodatkowej ifologii na wypadek załadowania nowego kodu. Kompilacja AOT odbywa się pod https://en.wikipedia.org/wiki/Closed-world_assumption a więc łatwiej jest optymalizować - wiadomo dokładnie co w danym miejscu może się stać i nie trzeba się dodatkowo zabezpieczać.

Sporym hamulcem dla rozwoju JVMa jest to, że ten standardowy wciąż jest napisany w C++. Mamy więc sytuację kompilatorów AOT do C++ napisanych w C++ vs kompilatora JIT do Javy napisanego w C++. Wysokowydajny JIT jest raczej trudniejszy do osiągnięcia niż wysokowydajny AOT, więc trzymanie się C++a raczej nie przyspieszy postępu. Z drugiej strony w Javie implementuje się pomysły szybciej niż w C++ie. Dopiero GraalVM czyli kompilator JIT + AOT do Javy napisany w Javie zmienia sytuację. Autorzy Javy przerzucili się z zaawansowanymi eksperymentami na GraalVMa i mamy już jakieś owoce tych eksperymentów:

Kod wygenerowany przez JITa z JVMa wcale nie musi być dokładnie tak szybki jak ten wygenerowany przez kompilatory C++. Wystarczy, że będzie blisko (np spowolnienie nie większe niż 20%) i już kurczowe trzymanie się C++a stracie sens, bo te 20% wydajności będzie można odzyskać z nawiązką inwestując w wielowątkowe przetwarzanie danych.

1

W praktyce cały czas nie jest 20% a 2x-3x i to jak się jest bardzo ostrożnym. Jak się nie uważa, i pisze w stylu OOP albo FP a nie data-oriented, to spokojnie można stracić 90% wydajności i nawet trudno zauważyć kiedy i gdzie zniknęło.

Druga sprawa to przewidywalność wydajności - w aplikacjach desktopowych ważne jest szybkie działanie od początku, a nie stopniowe rozpędzanie się. Ba! To jest ważne również w aplikacjach serwerowych. Jak klient ma zrestartować klaster ze 100 serwerów i każdy startuje 30-50 sekund, ale rozpędza się kolejne kilka minut, to trudno się dziwić, że taki klient będzie się przed tym bronić jak tylko może.

Trzecia sprawa to pamięć. Zdarza się, że JVM nawet faktycznie zbliza się do optymalnej wydajności, ale kosztem zużycia o rząd wielkości więcej RAMu. A czasem dwa rzędy wielkości. Zwłaszcza jeśli dołożymy konieczność minimalizacji pauz z GC. GC lubi dodatkowa przestrzeń.

Co do wielowątkowości - to C++ też umie w te klocki, a Rust Javę mocno przewyższa.

1
Krolik napisał(a):

W praktyce cały czas nie jest 20% a 2x-3x i to jak się jest bardzo ostrożnym. Jak się nie uważa, i pisze w stylu OOP albo FP a nie data-oriented, to spokojnie można stracić 90% wydajności i nawet trudno zauważyć kiedy i gdzie zniknęło.

Ale o czym mówimy? Liczby bez kontekstu nic nie znaczą, można sobie podać zupełnie dowolne dane i dowolnie je zinterpretować pod dowolną tezę - nikt nie sprawdzi. Można napisać program w Pythonie, który będzie nieskończenie szybszy niż program C, choć będą robić dokładnie to samo. Więc te dane, bez kodu i usecase są nic nie warte - możemy dyskutować jak filozofowie.

Dlaczego o tym wspominam: bo też śledze temat wydajności, i jeszcze nie trafiło mi się, żeby ktoś nie zareagował na "ostateczny" przykład wydajności w jakimkolwiek języku - zawsze znajdzie się maher co "zejdzie" kolejne kilka procent, jakimś abstrakcyjnym kodem, choć "reszta" maherów już myślała, że nie da się (dotyczy w zasadzie każdego języka).

Dobry przykład: stockfish (silnik szachowy, bardzo mocno cpu-bound), napisany w C++, gdzie nieraz PRy ciągną 20 plików masakrycznych zmian, bo ktoś "wynalazł" wzrost wydajności o 2% stosując jakieś supertricki bitowe i przesunięcia na rejestrach, ledwie udokumentowane w najnowszych CPU, które zna marny procent inżynierów. Tylko co z tego, jak np. inny silnik (LC0) jest z ~3000x wolniejszy w analizie pozycji, a gra lepiej, bo bazuje na zupełnie innym algorytmie - równie dobrze mogliby go w Pythonie napisać, bo to algorytm decyduje o tym co w tym najważniejsze: o sile gry. I podejrzewam, że podobnie jest w przypadkach optymalizacji w innych dziedzinach. Więc nie rozpędzałbym się z tą wydajnością za wczasu.

Druga sprawa to przewidywalność wydajności - w aplikacjach desktopowych ważne jest szybkie działanie od początku, a nie stopniowe rozpędzanie się.

Moim zdaniem, znowu zbyt szeroko powiedziane - jest pełno aplikacji w pythonie (to chyba nie jest przedstawiciel "szybkich" języków?) na linuksach, które mają przyzwoitą wydajność, nawet nie idzie w ogóle dociec, że one są w Pythonie (patrz: terminator, kupfer - te które ja znam). Nie robią cudów na procesorze, wystarczy, że robią co powinny i nie zauważam, by wydajnościowo odbiegały od natywnych. Jest wręcz odwrotnie, choć to wrażenie czysto subiektywne nic nie mające wspólnego z porządnymi pomiarami (krunner vs kupfer, krunner w C++, jakkolwiek go nie skonfiguruję, to zawsze jest wolniejszy niż kupfer w pythonie, więc nie wiem co on tam robi "jeszcze")

Ba! To jest ważne również w aplikacjach serwerowych. Jak klient ma zrestartować klaster ze 100 serwerów i każdy startuje 30-50 sekund, ale rozpędza się
kolejne kilka minut, to trudno się dziwić, że taki klient będzie się przed tym bronić jak tylko może.

Dziś są takie wynalazki jak canary deployment i nikt nie będzie wstrzymywał oddechu, bo jakiś restart się dzieje. Nie te czasy. Teraz część infrastruktury może się cały dzień restartować i nikt tego nie zauważy zwyczajnie (jeśli jest to zrobione dobrze)

3

@Krolik:
Spokojnie, Java się rozwija i powoli nadrabia zaległości w wydajności.

Zacznijmy od:

Trzecia sprawa to pamięć. Zdarza się, że JVM nawet faktycznie zbliza się do optymalnej wydajności, ale kosztem zużycia o rząd wielkości więcej RAMu. A czasem dwa rzędy wielkości. Zwłaszcza jeśli dołożymy konieczność minimalizacji pauz z GC. GC lubi dodatkowa przestrzeń.

Rzeczywiście - tracing GC wymaga dodatkowej przestrzeni i to z założenia. Pojedynczy przebieg tracing GC jest kosztowny, więc w takim przebiegu musi być wyłapane wystarczająco dużo śmiecia by był opłacalny. A dużo śmiecia wymaga dużego zapasu pamięci. To nie jest wada Javy, a tracing GC, więc każdy kto używa tracing GC będzie musiał się z tym pogodzić.

Wracając jednak do wydajności alokacji skopiuję komentarze:

Alokacja w Javie nie jest wcale szybsza od malloc. Dla małych obiektów jest taka sama, dla dużych jest powolniejsza. Natywne alokatory nie stały w miejscu przez ostatnie 10 lat. - Krolik dziś, 11:50
Benchmarki mówią co innego: https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/binarytrees.html Najszybszym rozwiązaniem na mallocu (czyli bez pul obiektów) w C++ jest C++ g++ #2 zabierające 38s, dla C gcc #5 zabierające 18s, Rust ma tylko pule (typed_arena), dla Javy mamy Java #7 zabierające 8.25s, a dla C# jest C# .NET Core #6 zabierające 5.59s. Dla dużych obiektów oczywiście malloc będzie szybsze niż alokacja w Javie, bo malloc nie inicjalizuje alokowanych obiektów, a Java je zawsze zeruje (pomijając Unsafe). - Wibowit 29 minut temu

Wracając do postu:

Druga sprawa to przewidywalność wydajności - w aplikacjach desktopowych ważne jest szybkie działanie od początku, a nie stopniowe rozpędzanie się. Ba! To jest ważne również w aplikacjach serwerowych. Jak klient ma zrestartować klaster ze 100 serwerów i każdy startuje 30-50 sekund, ale rozpędza się kolejne kilka minut, to trudno się dziwić, że taki klient będzie się przed tym bronić jak tylko może.

Rozwiązania są dwa, oba oparte o GraalVMa. Jedno to native-image z GraalVMa, który kompiluje aplikację całkowicie w trybie AOT i działa to całkiem dobrze: https://quarkus.io/#container-first Drugie to jaotc czyli połączenie AOTa z JITem (przy użyciu opcji --compile-for-tiered): https://openjdk.java.net/jeps/295 i wtedy mamy zarówno szybki start dzięki binarce jak i możliwości JITa.

W praktyce cały czas nie jest 20% a 2x-3x i to jak się jest bardzo ostrożnym. Jak się nie uważa, i pisze w stylu OOP albo FP a nie data-oriented, to spokojnie można stracić 90% wydajności i nawet trudno zauważyć kiedy i gdzie zniknęło.

Tutaj też coś się dzieje, ale trzeba poczekać. Project Valhalla http://openjdk.java.net/projects/valhalla/ dla typów wartościowych i generykach na nich oraz Project Panama http://openjdk.java.net/projects/panama/ który dostarcza np API do bezpiecznego niskopoziomowego zarządzania pamięcią spoza sterty Javowej czy SIMDów. Project Panama daje takie same możliwości aranżacji struktur danych w pamięci jak typowe języki niezarządzane typu C, C++, Rust, etc

Co do wielowątkowości - to C++ też umie w te klocki, a Rust Javę mocno przewyższa.

A Golang jest jeszcze wyżej. Project Loom http://openjdk.java.net/projects/loom/ ma dostarczyć równie lekkie i szybkie abstrakcje dla wielowątkowości co te dostępne w Go. Poza tym cały czas jestem zdania, że podejście funkcyjne znacząco ułatwia pisanie programów wielowątkowych - po prostu pakuję jakiś kawałek kodu do Future'a, Task'a, Executora czy czego tam jeszcze (obojętne nawet) i nie przejmuję się data races czy tym który wątek ma zwalniać pamięć.

Poza tym, jak wspomniał @TurkucPodjadek ważna jest architektura projektu, a nie tylko niskopoziomowe optymalizacje. Do tego trzeba dodać szybkość reakcji na zmiany. Jeśli robimy projekt, który trzeba w kółko orać bo zmieniają się wymagania (to jest normą w biznesowych aplikacjach) to kluczowym parametrem jest podatność kodu na refaktoryzację. Jeśli jest niska (tzn refaktor jest pracochłonny) to będzie tendencja ku nawarstwianiu się prowizorycznych haków (często kosztem wydajności) niż przeprojektowaniu kawałka systemu, by efektywniej realizował nowe wymagania.

0

Spokojnie, Java się rozwija i powoli nadrabia zaległości w wydajności.

@Wibowit jak długo już nadrabia? Nie czas żeby w końcu przeskoczyć? Zawodowo nie pracuję długo, od 2011, ale 9 lat to i tak sporo czekania i rok w rok ta sama śpiewka. Tymczasem przykład takiej ScyllaDB vs Cassandra pokazuje, że możemy jeszcze trochę poczekać. Z C++ jest wiele rzeczy nie tak, ale przynajmniej jednej rzeczy udało się w nim nie skopać, nie zabrano w nim kontroli.

EDIT:
Funny part is - java już teraz rywalizuje z C++ jeśli chodzi o performance, przynajmniej jeśli chodzi o low latency gdzie nie używa się FPGA. Tym nie mniej poza tą częścią branży, C++ nadal jest szybszy. Może javowa społeczność skupia się nie na tych aspektach, których powinna skoro wciąż czytam o nadrabianiu zaległości?

5

Z C++ jest wiele rzeczy nie tak, ale przynajmniej jednej rzeczy udało się w nim nie skopać, nie zabrano w nim kontroli.

W Javie też udało się pewnej ważnej rzeczy nie skopać: w dużej mierze nie ma https://en.wikipedia.org/wiki/Memory_corruption , nie ma undefined behaviour czy implementation defined (no chyba, że ktoś odpala z Javy natywny kod np poprzez JNI albo używa sun.misc.Unsafe ale tego żaden przeciętny Javowiec nie robi). Java jest na tyle przewidywalna, że każdy się może bawić w optymalizacje kodu i nie dostanie segfaultów czy wyjścia poza zakres tablic tylko exceptiona, którego można złapać i zdebugować jak każdego innego.

@Wibowit jak długo już nadrabia? Nie czas żeby w końcu przeskoczyć? Zawodowo nie pracuję długo, od 2011, ale 9 lat to i tak sporo czekania i rok w rok ta sama śpiewka.

No i jeszcze z parę lat minie zanim te projekty, o których napisałem wejdą do Javki z głównego nurtu.

Może javowa społeczność skupia się nie na tych aspektach, których powinna skoro wciąż czytam o nadrabianiu zaległości?

Autorzy Javy skupiają się przede wszystkim na tym co jest najbardziej potrzebne w typowych zastosowaniach Javy. Przeciętny Javowiec klepie kod biznesowy, a nie żyłuje stałą w złożoności obliczeniowej, więc jak na razie skupiano się na wydajności GC, modularności by umożliwić odchudzenie JRE (to był spory dług techniczny i zabrał wiele lat) czy zbijaniu narzutu wysokopoziomowego kodu.

Ponadto autorzy Java podnoszą sobie poprzeczkę i starają się wprowadzać mechanizmy bezpieczne i kompatybilne wstecznie. Dla przykładu:

  • Project Valhalla - tutaj dba się o kompatybilność wsteczną, czyli takie wprowadzenie typów wartościowych by stare kolekcje i inne klasy mogły na tym skorzystać. Ponadto typy wartościowe w Project Valhalla są immutable, więc dochodzi konieczność zaimplementowania wielu nowych optymalizacji, by wydajność była taka jak przy mutable.
  • Project Panama - tutaj dba się o https://en.wikipedia.org/wiki/Memory_safety i przewidywalny kontrakt. Nieudana zabawa ze wskaźnikami czy SIMDami ma skutkować zwykłymi exceptionami, a nie segfaultami, undefined behaviour, nadpisaniem pamięci spoza bufora przypisanego do wskaźnika itp itd Dodatkowo API do SIMDów jak i do żonglowania bajtami (padding w strukturach danych, endianess, itp) ma być przenośne między architekturami procesorów. Dzięki temu niedzielny programista będzie mógł się tym zająć bez obaw że urwie mu nogę i będzie musiał tygodniami szukać buga. Szukanie błędy będzie odbywać się tak jak w przypadku normalnych Javowych programów.
  • Project Loom - tutaj problemem jest pożenienie nowych rodzajów wątków z obecnym API czy obecnych GC z nowym sposobem obsługi stosu. Obecnie na zarządzanej stercie są tylko obiekty o regularnej strukturze (zdefiniowanej przez klasę obiektu), natomiast ramki stosu mają strukturę wyjątkowo nieregularną (zmieniającą się nie tylko z metody na metodę ale także z powodu optymalizacji, np escape analysis prowadzącej do przenoszenia alokacji ze sterty na stos). Project Loom jest o tyle istotny, że pozwala uniknąć (podobnie jak Golang) rozdwojenia kolorów funkcji (szczegóły: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ ), a więc nie trzeba każdej blokującej metody przerabiać na wersję async, bo Project Loom przerobi ją automatycznie wedle bieżących potrzeb (zamieniając operacje typu Thread.sleep na przełączenie zadania na inne, przez co nie blokujemy wątku i nie musimy ich dużo tworzyć - zaleta dokładnie taka sama jak przy typowym async).

Moje przewidywania co do popularności tych zmian są takie:

  • Project Valhalla (typy wartościowe i generyki na nich) - to będzie szeroko używane, zwłaszcza jeśli wypali plan przerobienia części już istniejących klas na klasy wartościowe (wtedy będą szeroko używane z automatu). C#-owcy stracą koronny dowód na wyższość C# czyli wyłączność na możliwość zrobienia List<int>.
  • Project Panama (konkretnie chodzi mi o niskopoziomowe, ale bezpieczne zabawy ze wskaźnikami i SIMDem) - to będzie ogólnie rzadko używane (bo w 90%+ aplikacji ważniejsze jest szybkie prototypowanie i refaktoryzacja niż żyłowanie stałych w złożoności obliczeniowej), ale będzie królować w niszach takich jak benchmarki, low-latency Java (HFT i takie tam) czy biblioteki do ciężkich obliczeń matematycznych (typu mnożenie macierzy, sztuczne sieci neuronowe, przetwarzanie obrazu, itd).
  • Project Loom (zmiana sposobu współbieżności) - tutaj migracja prawdopodobnie trochę potrwa. Trudno powiedzieć, bo nie znam innej popularnej platformy, która migrowałaby się ze zwykłych wątków na mechanizmy spotykane w Go.

Oczywiście można mieć pretensje do tego podejścia i stwierdzić, że można zrobić to prościej, np:

  • Project Valhalla - można zrobić typy wartościowe i generyki na nich niekompatybilne ze starymi mechanizmami. Wtedy zdecydowanie byłoby prościej, bo tworzyłoby się nowy system typów od nowa i od razu byłoby coś widać. Jednak problemem jest kompatybilność wsteczna. C# 2.0 mógł sobie pozwolić na generyczne kolekcje niekompatybilne ze starymi, bo C# był jeszcze młody, a kodu w nim mało. Dzisiaj C# dba o kompatybilność wsteczną prawie, że równie mocno co Java.
  • Project Panama - można by zrobić zabawy ze wskaźnikami czy SIMDy, które są nieprzenośne (osobne intrinsics dla każdej architektury procesora), unsafe i mają pełno UB czy implementation specific i wtedy rzeczywiście byłoby znacznie prościej dla autorów Javy. Jednak wtedy byłoby to równie nieprzystępne dla programisty Javy co analogiczne rozwiązania w innych językach.
  • Project Loom - można by olać continuations, fibery, itp itd mechanizmy znane np z Go i pójść drogą innych języków głównego nurtu wstawiając nową składnię dla asynca i zrzucając na programistów problem integracji kodu synchronicznego z asynchronicznym.

Co kto lubi. Java ma być językiem, który ma przetrwać kolejne dekady w korporacjach i aplikacjach biznesowych, więc musi być przystępna dla przeciętnego programisty i nadająca się do płynnej migracji starego kodu do nowych konstrukcji składniowych czy nowego API. Do tego ma być nadal bezpieczna (memory safety itd), ustandaryzowana i przenośna (ten sam kawałek kodu ma działać na różnych systemach operacyjnych i architekturach procesorów). Gdyby to nie było ważne to do Javy już dawno można byłoby napchać mnóstwo nieprzenośnych konstrukcji unsafe i chwalić się wydajnością na poziomie C++.

3

Zdaje sobie sprawę z tego, że Java nie stoi w miejscu i że te projekty usprawnią wydajność. Tylko że w tym czasie twórcy takiego GCC czy LLVM też nie próżnują.

Przykład, który podałem wcześniej dotyczył realnego kodu wziętego z istniejącego systemu. Kod był zoptymalizowany przez eksperta zajmującego się tuningiem Javy zawodowo (obecnie pracującego dla Apple) - zero alokacji na stercie, brak obiektowości, brak dynamizmu, wszędzie typy proste. Ogólnie czyta się ten kod jak C napisane w Javie. Mimo tego ostateczny rezultat jest nadal 5x powolniejszy od kodu Rust napisanego przez nooba (mnie) w trzy weekendy.

Co więcej, cała ta magia JVM wprowadza masę dodatkowej złożoności, która jakby żyje swoim własnym życiem. Ja nie wątpię, że w końcu da się osiągnąć dobrą wydajność. Możliwe, że przy kolejnej iteracji nasz ekspert poprawiłby kod jeszcze bardziej i przyspieszył go o te brakujące 5x. Jednak nie ma mowy o jakiejś większej produktywności Javy w tym przypadku. Kod powstaje znacznie wolniej, jest znacznie mniej czytelny i bardzo łatwo go zepsuć.

Przy każdym wydaniu naszego softu w Javie trafi się zawsze ileś testów wydajnościowych, które pokazują na regresję wydajności. Tak samo raz na 100 klientów trafi się jakiś, któremu automagiczne GC świruje. Albo taki, który ma 10 sekundowe pauzy. Oczywiście domyślam się, że to jest niezależne od języka i w C++ byłyby regresje. Jednak nie trzeba by było walczyć z taką ilością magii w samym środowisku wykonawczym.

Zobacz sobie ile jest opcji samego GC i ile jest osób w środowisku, które rozumieją co te opcje robią. Spróbuj też wrzucić jakiś nietrywialny kod na profiler Javy i sprawdź czy wyniki mają sens i ile czasu musisz mierzyć aby sygnał zaczął przewyższać szum. Tworzenie wydajnego kodu w Java to jest zabawa w chodzenie po polu minowym.

W C, C++, Rust jak mam regresję, to w ostateczności podejrzę kod w assembly i zweryfikuję czy kompilator nie porobił głupot. W Javie mogę oczywiście też podejrzeć, ale nic mi to nie da bo u klienta może być zupełnie inaczej!

zawsze znajdzie się maher co "zejdzie" kolejne kilka procent, jakimś abstrakcyjnym kodem, choć "reszta" maherów już myślała, że nie da się (dotyczy w zasadzie każdego języka).

Pytanie jakim kosztem. Jeżeli trzeba zrezygnować z 90% udogodnień języka, aby uzyskać dobrą wydajność, to nie możemy wtedy używać argumentu, że Java / C# / Python / (inny wysokopoziomowy wynalazek) oferują większą produktywność niż języki od początku zorientowane na wydajność.

Różnice w produktywności programistów pomiędzy językami są często przeszacowane. Np. to badanie stwierdza, że jest pewna różnica między C++ a Java, ale jest dość niewielka:

https://www.academia.edu/26018067/Comparison_of_software_development_productivity_based_on_object-oriented_programming_languages

0
Krolik napisał(a):

Różnice w produktywności programistów pomiędzy językami są często przeszacowane. Np. to badanie stwierdza, że jest pewna różnica między C++ a Java, ale jest dość niewielka:

https://www.academia.edu/26018067/Comparison_of_software_development_productivity_based_on_object-oriented_programming_languages

Zerknąłem w to badanie, jest z roku 2010 (i odwołuje się w źródłach do prac z lat 80 nawet)... 2010... nie wiem czy to te czasy, gdy w Javie gettery i settery trzeba było nawet do hello world stosować :-), co w połączeniu z badaniem, w którym jednym z wiodących wskaźników jest ilość linii kodu, wiadomo do jakiego wniosku to poprowadzi. Rusta, Go (self compiled dopiero od 1.5 - 2015) czy Kotlina wtedy jeszcze nie było, więc nie wiem czemu dla porównania nie użyto np. Pythona wtedy.

Bardziej do mnie przemawia to "badanie": https://thume.ca/2019/04/29/comparing-compilers-in-rust-haskell-c-and-python/#python - przykład w Python tutaj rzekomo był "przeładowany" for fun i z nudów już, a i tak był w kwestii linii kodu 2x mniejszy niż baseline, co pokazuje, że kod w języku statycznie typowanym i nastawionym na poprawnośc i wydajność, możesz po napisaniu równie dobrze wyrzucić do kosza, bo ze względu na time-to-market, konkurencja będzie mieć już z 3 lub 4 wersję swojego produktu, co prawda pełną błędów, ale będzie go mieć i klienci będą już korzystać, gdy Ty dopiero skończysz swoją "poprawną" 1.0. I to zakładając, że nie wystrzeli Cię z siodełka wcześniej brak ważnych bibliotek - np. Rust i Go ze względu na "młodość" są na to bardzo narażone, a nie każdy od razu zakłada, że idzie w mikroserwisy i brak takich bibliotek nadrobi jakimś serwisem we właściwym języku, który się odpyta po jakimś grpc czy kolejkami.

0
Krolik napisał(a):

Różnice w produktywności programistów pomiędzy językami są często przeszacowane. Np. to badanie stwierdza, że jest pewna różnica między C++ a Java, ale jest dość niewielka:

https://www.academia.edu/26018067/Comparison_of_software_development_productivity_based_on_object-oriented_programming_languages

Zerknąłem w badanie i nie wydaje mi się wiarygodne. Zadania są w stylu "Matrix addition integrated by real numbers", " Computing the linear regression equation parameters", "Both storing and searching records from a file". Przy tak prostych zadaniach rzeczywiście java nie będzie bardziej produktywna niż cpp. Ba kod w javie nawet nie będzie się jakoś mega różnił od cpp.

EDIT: to jest imo bardziej wiarygodne - https://www.ifpug.org/wp-content/uploads/2017/04/IYSM.-Thirty-years-of-IFPUG.-Software-Economics-and-Function-Point-Metrics-Capers-Jones.pdf
EDIT2: trzeba jeszcze zauważyć, że badania zazwyczaj sprawdzają, ile zajmuje Ci wyklepania zadania od 0. Sam maven daje +10 do produktywności javy. Nie mówiąc o wszystkich javowych bibliotekach, które działają bez zabawy - w cpp, jeżeli uda Ci się pobrać projekt z gh i make się nie wywali, to resztę dnia świętujesz.

1

Przykład, który podałem wcześniej dotyczył realnego kodu wziętego z istniejącego systemu. Kod był zoptymalizowany przez eksperta zajmującego się tuningiem Javy zawodowo (obecnie pracującego dla Apple) - zero alokacji na stercie, brak obiektowości, brak dynamizmu, wszędzie typy proste. Ogólnie czyta się ten kod jak C napisane w Javie. Mimo tego ostateczny rezultat jest nadal 5x powolniejszy od kodu Rust napisanego przez nooba (mnie) w trzy weekendy.

Co więcej, cała ta magia JVM wprowadza masę dodatkowej złożoności, która jakby żyje swoim własnym życiem. Ja nie wątpię, że w końcu da się osiągnąć dobrą wydajność. Możliwe, że przy kolejnej iteracji nasz ekspert poprawiłby kod jeszcze bardziej i przyspieszył go o te brakujące 5x. Jednak nie ma mowy o jakiejś większej produktywności Javy w tym przypadku. Kod powstaje znacznie wolniej, jest znacznie mniej czytelny i bardzo łatwo go zepsuć.

Skoro Java jeszcze nie ma konstrukcji językowych, ani API do niskopoziomowych optymalizacji to nie dziwne, że takie zabawy są w Javie mało opłacalne. To trochę jakby powiedzieć, że w Pythonie żonglowanie bajtami by uzyskać wydajność zbliżoną do C++a jest niepraktyczne i dlatego ten język nie sprzyja produktywności. Java na razie jest nastawiona głównie na aplikacje biznesowe, gdzie jest dużo warstw abstrakcji, dużo mielenia stringów, dużo używania standardowych kolekcji, dużo niskiej jakości (ale i tak poprawnie działającej) wielowątkowości, itd

3

No i teraz wracamy do głównego tematu - na desktopie i w aplikacjach mobilnych wydajności brakuje. Procesory nie przyspieszają już tak jak kiedyś, a aplikacje są coraz bardziej rozbudowane.

Efekty? Współczesne interfejsy lagują bardziej niż Windows na 486 20+ lat temu. W VSCode wpisywanie literek potrafi lagować!

Zapotrzebowanie na szybki kod będzie rosło, bo sprzęt już tak nie przyspiesza jak kiedyś i wydajność będzie coraz bardziej ograniczała funkcjonalność.

Przy tym sam desktop może nawet będzie tracił na znaczeniu, ale za to aplikacje mobilne będą zyskiwać na znaczeniu. A tak liczy się niskie zużycie baterii. W tym zakresie języki pozwalające tworzyć lekkie i szybkie aplikacje będą nadal w użyciu. Np. Google Fuchsia stawia na C++, Rust I Dart.

1

Efekty? Współczesne interfejsy lagują bardziej niż Windows na 486 20+ lat temu. W VSCode wpisywanie literek potrafi lagować!

Mi kilkanaście lat temu strony bez JSa i obrazków potwornie lagowały (widać było proces rysowania strony poszczególnymi etapami) na Internet Explorerze na kompie z chyba Celeonem 300 MHz i 96 MiB RAMu.
VSCode zamula, ale pomiędzy JSem, a C++em jest sporo innych platform do wyboru, a w takim IntelliJu edycja kodu przebiega bardzo płynnie.

1
Krolik napisał(a):

No i teraz wracamy do głównego tematu - na desktopie i w aplikacjach mobilnych wydajności brakuje. Procesory nie przyspieszają już tak jak kiedyś, a aplikacje są coraz bardziej rozbudowane.

Efekty? Współczesne interfejsy lagują bardziej niż Windows na 486 20+ lat temu.

To może problem tych "interfejsów" (na marginesie, wolałbym jakbyś użył "interfejsu użytkownika"). Rzekomo chcianych przez użytkowników, "projektowanych" przez marketingowców. nawiasem, właśnie minute temu się zmagałem z aplikacją webową od firmy finansowej, która miała "ułatwiacz", fruwający przycisk "skopiuj do schowka", który znikał po najechaniu, ale zaraz potem zasłaniał pole.

Czy branża UI zmądrzeje?

Zapotrzebowanie na szybki kod będzie rosło, bo sprzęt już tak nie przyspiesza jak kiedyś i wydajność będzie coraz bardziej ograniczała funkcjonalność.

Wątek skręcił z "desktop". Nie neguję, że szybki kod ogólnie w algorytmach to OK, nie ma gadania. Ale po co hyper super duper szybki kod w UI, jeśli większość jest interpretowana dla zwiększenia bajerów?
Przykład WPF versus WinForms, JavaFX versus Swing, to samo w C/C++

0
AnyKtokolwiek napisał(a):

Wątek skręcił z "desktop". Nie neguję, że szybki kod ogólnie w algorytmach to OK, nie ma gadania. Ale po co hyper super duper szybki kod w UI, jeśli większość jest interpretowana dla zwiększenia bajerów?
Przykład WPF versus WinForms, JavaFX versus Swing, to samo w C/C++

O co chodzi z tym WPF vs WinForms?

0

WPF różni się w podstawie od WinForms, tym, że posiada swój silnik renderujący i stos okien, a WinForms używa WinAPI. To są zupełnie inne technologie... Niemniej WPF na współczesnych komputerach wcale nie jest taki wolny, a co do samego UI - od lat nie pisze się aplikacji zależnych od prędkości UI a raczej idzie się w asynchroniczność. Zresztą to nie UI może spowalniać. UI zazwyczaj jest proste, natomiast operacje na danych mogą w miarę rozrostu danych być bardziej czasochłonne. Z doświadczenia wiem, że przy rozwoju programu czas potrzebny na dana operacje jest zależny od długości przetwarzania a nie czasu wyrysowania okna.

0

Interfejs nie musi być szybki w sensie "przepustowości" (throughout) ale ma mieć niskie opóźnienia (response time). I dlatego mimo tego że to powiedzmy taka Java z Hotspotem potrafi osiągnąć niezłą wydajność w hurcie, to do desktopa nadaje się słabo i praktycznie na desktopie umarła (poza jednym lub może dwoma IDE).

Na Androidzie też koncepcja maszyny wirtualnej umarła dość dawno temu i teraz jest ART (czyli AOT), a w Fuchsji Google położył jeszcze większy nacisk na kod natywny i tam chyba wszystko musi być skompilowane normalnie statycznie.

1

Skoro AOT na Androidzie niby uratował Javę, to czemu miałby nie uratować na desktopie? native-image z GraalVM CE jest za darmo.

0

Fajnie, tylko po co? I jak to niby zlikwiduje problemy np. z lagami GC? To rozwiązuje tylko problem ładowania / kompilacji w tle.
Poza tym Java jest masakrycznie trudna do dobrej kompilacji statycznej, bo polega na dynamicznych wywołaniach, więc po co mieć wady wydajnościowe Javy i wady dystrybucji natywnych binarek? Lepiej już wziąć coś co było projektowane z myślą o natywnych obrazach od początku.

2

@Krolik: nie rozumiem esencji Twojego problemu - może go jakoś opisz, przedstaw kod, cokolwiek. Na razie, opisujesz abstrakcyjno-teoretyczne problemy czegoś tam bez żadnego konkretnego use case na stole. Faworyzujesz ogólnikowo jedną rzecz (wydajność), kosztem wszystkiego innego (przynajmniej ja to tak rozumiem). Na przykładzie silników szachowych obecnych pokazałem Ci, że to błędne podejście (jako ogólne). Nie wiem, przedstaw konkretny problem, opisz swoje pomiary i pokaż coś, co udowadnia w "praniu" Twoje twierdzenia, pokazuje Twój problem - np. masz kod w Javie, który z powodu tego, że to Java/JVM, ma taki a taki problem, a problem rozwiązujesz takim prostym kodem C++ czy Rust[1], który tego problemu nie ma. Bo na razie to dyskusja religijna, niby górnolotne kwestie latają, ale konkretów zupełnie brak.

[1] Risky as hell - niemal zawsze, na odpowiednio specjalistycznym forum pokażą Ci, że języka programowania tak naprawdę jeszcze nie znasz (albo nie wiesz jaki masz problem - to jeszcze gorzej). Polecam to podejście osobom, które chcą się czegoś nauczyć. :-) Zresztą, polecam moje 2 ostatnie wpisy na mikroblogu o Rust - zawsze mnie to zaskakuje ile można się dowiedziec z komentarzy pod takim wpisem, a jak tylko proste biedakody pokazuję.

1

Kompilacji za pomocą native-image nie trzeba wymyślać od nowa za każdym razem. Powstają narzędzia, które to automatyzują i upraszczają.

Czy GC dużo zmienia? Moim zdaniem niespecjalnie. Programy z MS Office są napisane w C++, a potrafią się zamrozić na wiele sekund i to regularnie (głównie Excel i Outlook w moim przypadków). Głupie notatniki też się zamrażają przy otwieraniu dużych plików. Komunikatory są pisane często w JS lub Javie i korpo chętnie z nich korzystają. W sumie to na firmowym lapku niestandardowe (tzn nie systemowe, nie przeglądarki i nie z MS Office) apki pisane w C/ C++ to nawet mniejszość - tylko notatniki i terminale. Komunikatory (przez bałagan w firmie mam ich kilka) i narzędzia to głównie JS i Java.

Sytuacje w których GC odczuwalnie przeszkadza w aplikacjach desktopowych to moim zdaniem raczej rzadkość. Większym problemem jest brak asynchroniczności. GUI się totalnie przycina, bo w tle coś się mieli, albo coś czeka na komplet nowych danych.

2

Polemizuję z tezą z pierwszego posta:

Projektów w których szczególnie zależy klientowi na wydajności jest już mało i wraz z rozwojem sprzętu będzie coraz mniej.

  1. Dynamiczny rozwój sprzętu to już przeszłość. O 2-krotnym wzroście wydajności na półtora roku można zapomnieć. Dobijamy do granic fizycznych możliwości. Będzie co najwyżej więcej rdzeni, ale też bez szału, bo chłodzenie / zużycie energii nie pozwoli. Na urządzeniach mobilnych liczy się zużycie baterii.

  2. W każdym projekcie wydajność jest istotna, jedynie w innym miejscu leży granica pomiędzy nieakceptowalną a akceptowalną wydajnością. Czasami sama zmiana wydajności otwiera nowe możliwości funkcjonalne. Gdybyśmy nadal internet mieli na modemach 28 kbps to taki serwis jak YouTube nigdy nie miałby racji bytu.

  3. Większość oprogramowania, które używam ma wydajność na granicy akceptowalności lub niestety poniżej. Ale mało kto faktycznie tym się przejmuje, ponieważ mylnie uważa się że czas programisty jest droższy od czasu maszyny. VSCode porzuciłem i używam tylko jak muszę, bo lagi mnie irytują. Irytuje mnie jak na przeglądarkę muszę czekać 2 sekundy aż się otworzy. Oczekiwania klientów wobec wydajności będą rosły.

  4. Wielu adwersarzy tej dyskusji robi tu mylnie ciche założenie, że dobra wydajność jest ZAMIAST czegoś innego i że coś tam innego WYKLUCZA. Np. że można mieć wydajniejszy kod ale kosztem produktywności albo kosztem stabilności itp. To jest bardzo naciągane założenie. C++ ma faktycznie słabo zrobione sprawy związane z bibliotekami i zależnościami, co może ubić produktywność, ale to tylko tooling i nie ma nic wspólnego z tym, czy programy w C++ są wydajne czy nie. To wynika po prostu z historii. Rust jakoś ma o niebo lepiej rozwiązaną modularyzację niż Java/C# i nic to w wydajności mu nie przeszkadza.

I skoro tak chcieliście kod, to proszę.
W czym ten górny kod (Rust) jest trudniejszy do napisania niż ten dolny (Scala)?

let collection = vec![0, 1, -59, 2, 4];
println!("Sum of positive elements: {}", collection.iter().filter(|&&item| item > 0).sum());
val collection = Vector(0, 1, -59, 2, 4)
println(s"Sum of positive elements: ${collection.iterator().filter(_ > 0).sum())}")

Przecież ten kod jest niemal identyczny. Ale nie jest identyczny wydajnościowo.
Ten drugi będzie o rząd wielkości powolniejszy (oczywiście trzeba mierzyć na większych tablicach).
Nawet Java na IntStreamach, które rzekomo są zoptymalizowane będzie jakieś 5x powolniejsza. Po co oddawać wydajność za nic w zamian?

1

Dynamiczny rozwój sprzętu to już przeszłość. O 2-krotnym wzroście wydajności na półtora roku można zapomnieć. Dobijamy do granic fizycznych możliwości. Będzie co najwyżej więcej rdzeni, ale też bez szału, bo chłodzenie / zużycie energii nie pozwoli.

AMD pokazuje, że da się upakować 2x więcej rdzeni przy tym samym TDP co Intel, a to dzięki postępowi w TSMC. Jeszcze dobre kilka lat będziemy mieć zwiększanie ilości rdzeni. A skoro ilość rdzeni się zwiększa to coraz ważniejsze jest łatwe programowanie wielowątkowe. Javowym rozwiązaniem na to ma być Project Loom https://openjdk.java.net/projects/loom/ Do wielowątkowości jest wiele podejść, każdy niech sobie oceni w czym mu się najwygodniej kodzi.

Kod jest niemal identyczny.
Ale nie jest identyczny wydajnościowo.
Ten drugi będzie o rząd wielkości powolniejszy (oczywiście trzeba mierzyć na większych tablicach).

Ten drugi jest wolniejszy, bo tam są Integery, a nie inty (czyli jest boxing). Na razie nie ma reified generics, więc takie benchmarki testuję dwa zupełnie inne przypadki. Gdybyś testował na tablicach to miałoby to więcej sensu.

0

A skoro ilość rdzeni się zwiększa to coraz ważniejsze jest łatwe programowanie wielowątkowe. Javowym rozwiązaniem na to ma być Project Loom https://openjdk.java.net/projects/loom/

Za późno i za mało. W jaki sposób Loom pozwoli pisać łatwiej kod współbieżny?
Dla mnie to rozwiązuje bardzo specyficzny problem pisania kodu asynchronicznego tak jakby był synchroniczny. Ok, tylko że to nie jest wcale łatwiejsze niż to co jest teraz. A i inne języki już to dawno mają (Go, Rust, Kotlin).

1

Dla mnie to rozwiązuje bardzo specyficzny problem pisania kodu asynchronicznego tak jakby był synchroniczny.

No to bardzo podobnie do składni async/await, z tym, że nie trzeba w ogóle stosować async/await i dzięki temu unikamy duplikowania metod. Nie trzeba mieć osobnych metod async i nie-async, bo to czy są async zależy od tego na jakim typie wątku/ executora/ etc go puścisz, a nie jaką ma sygnaturę.

0

Zawsze znajdą się ludzie którzy będą chcieli wycisnąć max'a z hardwareu. nawet jak gry będą w rozdzielczości 256k na 288MHz i wymagały 1024 GB RAMU, to będą ludzie którzy będą chcieli je odpalić na Ultra High, zamiast High.

4

Atwood's Law:

Any application that can be written in JavaScript, will eventually be written in JavaScript

1 użytkowników online, w tym zalogowanych: 0, gości: 1