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

Czy C++ desktop upadnie?

0

@Krolik:
W zasadzie jedynym popularnym językiem, który ma wbudowanego borrow-checkera w kompilator jest Rust, a z twoich twierdzeń wynika, że bez borrow checkera od razu wpakujemy się w kupę, więc prostym wnioskiem jest to, że każdy język poza Rustem jest badziewiem. Zgadza się?

Rust jest jeszcze za młody, by było w nim naklepane dużo legacy kodu, a jak do tego dojdzie to pewnie poczytamy i krytykę.

3

Nie. Raczej spodziewam się, że takie rzeczy jak borrow checker będą trafiały do innych języków. Dla C++ już są lintery które sprawdzają czas życia obiektów i referencji.

Po prostu do niedawna uważano, że problemy z pamięcią można rozwiązać tylko przez GC, a żeby mieć dobre GC, to potrzebna jest maszyna wirtualna.
Natomiast teraz Rust pokazał, że w sumie można rozwiązać problemy z pamięcią bez GC, bez maszyny wirtualnej, do tego jeszcze oferując parę innych fajnych cech jak wolność od wyścigów. A wszystko przy świetnej wydajności i niezłej czytelności / ekspresywności kodu. Tym samym jeden z bardzo dużych powodów dla istnienia platform zarządzanych takich jak JVM czy .NET właśnie został unieważniony. Bak GC i brak rozbudowanego runtime'u upraszcza bardzo wiele rzeczy. To nie tylko rozwiązania embedded, ale też choćby możliwość kompilacji na webassembly. Możliwość bezproblemowego wywoływania API systemu i integracja z kodem C / C++ to też bardzo duża rzecz.

Liczę na to, że pociągną to dalej i nawet jeśli sam Rust się nie przyjmie, to że będzie mieć naśladowców. Microsoft zdaje się pracuje nad kolejnym językiem z tej samej kategorii.

Drugim dużym powodem istnienia platform takich jak Java była przenośność, ale... serio, kto używał Javy dla przenośności? Przyjęła się głównie na serwerach, a tam przenośność nie ma żadnego znaczenia bo aplikacje buduje się pod jedną konkretną platformę.

0

Drugim dużym powodem istnienia platform takich jak Java była przenośność, ale... serio, kto używał Javy dla przenośności? Przyjęła się głównie na serwerach, a tam przenośność nie ma żadnego znaczenia bo aplikacje buduje się pod jedną konkretną platformę.

Chyba każda korpo udowadnia, że przenośność jest ważna, bo aplikacje biznesowe klepie się na Windowsach, a uruchamia docelowo na Linuksach.

Nie. Raczej spodziewam się, że takie rzeczy jak borrow checker będą trafiały do innych języków. Dla C++ już są lintery które sprawdzają czas życia obiektów i referencji.

Tylko do jakich? Borrow checker w Ruście to w zasadzie formalizacja mechanik z C++a (nawet się trochę przyznali tutaj: https://doc.rust-lang.org/reference/influences.html ), więc dość naturalne, że C++ ma do tego lintery. Względnie mało języków w ogóle polega na czymś takim jak destruktor.

Poza tym myślę, że reactive streams są lepszym rozwiązaniem dla aplikacji biznesowych niż borrow-checker. Borrow-checker może rozwiązywać w pewnym stopniu problem cyklu życia zasobów, ale implementacje reactive streams dodatkowo rozwiązują problem ograniczania zużycia zasobów w danym momencie (by nie wyjść poza limit).

2
Krolik napisał(a):

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).

I to jest cały kod biznesowy? Nic więcej nie ma i się nie liczy? Nic więcej znaczenia mieć nie będzie, tylko to, że np. w Scali ten kod będzie wolniejszy niż w Rust, przy pewnych założeniach (dajmy na to, co napisałes)?

I to, jak rozumiem, uderzy w biznes, nie jakiś tam zły marketing (takie "nic" w sumie), nie jakiś tam brak zapotrzebowania na dany software, bo ktoś przestrzelił z pomysłem, nie jakiś tam mały problem typu brak dostępnej bibliotek(i) na teraz, brak dostępnych developerów Rusta (no bo dużo ich!), nie jakies opóźnienia np. na styku z bazą danych (wszakże np. redisy to zbędne wynalazki), tylko taki właśnie mikrobenchmark zadecyduje o być albo nie być, oprogramowania. Zwłaszcza w czasach, kiedy są mikroserwisy, loadbalancery, cache i niech i super ślamazarny soft był w pythonie, co obrabia, nie wiem, w pojedynkę 1 request na sekundę, to jak się postawi dobrze infrastrukture to na "zewnątrz" będzie to wyglądać np. na 1000req/s

Chyba, że jak firmy bedą iść w Rusta, to ich revenue będzie też szedł o rząd wielkości wyżej - jeśli tak jest, to nie martw się, Rust błyskawicznie zdobędzie popularność. Wystarczy, że 1 firma przepisze core na Rusta i poda ile jej to wygenerowało ekstra $ (obojętnie czy oszczędzając sprzęt czy obsługując "więcej")

Także tylko zaznaczam, że patrzenie po wspaniałości języka, po jakimś mikrobenchmarku jest słabe. Kontekst jest ważny. Dla mnie EOT.

4

Wyrywasz mój argument z kontekstu a potem dyskutujesz z czymś czego nie napisałem. Typowy chochoł (straw-man).

Mój kod miał tylko ilustrować, że można mieć wysoką wydajność kodu bez płacenia za to czasem pisania oprogramowania. Te dwie rzeczy są od siebie niezależne. Aby nie było łatwo, wybrałem do porównania język, który uchodzi za jeden z najbardziej ekspresywnych. Mogłem porównać z Javą, ale wtedy by się okazało, że kod Javy jest nie tylko powolniejszy, ale i bardziej skomplikowany.

Argument z brakującą biblioteka jest chybiony, bo brakujące biblioteki są w każdym języku, a z kolei żaden nowy język nie opiera się na bibliotekach tworzonych od zera. Rust ma banalny dostęp do całego ekosystemu C i łatwą integrację z C++. Z punktu widzenia desktopu jest to duży i dojrzały ekosystem. Zwłaszcza że np. inny popularny język, Python, polega na libkach w C++, więc wszystko co masz w Pythonie, jest dostępne też w C++ i Rust, ale nie w Javie (niby da się, ale jest to droga przez mękę).

Natomiast czy wyższa wydajność ma znaczenie biznesowe to osobna kwestia. Znam przypadki z pierwszej ręki gdzie ma. W tej chwili bardzo popularne są rozwiązania chmurowe i mikroserwisy. Pewna znajoma firma robi system zdalnego monitorowania serwerów, którego jedną z funkcji jest zbieranie logów. Pierwsza wersja używała Node.JS. Klienci skarżyli się, że agent zjada im ponad 500 MB. 500 MB na głupiego agenta zbierającego logi to bardzo dużo, jak w chmurze masz instancje z 2 GB na cały system + biznes. Firma przepisała agenta na Rust i zużycie RAMu spadło do 20 MB.

brak dostępnych developerów Rusta (no bo dużo ich!

To jest problem który dotyczy tylko firm klepiących studentami dużo softu niskiej jakości na masę. Ale jeżeli podniesiesz poprzeczkę jakościową (np. wymagasz, aby system webowy otwierał strony w mniej niż 0,1s a nie jak JIRA w 5s), albo robisz oprogramowanie infrastrukturalne (np. system baz danych) czy złożone oprogramowanie desktopowe (np. CAD) to nic Ci po tabunach tanich programistów Pythona, Javy i PHP. Ich ilość tylko utrudnia rekrutację. Liczy się ile jest dobrych programistów.

nie jakiś tam brak zapotrzebowania na dany software, bo ktoś przestrzelił z pomysłem

To jest niezależne od użytej technologii. Równie dobrze możesz przestrzelić używając JS, Javy i Pythona.

Zwłaszcza w czasach, kiedy są mikroserwisy, loadbalancery, cache i niech i super ślamazarny soft był w pythonie, co obrabia, nie wiem, w pojedynkę 1 request na sekundę, to jak się postawi dobrze infrastrukture to na "zewnątrz" będzie to wyglądać np. na 1000req/s

Tak, zaoszczędzić 100k jednorazowo na programiście aby wydać 100k miesięcznie więcej na infrastrukturę. Seems legit.

Te serwery cache i bony na ich utrzymanie to rozumiem rozdają podczas świątecznej promocji w Biedronce za 24,99?

Ciekawe dlaczego Google tak nie robi, tylko woli płacić programistom 40k / miesiąc aby pisali w C++? Mimo, że mieli nawet twórcę Pythona u siebie...

2

Mój kod miał tylko ilustrować, że można mieć wysoką wydajność kodu bez płacenia za to czasem pisania oprogramowania. Te dwie rzeczy są od siebie niezależne. Aby nie było łatwo, wybrałem do porównania język, który uchodzi za jeden z najbardziej ekspresywnych. Mogłem porównać z Javą, ale wtedy by się okazało, że kod Javy jest nie tylko powolniejszy, ale i bardziej skomplikowany.

Mini programiki jednak niewiele pokazują, bo wymagania są mocno abstrakcyjne. Ja mógłbym to zapisać w Javie tak:

        IntStream stream = IntStream.of(0, 1, -59, 2, 4);
        System.out.println("Sum of positive elements: " + stream.sum());

Działa :)

Dobrym benchmarkiem jest np http://sortbenchmark.org/ - tam nie ma nic na temat szczegółów implementacyjnych w wymaganiach.

W tej chwili bardzo popularne są rozwiązania chmurowe i mikroserwisy. Pewna znajoma firma robi system zdalnego monitorowania serwerów, którego jedną z funkcji jest zbieranie logów. Pierwsza wersja używała Node.JS. Klienci skarżyli się, że agent zjada im ponad 500 MB. 500 MB na głupiego agenta zbierającego logi to bardzo dużo, jak w chmurze masz instancje z 2 GB na cały system + biznes. Firma przepisała agenta na Rust i zużycie RAMu spadło do 20 MB.

Javkę też można odchudzić: https://quarkus.io/#container-first

Ale jeżeli podniesiesz poprzeczkę jakościową (np. wymagasz, aby system webowy otwierał strony w mniej niż 0,1s a nie jak JIRA w 5s),

JIRA zamula, ale raczej przez bazę, a nie przez Javę.

1

Javkę też można odchudzić: https://quarkus.io/#container-first

No super, jest jeden framework zoptymalizowany na wydajność.

Nie wiem jakim sprzętem testowali, ale patrząc na ich benchmark, to im wyszło że:

  • najbardziej zoptymalizowana natywna wersja REST hello potrzebuje po starcie 12 MB RSS
  • przy 16 wątkach osiąga zawrotne 40k req/s i jest 2x wolniejsza od wersji na JVM, ale niestety wersja na JVM za to wcina 100+ MB.

Teraz porównajmy sobie prosty REST hello na Actix-Web przy 16 wątkach na 3-letnim laptopie:

  • RSS po starcie: 3,8 MB
  • RSS w czasie benchmarku: 6.8 MB
  • throughput: 160k req/s
  • sredni czas odpowiedzi: 0.1 ms
  • czas odpowiedzi 99 percentyl: < 1 ms (program do testów nie podaje lepszej rozdzielczości)

Niestety nie opublikowali najważniejszego parametru, czyli opóźnień - jestem ciekaw jak ich natywne GC sobie radzi, ale najlepsze GC w klasie JVM na razie nie schodzi poniżej 10 ms ;)

1

Jak sobie popatrzę na hello worldy na https://www.techempower.com/benchmarks/ to Javie wcale nie brakuje 5x do Rusta.

Zresztą w benchmarkach Quarkusa wychodzi im max ponad 120k req/s na starym Xeonie:
https://github.com/johnaohara/quarkus-iothread-workerpool/tree/1.3.1.Final

2

Zresztą w benchmarkach Quarkusa wychodzi im max ponad 120k req/s na starym Xeonie

Porównujesz serwerowy Xeon z procesorem laptopowym z TDP 45W? Serio?
Do tego te 120k to jest przy zużyciu pamięci 100+ MB.
Moje 160k na laptopie jest przy zużyciu < 7 MB.

Nadal to jest miażdżąca przewaga kodu kompilowanego statycznie w szybkim języku (to czy to jest Rust, nie ma dużego znaczenia, bo w C++ byłoby tak samo).

I teraz wracając do tematu na serwerze oczywiście te 100 MB vs 7 MB może nie mieć znaczenia, ale już na desktopie lub na smartfonie taka różnica będzie decydowała o tym, ile system uśpi Ci aplikacji w tle. Ubijanie aplikacji w tle jest dość poważnym problemem Androida (zarazem napędzającym sprzedaż telefonów z 6 GB RAM i więcej).

Nawet jeśli Javę da się jakoś odchudzić, to naprawdę - nie widzę powodu po co tak się męczyć? Po co projektować trudny do optymalizacji język i potem walczyć przez 30 lat aby zrobić jako-tako wydajny runtime / system kompilacji do niego, kiedy można po prostu pisać w C++ x20 / Rust / Swift i mieć porządną wydajność i pełną kontrolę od początku bez kombinowania? W latach 90-tych i pierwszej dekadzie 2000 miało to sens, bo segfaulty w C++ z klasami, a Swifta/Rusta nie było. Teraz mamy C++x20 + lintery, Rust, Swift (oba bezpieczne i kompilowane statycznie) i takie kombinowanie dla mnie nie ma sensu.

1

Podaj architekturę procka i średnie taktowanie podczas testu.

Nawet jeśli Javę da się jakoś odchudzić, to naprawdę - nie widzę powodu po co tak się męczyć? Po co projektować trudny do optymalizacji język i potem walczyć przez 30 lat aby zrobić jako-tako wydajny runtime / system kompilacji do niego, kiedy można po prostu pisać w C++ x20 / Rust / Swift i mieć porządną wydajność i pełną kontrolę od początku bez kombinowania? W latach 90-tych i pierwszej dekadzie 2000 miało to sens, bo segfaulty w C++ z klasami, a Swifta/Rusta nie było. Teraz mamy C++x20 + lintery, Rust, Swift (oba bezpieczne i kompilowane statycznie) i takie kombinowanie dla mnie nie ma sensu.

Masę energii i pieniedzy zainwestowano zarówno w Javę jak i w GCC, LLVM, itp itd Inwestowanie w Javę ma sens, bo jest masa programistów Javy, masa kodu Javowego, więc ulepszenia w Javie wpływają na dużą część rynku. Rust dalej jest w fazie "inflated expectations" z https://en.wikipedia.org/wiki/Hype_cycle i nie wiadomo w jakim stopniu zawojuje rynek i na ile opłaca się w niego inwestować.

1

https://quarkus.io/blog/runtime-performance/

Tu podają przynajmniej czasy odpowiedzi. Masakra.
20x powolniej. To też pewnie zasługa mojego wyższego taktowania?

Niemniej, wątek jest o desktopie i wolałbym zobaczyć jakieś porównania może np. responsywności JavaFX vs GTK (w C, C++ lub Rust) vs QT. To byłoby bardziej na temat. :D
A zwłaszcza sytuacje takie jak otwierasz nowy dialog-box i on oczywiście musi zaalokować ileś pamięci na nowe kontrolki a wtedy akurat zwykle wcina się GC, więc okienko zobaczysz dopiero jak GC się skończy.

1

Częściej freezuje mi się Excel i Outlook niż IntelliJ mimo iż IntelliJa znacznie intensywniej używam. Na przycinanie GUI ma wpływ głównie nie GC, a kwiatki typu pełniaKsiężyca.await() albo przemielJakNajszybciejPięćGigaDanych() w wątku GUI, które (jeśli już muszą być) to powinny siedzieć w osobnych wątkach.

1

Dokładnie jest jak pisze wibowit, gc javowym UI to był problem 20 lat temu. Problemem jest natomiast to, że większość programitów UI nigdy nawet podstawowej dokumentacji nie przeczytała o good practices w UI.
GC natomiast - to jest nadal problemem w grach 3d/fps. I dlatego (między innymi) raczej ich się nie robi w javie.

Jestem zszokowany, że w javowego minecrafta da się grać i to nawet w VR, gdzie każda przycinka to potencjalna migrena / wymioty dla gracza.
Aczkolwiek sądze, że ludzie to tunujący do wersi używalnej gdzieś w pewnym momencie pomyśleli, że być może gdyby nie mieli gc byłoby w sumie mniej pracy....

1

GC nadal jest problemem. Wyjdziesz na swap i pozamiatane. Natomiast aplikacja natywna po wyjściu na swap działa nadal przyzwoicie, bo zwykle nie ma potrzeby przeglądania całej sterty na raz jak to robi GC. OS nie wie o tym, że aplikacja używa GC i może sobie dowolnie ją wymieść na swap, nawet jeśli pamięci jest wystarczająco dużo.

Dlatego Cassandra na początku woła mlockall, z dobrym efektem dla siebie ale złym dla pozostałych procesów.

Często opłaca się wywalić na swap rzadko używaną pamięć po to aby mieć jej więcej na obiekty często u używane.

2
Krolik napisał(a):

GC nadal jest problemem. Wyjdziesz na swap i pozamiatane. Natomiast aplikacja natywna po wyjściu na swap działa nadal przyzwoicie, bo zwykle nie ma potrzeby przeglądania całej sterty na raz jak to robi GC.

Hm, czy Go jest według Ciebie językiem natywnym?

1
Krolik napisał(a):

GC nadal jest problemem. Wyjdziesz na swap i pozamiatane. Natomiast aplikacja natywna po wyjściu na swap działa nadal przyzwoicie, bo zwykle nie ma potrzeby przeglądania całej sterty na raz jak to robi GC. OS nie wie o tym, że aplikacja używa GC i może sobie dowolnie ją wymieść na swap, nawet jeśli pamięci jest wystarczająco dużo.

Captain obvious przypomina: GC zwykle nie musi przeglądać całej sterty. Od algorytmu GC zależy jak często, ale przeglądanie całej sterty to jest zwykle opcja ostateczna i praktycznie każde GC stara sie to minimalizować (różnie to oczywiście wychodzi - zależy to od programu i od GC).
Poza tym czasy, że obiekty rzeczywiście lądowały na stercie powoli się kończą - z każdym nowszym wcieleniem graala jednak więcej ląduje na stosie (escape analysis) i w stertę trafia to co faktycznie żyje trochę dłużej. (ten feature akurat ostatnio testowałem).

3

Uniknąć przeglądania całej sterty się nie da.
Można to tylko opóźnić. Opóźnianie polega zwykle na przydzieleniu bardzo dużo pamieci dla młodej generacji, na tyle dużo, aby większość obiektów umarło zanim zostaną wypromowane. Drugim sposobem jest oczywiście odpowiednio większa cała sterta. Teraz już wiecie dlaczego aplikacje zarządzane potrzebują 10x więcej RAMu niż natywne.

Ale tak czy inaczej przeglądanie całej sterty zawsze nastąpi. I jeśli jej część została wyrzucona na swap to będzie wyraźna zawieszka.

2

Z powodu jak powyżej powstał Shanondoah GC, który nie jest generacyjny, a wydajne duże cache to jeden z obsługiwanych przypadków.
Chyba też ZGC ma podobne właściwości (ale ten znowu zupełnie zapomniałem jak działa :/ )
Oczywistą wadą jest to, że aplikacja musi się zdecydować na jeden gc, więc aplikacja, która przez 2 godziny jest 100 Gb cache, a przez następne 2 godziny będzie przetwarzać jakieś batche (i potencjalnie będzię generational friendly) to nie jest idealny dla JVM przypadek.

Zresztą ta globalność i duża ilość opcji JVM to akurat jest problem. W benchmarku zwykle da się przytunować nieźle (choć czasem w nieoczywisty sposób :/). Tunowanie jvm (szczególnie gc) w rzeczywistej, zmieniającej się aplikacji to czarna magia (na szczęście 99% aplikacji ten problem nie dotyczy).

Zresztą jeśli chodzi o SWAPowanie (gdzieś były komentarze jak to GC przeszkadza...) to akurat posiadanie GC jest często dość dobrym pomysłem - w szczególności kompaktującego GC!

1

Zarówno Shenandoah, ZGC jak i G1 muszą przeglądać całą stertę.

Innowacja nie polega na braku przeglądania całej sterty a na braku konieczności defragmentacji całej sterty. Zamiast tego one defragmentują po kawałku, regionami.

Jeżeli nigdy byś nie przeglądał całej sterty, to nie zidentyfikowałbyś które obiekty są używane, a które nie.

Jak już jesteśmy przy GC o niskich opóźnieniach to należy wspomnieć, że te GC mają 2-3x niższą przepustowość niż klasyczne blokujące GC, a mimo to nie dają żadnych gwarancji na pauzy.

Nasi klienci używają G1 na produkcji i zwykle wszystko jest ok dopóki obciążenie jest małe. Przychodzi czarny piątek, obciążenie rośnie, GC przestaje wyrabiać i nagle się pojawiają pauzy, bo nagle okazuje się że szybkość sprzątania jest mniejsza niż szybkość generowania nowych śmieci.

Zresztą jeśli chodzi o SWAPowanie (gdzieś były komentarze jak to GC przeszkadza...) to akurat posiadanie GC jest często dość dobrym pomysłem - w szczególności kompaktującego GC!

Jest bardzo złym pomysłem, bo nie masz żadnego wpływu na to jak GC ułoży obiekty w pamięci. W C++ moge zrobić osobne sterty i mam gwarancję że nic się nie pomiesza.

1
Krolik napisał(a):

Zarówno Shenandoah, ZGC jak i G1 muszą przeglądać całą stertę.

Innowacja nie polega na braku przeglądania całej sterty a na braku konieczności defragmentacji całej sterty. Zamiast tego one defragmentują po kawałku, regionami.

Tu masz rację zapędziłem się.

Jeżeli nigdy byś nie przeglądał całej sterty, to nie zidentyfikowałbyś które obiekty są używane, a które nie.

W teorii nie trzeba. GC w GHC tego nie robi. Nie trzeba łazić po "niezmienionych starych" przy wywalaniu nowych. Generacyjny kolektor, który potrafi odróżnić obiekty niemutowalne (np, dzięki write barrier ) mógłby z tego skorzystać. Ale prawda jest taka, że nie znalazłem, żeby któryś javowy to naprawdę robił (są "legendy", ale nie znalazłem konkretów).

Ogólnie - jesteśmy przy desktopie C++, a dyskutujemy GC w JVM i to coraz bardziej niestandardowe przypadki, gdzie GC może bardziej przeszkadzać niż pomagać. Tak są takie przypadki - nawet spotkałem w swoim życiu.
Tylko, że to naprawdę bardzo skromny procent aplikacji i prawie w ogole problem nie dotyczy desktopa. Najbliżej desktopu to w grach fps jest problem z GC. A Java ma nawet większy i nie dlatego, że jest sama w sobie wolna, ale dlatego że korzysta głównie ze starego opengl (przenośność) i to jeszcze poprzez JNI :-(

W normalnym desktopie, ani jvm ani GC nie są żadnym problemem.
Dawno temu jvm miał problem z powolnym wstawaniem więc zrobienie czegoś prostego w stylu notatnika w javie to był absurdalnie nieżyciowy pomysł (seej Edit). Ale te czasy już minęły.

5

Jeśli komuś nie chce się czytać ośmiu stron dyskusji to podaję streszczenie:
"Jak dzielnie Java walczy z problemami które sama sobie stworzyła"

2
tajny_agent napisał(a):

Jeśli komuś nie chce się czytać ośmiu stron dyskusji to podaję streszczenie:
"Jak dzielnie Java walczy z problemami które sama sobie stworzyła"

Nie. Java to tylko przykład. Tak naprawdę dyskusja to Rust/ C++ vs reszta świata. Reszta świata ma te same niby problemy (?) co Java czyli brak destruktorów odpalających się tuż po wyjściu obiektu poza zasięg i/ lub brak w pełni działającej kompilacji AOT. Przykłady języków z tymi problemami to: C, Java, C#, Python, JavaScript, PHP, Go, Haskell, itp itd

1

@Wibowit - Swift jest ciekawy, bo bazuje na reference counting - jak dla mnie bardzo nietypowe posunięcie, dziwne. Myślałem że wiadomo, że to nie działa :-) ale z drugiej strony dobrze, że ktoś robi eksperymenty.

2
Wibowit napisał(a):

Nie. Java to tylko przykład. Tak naprawdę dyskusja to Rust/ C++ vs reszta świata.

To co się rzuca w oczy w każdym kolejnym poście to Java/JVM/GraalVM/GC/etc. Co to ma wspólnego z C++ i czy tak wygląda dyskusja C++/Rust vs reszta świata? No wątpię ;)

Zarzut: GC zamula! - ojej, ale mamy już fyfnastą implementację i ona jest super!
Zarzut: apka wolno startuje - ale stworzyliśmy native-image i kompiluje się do natywnego kodu, działa szybko!
Zarzut: wszystko ląduje na stercie - ale GraalVM robi czary-mary i przerzuca co trzeba na stos!

Przyznam szczerze, że trochę nie rozumiem takiego podejścia. Zamiast bronić Java-style to na siłę próbujesz/próbujecie udowodnić, że już chwilka, momencik i będziemy jak C++.
Po co?

1
tajny_agent napisał(a):
Wibowit napisał(a):

Nie. Java to tylko przykład. Tak naprawdę dyskusja to Rust/ C++ vs reszta świata.

To co się rzuca w oczy w każdym kolejnym poście to Java/JVM/GraalVM/GC/etc. Co to ma wspólnego z C++ i czy tak wygląda dyskusja C++/Rust vs reszta świata? No wątpię ;)

Zarzut: GC zamula! - ojej, ale mamy już fyfnastą implementację i ona jest super!
Zarzut: apka wolno startuje - ale stworzyliśmy native-image i kompiluje się do natywnego kodu, działa szybko!
Zarzut: wszystko ląduje na stercie - ale GraalVM robi czary-mary i przerzuca co trzeba na stos!

Przyznam szczerze, że trochę nie rozumiem takiego podejścia. Zamiast bronić Java-style to na siłę próbujesz/próbujecie udowodnić, że już chwilka, momencik i będziemy jak C++.
Po co?

Żeby nie mieć wycieków pamięci i nie musieć się uczyć o wiele bardziej skomplikowanej składni języka C++

1

@tajny_agent:
Czym jest Java-style, żeby tego bronić i po co?

Jakbyś się wczytał to byś zauważył, że jedynym argumentem Krolika który się ostał jest kwestia deterministycznych destruktorów. Są one tylko w C++, Ruście + może jakichś mniej znanych językach, natomiast reszta świata tego nie ma. Krolik ma nadzieję, że:

Raczej spodziewam się, że takie rzeczy jak borrow checker będą trafiały do innych języków.

Borrow checker jest tylko w Ruście i linterach do C++. Jaka jest szansa, że borrow checker wejdzie do innych języków (tych, ktore nie polegają teraz na destruktorach) i będzie miał te same zalety co w Ruście? Dalej to nie wygląda na wojnę Rust/ C++ vs reszta świata? Zamiast Javy można podstawić Haskella, Go, JavaScript, itd i zarzuty będą te same.

2
tajny_agent napisał(a):

To co się rzuca w oczy w każdym kolejnym poście to Java/JVM/GraalVM/GC/etc.

Każda dostatecznie długa dyskusja zejdzie albo na Java albo na Linux.

2

Zrobiłem benchmarki malloc vs GC Javowe bazując na najszybszych programach z https://benchmarksgame-team.pages.debian.net/benchmarksgame/performance/binarytrees.html które nie stosują pul obiektów (zmodyfikowałem kod Javowy by wypisywał rodzaje GC):

:/tmp/binary-trees$ ~/devel/jdk-14.0.1+7/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14.0.1+7, mixed mode, sharing)
:/tmp/binary-trees$ ~/devel/jdk-15/bin/java --version
openjdk 15-ea 2020-09-15
OpenJDK Runtime Environment (build 15-ea+20-899)
OpenJDK 64-Bit Server VM (build 15-ea+20-899, mixed mode, sharing)
:/tmp/binary-trees$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
:/tmp/binary-trees$ g++ --version
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
:/tmp/binary-trees$ /usr/bin/gcc -pipe -Wall -O3 -fomit-frame-pointer -march=core2 -pthread c-gcc-5.c -o c-gcc-5.run
:/tmp/binary-trees$ /usr/bin/g++ -c -pipe -O3 -fomit-frame-pointer -march=core2 cpp-gpp-2.cpp -o cpp-gpp-2.o
:/tmp/binary-trees$ /usr/bin/g++ cpp-gpp-2.o -o cpp-gpp-2.run
:/tmp/binary-trees$ javac -d . binarytrees.java

:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ./c-gcc-5.run 21
	Elapsed (wall clock) time: 0:05.90
	Maximum resident set size (kbytes): 351424
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ./cpp-gpp-2.run 21
	Elapsed (wall clock) time: 0:12.08
	Maximum resident set size (kbytes): 265664

:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-15/bin/java -Xmx500M -XX:+UseG1GC binarytrees 21
G1 Young Generation
[G1 Eden Space, G1 Survivor Space, G1 Old Gen]
G1 Old Generation
[G1 Eden Space, G1 Survivor Space, G1 Old Gen]
	Elapsed (wall clock) time: 0:03.36
	Maximum resident set size (kbytes): 578732
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-15/bin/java -Xmx500M -XX:+UseParallelGC binarytrees 21
PS MarkSweep
[PS Eden Space, PS Survivor Space, PS Old Gen]
PS Scavenge
[PS Eden Space, PS Survivor Space]
	Elapsed (wall clock) time: 0:03.56
	Maximum resident set size (kbytes): 535872
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-15/bin/java -Xmx500M -XX:+UseZGC binarytrees 21
ZGC
[ZHeap]
	Elapsed (wall clock) time: 0:13.06
	Maximum resident set size (kbytes): 1528744
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-14.0.1+7/bin/java -Xmx500M -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC binarytrees 21
Shenandoah Pauses
[Shenandoah]
Shenandoah Cycles
[Shenandoah]
	Elapsed (wall clock) time: 0:12.42
	Maximum resident set size (kbytes): 467780

:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-15/bin/java -Xmx300M -XX:+UseParallelGC binarytrees 21
PS MarkSweep
[PS Eden Space, PS Survivor Space, PS Old Gen]
PS Scavenge
[PS Eden Space, PS Survivor Space]
	Elapsed (wall clock) time: 0:06.03
	Maximum resident set size (kbytes): 352524
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-15/bin/java -Xmx300M -XX:+UseG1GC binarytrees 21
G1 Young Generation
[G1 Eden Space, G1 Survivor Space, G1 Old Gen]
G1 Old Generation
[G1 Eden Space, G1 Survivor Space, G1 Old Gen]
	Elapsed (wall clock) time: 0:06.05
	Maximum resident set size (kbytes): 367740
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-15/bin/java -Xmx300M -XX:+UseZGC binarytrees 21
ZGC
[ZHeap]
	Elapsed (wall clock) time: 0:21.84
	Maximum resident set size (kbytes): 934152
:/tmp/binary-trees$ /usr/bin/time -f "\tElapsed (wall clock) time: %E\n\tMaximum resident set size (kbytes): %M" ~/devel/jdk-14.0.1+7/bin/java -Xmx300M -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC binarytrees 21
Shenandoah Pauses
[Shenandoah]
Shenandoah Cycles
[Shenandoah]
	Elapsed (wall clock) time: 0:25.32
	Maximum resident set size (kbytes): 333436

Wartości zajętości pamięci dla ZGC są przekłamane, bo ZGC wielokrotnie mapuje te same obszary pamięci, a kalkulatory zajętości pamięci tego nie rozpoznają. Jak widać zarówno czasy jak i zajętość pamięci są konkurencyjne dla C i C++.

0

Czyli wykazałeś to co pisałem nas początku, że alokacja Javy jest co najwyżej tak samo wydajna jak malloc, przy założeniu że mamy duży nadmiar wolnej sterty. Przy czym nadal masz kilka problemów z tym benchmarkiem:

  1. porównywałeś na domyślnym alokatorze z glibc. A można było użyć jemalloc albo tcmalloc, które uchodzą za szybsze
  2. JVM oszczędza dużo czasu na prealokowaniu pamięci z systemu przy starcie. Alokatory natywne natomiast żądają kolejnych stron w miarę rosnącego zużycia. Jeśli ten benchmark jedyne co robi to jednorazowo alokuje dużą liczbę obiektów, to oczywiste jest, że domapowywanie kolejnych stron przyrostowo będzie mieć większy narzut. JVM zna docelową ilość pamięci, programy w C++ nie.
  3. Testujesz jeden bardzo specyficzny schemat alokacji. Trudno ekstrapolować to na inne przypadki.
  4. GC Javy w ogóle się wyłączył choć raz? ;)

Natomiast takie porównanie jest oczywiście i tak bez sensu, bo programy w C++ / Rust alokują na stercie znacznie mniej niż JVM, więc nawet jeśli malloc/new byłoby 2x wolniejsze w C++ to i tak C++ ma dużą przewagę. W C++ można pisać programy całkowicie bez użycia sterty. Tam gdzie alokacja zajmuje za dużo czasu, to się robi pulę / arenę. Tak jak w benchmarkach, które celowo wyrzuciłeś.

No i Shenandoah i ZGC jednak przegrały ten benchmark pod względem zajętości pamięci i czasu wykonania.

1

@Krolik:

Nawet jeśli Javę da się jakoś odchudzić, to naprawdę - nie widzę powodu po co tak się męczyć? Po co projektować trudny do optymalizacji język i potem walczyć przez 30 lat aby zrobić jako-tako wydajny runtime / system kompilacji do niego, kiedy można po prostu pisać w...

honestly? gdybyś tutaj nie wstawił javy, to bym pomyślał że piszesz o C++

dekady puchnięcia języka którego parsowanie to wyzwanie samo w sobie i dziesiątki MILIONÓW linii kodu kompilatora mielącego nieprawdopodobnie długo (z mojej perspektywy) i próbowanie obsługiwać coraz więcej możliwych rzeczy do policzenia podczas kompilacji.

W VSCode wpisywanie literek potrafi lagować!

to ja zapytam, a jaki kod trzeba było parsnąć pod spodem gdy wpisywałeś te literki? ;)

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