Wydajność C ++ a Java / C #


119

Rozumiem, że C / C ++ tworzy natywny kod do uruchomienia na określonej architekturze maszyny. I odwrotnie, języki takie jak Java i C # działają na maszynie wirtualnej, która oddziela architekturę natywną. Logicznie wydaje się niemożliwe, aby Java lub C # dorównywała szybkością C ++ z powodu tego pośredniego kroku, jednak powiedziano mi, że najnowsze kompilatory („hot spot”) mogą osiągnąć tę prędkość lub nawet ją przekroczyć.

Być może jest to bardziej pytanie kompilatora niż pytanie o język, ale czy ktoś może wyjaśnić prostym językiem angielskim, w jaki sposób jeden z tych języków maszyn wirtualnych może działać lepiej niż język ojczysty?


Java i C # mogą dokonywać optymalizacji w oparciu o faktyczne działanie aplikacji przy użyciu kodu, który jest dostępny w czasie wykonywania. np. może wbudować kod we współdzielonej bibliotece, który może faktycznie zmieniać się podczas działania programu i nadal być poprawny.
Peter Lawrey

Niektóre rzeczywiste pomiary do sprawdzenia przed przeczytaniem wielu bardzo błędnych teorii w tych odpowiedziach: shootout.alioth.debian.org/u32/ ...
Justicle

Odpowiedzi:


178

Ogólnie rzecz biorąc, C # i Java mogą być tak samo szybkie lub szybsze, ponieważ kompilator JIT - kompilator, który kompiluje twój IL przy pierwszym uruchomieniu - może dokonywać optymalizacji, których nie może skompilowany program C ++, ponieważ może wysyłać zapytania do maszyny. Potrafi określić, czy maszyna to Intel czy AMD; Pentium 4, Core Solo lub Core Duo; lub jeśli obsługuje SSE4 itp.

Program w C ++ musi być wcześniej skompilowany, zwykle z mieszanymi optymalizacjami, aby działał przyzwoicie na wszystkich maszynach, ale nie jest zoptymalizowany tak bardzo, jak mógłby być dla pojedynczej konfiguracji (tj. Procesora, zestawu instrukcji, innego sprzętu).

Ponadto niektóre funkcje językowe pozwalają kompilatorowi w C # i Javie przyjmować założenia dotyczące kodu, które pozwalają mu na optymalizację pewnych części, które po prostu nie są bezpieczne dla kompilatora C / C ++. Gdy masz dostęp do wskaźników, istnieje wiele optymalizacji, które po prostu nie są bezpieczne.

Również Java i C # mogą wykonywać alokacje sterty bardziej wydajnie niż C ++, ponieważ warstwa abstrakcji między modułem odśmiecania pamięci a kodem pozwala na jednoczesne wykonywanie całej kompresji sterty (dość kosztowna operacja).

Teraz nie mogę mówić za Javą w następnym punkcie, ale wiem, że na przykład C # faktycznie usunie metody i wywołania metod, gdy wie, że treść metody jest pusta. I będzie używać tego rodzaju logiki w całym kodzie.

Jak widać, istnieje wiele powodów, dla których niektóre implementacje C # lub Java będą szybsze.

Teraz to wszystko powiedziawszy, w C ++ można wprowadzić określone optymalizacje, które zniweczą wszystko, co można zrobić z C #, szczególnie w dziedzinie grafiki i zawsze, gdy jesteś blisko sprzętu. Wskaźniki czynią tutaj cuda.

Więc w zależności od tego, co piszesz, wybrałbym jedną lub drugą. Ale jeśli piszesz coś, co nie jest zależne od sprzętu (sterownik, gra wideo itp.), Nie martwiłbym się o wydajność C # (znowu nie mogę mówić o Javie). Wystarczy.

Po stronie Java @Swati wskazuje dobry artykuł:

https://www.ibm.com/developerworks/library/j-jtp09275


Twoje rozumowanie jest fałszywe - programy C ++ są budowane pod kątem docelowej architektury i nie muszą się zmieniać w czasie wykonywania.
Justicle,

3
@Justicle Najlepsze, co Twój kompilator C ++ zaoferuje dla różnych architektur, to zazwyczaj x86, x64, ARM i tak dalej. Teraz możesz powiedzieć mu, aby używał określonych funkcji (powiedzmy SSE2), a jeśli masz szczęście, wygeneruje nawet kod zapasowy, jeśli ta funkcja nie jest dostępna, ale jest to tak precyzyjne, jak tylko można. Z pewnością nie ma specjalizacji w zależności od rozmiaru pamięci podręcznej i tak dalej.
Voo

4
Zobacz shootout.alioth.debian.org/u32/…, aby zapoznać się z przykładami, w których ta teoria nie ma miejsca .
Justicle,

1
Szczerze mówiąc, to jedna z najgorszych odpowiedzi. To jest tak bezpodstawne, że mógłbym to po prostu odwrócić. Za dużo uogólnień, za dużo niewiedzy (optymalizacja pustych funkcji to tak naprawdę tylko wierzchołek góry lodowej). Jeden z luksusowych kompilatorów C ++ ma: Czas. Kolejny luksus: brak kontroli. Ale więcej znajdziesz na stackoverflow.com/questions/145110/c-performance-vs-java-c/… .
Sebastian Mach

1
@OrionAdrian ok, jesteśmy teraz w pełnym kręgu ... Zobacz shootout.alioth.debian.org/u32/…, aby zobaczyć przykłady, że ta teoria nie ma miejsca. Innymi słowy, pokazują nam, że teoria może być udowodnione poprawne przed dokonaniem niejasne oświadczenia spekulacyjnych.
Justicle

197

JIT vs. kompilator statyczny

Jak już powiedziano w poprzednich postach, JIT może kompilować IL / kod bajtowy do kodu natywnego w czasie wykonywania. Wspomniano o koszcie, ale nie do końca:

JIT ma jeden poważny problem polega na tym, że nie może skompilować wszystkiego: kompilacja JIT wymaga czasu, więc JIT skompiluje tylko niektóre części kodu, podczas gdy statyczny kompilator utworzy pełny natywny plik binarny: w przypadku niektórych programów statyczny kompilator z łatwością przewyższy JIT.

Oczywiście C # (lub Java lub VB) jest zwykle szybsze w tworzeniu realnego i niezawodnego rozwiązania niż C ++ (choćby dlatego, że C ++ ma złożoną semantykę, a standardowa biblioteka C ++, choć interesująca i potężna, jest dość słaba w porównaniu z pełną zakres biblioteki standardowej z .NET lub Java), więc zwykle różnica między C ++ a .NET lub Java JIT nie będzie widoczna dla większości użytkowników, a dla tych plików binarnych, które są krytyczne, nadal można wywołać przetwarzanie w C ++ z C # lub Java (nawet jeśli tego rodzaju natywne wywołania mogą same w sobie być dość kosztowne) ...

Metaprogramowanie w C ++

Zauważ, że zazwyczaj porównujesz kod środowiska uruchomieniowego C ++ z jego odpowiednikiem w C # lub Javie. Ale C ++ ma jedną cechę, która może przewyższać Java / C # po wyjęciu z pudełka, to jest metaprogramowanie szablonów: Przetwarzanie kodu będzie wykonywane w czasie kompilacji (w ten sposób znacznie wydłużając czas kompilacji), co skutkuje zerowym (lub prawie zerowym) uruchomieniem.

Widzę jeszcze realny wpływ na to (bawiłem się tylko koncepcjami, ale wtedy różnica wynosiła sekundy wykonania dla JIT i zero dla C ++), ale warto o tym wspomnieć, obok metaprogramowania szablonu faktów nie jest trywialny...

Edycja 2011-06-10: W C ++ gra z typami odbywa się w czasie kompilacji, co oznacza tworzenie kodu generycznego, który wywołuje kod nieogólny (np. Parser generyczny ze stringa do typu T, wywołanie standardowego API biblioteki dla typów T, które rozpoznaje, i uczynienie parsera łatwym do rozszerzania przez użytkownika) jest bardzo łatwe i bardzo wydajne, podczas gdy odpowiednik w Javie lub C # jest w najlepszym przypadku bolesny w pisaniu i zawsze będzie wolniejszy i rozwiązywany w czasie wykonywania, nawet jeśli typy są znane w czasie kompilacji, co oznacza, że ​​jedyną nadzieją jest to, że JIT włączy całość.

...

Edytuj 2011-09-20: Zespół odpowiedzialny za Blitz ++ (strona główna , Wikipedia ) poszedł w ten sposób i najwyraźniej ich celem jest osiągnięcie wydajności FORTRAN w obliczeniach naukowych, przechodząc jak najwięcej od wykonania w czasie wykonywania do czasu kompilacji, za pomocą metaprogramowania szablonu w C ++ . Więc „ mam jeszcze tak zobaczyć rzeczywisty wpływ na życie na tej ” części napisałem powyżej najwyraźniej nie istnieje w realnym życiu.

Natywne użycie pamięci w języku C ++

C ++ ma inne wykorzystanie pamięci niż Java / C #, a zatem ma inne zalety / wady.

Bez względu na optymalizację JIT, nic nie pójdzie tak szybko, jak bezpośredni dostęp wskaźnika do pamięci (zignorujmy na chwilę pamięci podręczne procesora itp.). Tak więc, jeśli masz w pamięci ciągłe dane, dostęp do nich przez wskaźniki C ++ (tj. Wskaźniki C ... dajmy Cezarowi należność) będzie przebiegał szybciej niż w Javie / C #. A C ++ ma RAII, co sprawia, że ​​wiele operacji jest o wiele łatwiejszych niż w C # czy nawet w Javie. C ++ nie musi usingograniczać istnienia swoich obiektów. A C ++ nie ma finallyklauzuli. To nie jest błąd.

:-)

I pomimo prymitywnych struktur w C #, obiekty „na stosie” w C ++ nie będą kosztować nic przy alokacji i niszczeniu i nie będą potrzebować GC do pracy w niezależnym wątku do czyszczenia.

Jeśli chodzi o fragmentację pamięci, alokatory pamięci w 2008 r. Nie są starymi alokatorami pamięci z 1980 r., Które zwykle porównuje się z alokacją GC: C ++ nie można przenieść w pamięci, to prawda, ale potem, jak w systemie plików Linuksa: kto potrzebuje dysku twardego defragmentacja, gdy fragmentacja nie występuje? Użycie odpowiedniego alokatora do odpowiedniego zadania powinno być częścią zestawu narzędzi dla programistów C ++. Teraz pisanie alokatorów nie jest łatwe, a wtedy większość z nas ma lepsze rzeczy do zrobienia, a dla większości zastosowań RAII lub GC są więcej niż wystarczająco dobre.

Edytuj 2011-10-04: Przykłady wydajnych alokatorów: Na platformach Windows od wersji Vista sterta o niskiej fragmentacji jest domyślnie włączona. W poprzednich wersjach LFH można aktywować, wywołując funkcję WinAPI HeapSetInformation ). W przypadku innych systemów operacyjnych dostępne są alternatywne podzielniki (patrzhttps://secure.wikimedia.org/wikipedia/en/wiki/Malloc, aby uzyskać listę)

Obecnie model pamięci staje się nieco bardziej skomplikowany wraz z rozwojem technologii wielordzeniowej i wielowątkowej. W tej dziedzinie myślę, że .NET ma przewagę, a Java, jak mi powiedziano, ma przewagę. Niektórym hakerom „na gołym metalu” łatwo jest pochwalić swój kod „blisko maszyny”. Ale teraz znacznie trudniej jest stworzyć lepszy zestaw ręcznie, niż pozwolić kompilatorowi wykonać swoje zadanie. W przypadku C ++ kompilator był zwykle lepszy od hakera od dekady. W przypadku języków C # i Java jest to jeszcze łatwiejsze.

Mimo to, nowy standard C ++ 0x narzuci prosty model pamięci kompilatorom C ++, który ustandaryzuje (a tym samym uprości) efektywny kod wieloprocesorowy / równoległy / wątkowy w C ++ i uczyni optymalizacje łatwiejszymi i bezpieczniejszymi dla kompilatorów. Ale wtedy zobaczymy za kilka lat, czy jej obietnice zostaną spełnione.

C ++ / CLI a C # / VB.NET

Uwaga: W tej sekcji mówię o C ++ / CLI, to znaczy C ++ hostowanym przez .NET, a nie natywnym C ++.

W zeszłym tygodniu odbyłem szkolenie z optymalizacji .NET i odkryłem, że statyczny kompilator i tak jest bardzo ważny. Równie ważne niż JIT.

Ten sam kod skompilowany w C ++ / CLI (lub jego przodku, Managed C ++) może być razy szybszy niż ten sam kod utworzony w C # (lub VB.NET, którego kompilator tworzy taki sam IL niż C #).

Ponieważ statyczny kompilator C ++ był o wiele lepszy do tworzenia już zoptymalizowanego kodu niż C #.

Na przykład wstawianie funkcji w .NET jest ograniczone do funkcji, których kod bajtowy jest mniejszy lub równy 32 bajtom. Tak więc, część kodu w C # utworzy 40-bajtowy akcesor, który nigdy nie zostanie wstawiony przez JIT. Ten sam kod w C ++ / CLI wygeneruje 20-bajtowy akcesor, który zostanie wstawiony przez JIT.

Innym przykładem są zmienne tymczasowe, które są po prostu kompilowane przez kompilator C ++, a wciąż są wspomniane w IL utworzonym przez kompilator C #. Optymalizacja kompilacji statycznej w C ++ spowoduje mniej kodu, a tym samym ponownie zezwoli na bardziej agresywną optymalizację JIT.

Spekulowano, że powodem tego jest fakt, że kompilator C ++ / CLI korzystał z ogromnych technik optymalizacji natywnych kompilatorów C ++.

Wniosek

Uwielbiam C ++.

Ale o ile to widzę, C # lub Java są w sumie lepszym rozwiązaniem. Nie dlatego, że są szybsze niż C ++, ale dlatego, że po dodaniu ich cech stają się bardziej produktywne, wymagają mniej szkoleń i mają pełniejsze standardowe biblioteki niż C ++. A tak jak w przypadku większości programów, różnice w szybkości (w ten czy inny sposób) będą znikome ...

Edycja (2011-06-06)

Moje doświadczenie w C # / .NET

Mam teraz 5 miesięcy prawie wyłącznego profesjonalnego kodowania w C # (co składa się na moje CV już pełne C ++ i Java oraz odrobinę C ++ / CLI).

Grałem z WinForms (Ahem ...) i WCF (super!) I WPF (fajnie !!!! zarówno przez XAML, jak i surowy C #. WPF jest tak łatwy, że wierzę, że Swing po prostu nie może go porównać) i C # 4.0.

Wniosek jest taki, że chociaż łatwiej / szybciej jest stworzyć kod działający w C # / Javie niż w C ++, o wiele trudniej jest stworzyć mocny, bezpieczny i niezawodny kod w C # (a jeszcze trudniej w Javie) niż w C ++. Powodów jest mnóstwo, ale można je podsumować następująco:

  1. Generics nie są tak potężne jak szablony ( spróbuj napisać wydajną ogólną metodę Parse (od ciągu do T) lub wydajny odpowiednik boost :: lexical_cast w C #, aby zrozumieć problem )
  2. RAII pozostaje niezrównany ( GC nadal może przeciekać (tak, musiałem sobie poradzić z tym problemem) i obsługuje tylko pamięć. Nawet C # usingnie jest tak łatwe i wydajne, ponieważ pisanie poprawnych implementacji Dispose jest trudne )
  3. C # readonlyi Java finalnigdzie nie są tak użyteczne jak C ++const ( nie ma sposobu, aby ujawnić złożone dane tylko do odczytu (na przykład Drzewo węzłów) w C # bez ogromnej pracy, podczas gdy jest to wbudowana funkcja C ++. Niezmienne dane to ciekawe rozwiązanie , ale nie wszystko można uczynić niezmiennymi, więc to zdecydowanie za mało ).

Tak więc C # pozostaje przyjemnym językiem, o ile chcesz czegoś, co działa, ale frustrującym językiem w momencie, gdy chcesz czegoś, co zawsze i bezpiecznie działa.

Java jest jeszcze bardziej frustrująca, ponieważ ma te same problemy co C #, a nawet więcej: z powodu braku odpowiednika usingsłowa kluczowego w C # mój bardzo utalentowany kolega spędził zbyt dużo czasu, upewniając się, że jego zasoby są poprawnie zwolnione, podczas gdy odpowiednik w C ++ było łatwe (przy użyciu destruktorów i inteligentnych wskaźników).

Wydaje mi się, że wzrost produktywności C # / Java jest widoczny w przypadku większości kodu ... aż do dnia, w którym kod powinien być tak doskonały, jak to tylko możliwe. Tego dnia poznasz ból. (nie uwierzysz, o co prosi nasz serwer i aplikacje GUI ...).

Informacje o języku Java i C ++ po stronie serwera

Utrzymywałem kontakt z zespołami serwerów (pracowałem między nimi 2 lata, zanim wróciłem do zespołu GUI), po drugiej stronie budynku i dowiedziałem się czegoś ciekawego.

W ostatnich latach trend polegał na tym, że aplikacje serwerowe Java miały zastąpić stare aplikacje serwerowe C ++, ponieważ Java ma wiele struktur / narzędzi i jest łatwa w utrzymaniu, wdrażaniu itp.

... Dopóki problem niskiej latencji nie pojawił się w ostatnich miesiącach. Następnie aplikacje serwerowe Java, niezależnie od optymalizacji podjętej przez nasz wykwalifikowany zespół Java, po prostu i czysto przegrały wyścig ze starym, niezbyt zoptymalizowanym serwerem C ++.

Obecnie podjęto decyzję o pozostawieniu serwerów Java do wspólnego użytku, gdzie wydajność, choć nadal ważna, nie jest zainteresowana celem o niskim opóźnieniu, i agresywną optymalizację i tak już szybszych aplikacji serwerowych C ++ pod kątem potrzeb o niskim opóźnieniu i bardzo niskim opóźnieniu.

Wniosek

Nic nie jest tak proste, jak oczekiwano.

Java, a nawet C #, to fajne języki, z obszernymi standardowymi bibliotekami i frameworkami, w których można szybko kodować i uzyskać wyniki bardzo szybko.

Ale kiedy potrzebujesz surowej mocy, potężnych i systematycznych optymalizacji, silnej obsługi kompilatorów, potężnych funkcji językowych i absolutnego bezpieczeństwa, Java i C # utrudniają zdobycie ostatniego brakującego, ale krytycznego procenta jakości, którego potrzebujesz, aby pozostać nad konkurencją.

To tak, jakbyś potrzebował mniej czasu i mniej doświadczonych programistów w C # / Java niż w C ++, aby stworzyć kod średniej jakości, ale z drugiej strony, w momencie, gdy potrzebujesz kodu doskonałej do doskonałej jakości, nagle uzyskanie wyników było łatwiejsze i szybsze w C ++.

Oczywiście jest to moja własna percepcja, być może ograniczona do naszych konkretnych potrzeb.

Ale nadal dzieje się tak dzisiaj, zarówno w zespołach GUI, jak i zespołach po stronie serwera.

Oczywiście zaktualizuję ten post, jeśli wydarzy się coś nowego.

Edytuj (2011-06-22)

„Okazuje się, że pod względem wydajności C ++ wygrywa z dużym marginesem. Jednak wymagało to również najbardziej rozległych wysiłków w zakresie dostrajania, z których wiele wykonano na poziomie zaawansowania, który nie byłby dostępny dla przeciętnego programisty.

[...] Wersja Java była prawdopodobnie najłatwiejsza do wdrożenia, ale najtrudniejsza do przeanalizowania pod kątem wydajności. W szczególności efekty związane ze zbieraniem elementów bezużytecznych były skomplikowane i bardzo trudne do dostrojenia ”.

Źródła:

Edytuj (2011-09-20)

„Na Facebooku mówi się, że„ rozsądnie napisany kod C ++ po prostu działa szybko ”, co podkreśla olbrzymi wysiłek włożony w optymalizację kodu PHP i Java. Paradoksalnie, kod w C ++ jest trudniejszy do napisania niż w innych językach, ale wydajny kod to dużo łatwiej [pisać w C ++ niż w innych językach]. "

- Herb Sutter z // build / , cytując Andreia Alexandrescu

Źródła:


8
Edytujesz po 5 miesiącach C # dokładnie opisuje moje własne doświadczenia (szablony lepsze, const lepiej, RAII). +1. Te trzy pozostają moimi osobistymi zabójczymi funkcjami dla C ++ (lub D, na które jeszcze nie miałem czasu).
Sebastian Mach

„Przetwarzanie kodu będzie wykonywane w czasie kompilacji”. Stąd metaprogramowanie szablonów działa tylko w programie jest dostępne w czasie kompilacji, co często nie ma miejsca, np. Niemożliwe jest napisanie konkurencyjnej biblioteki wyrażeń regularnych w waniliowym C ++, ponieważ nie jest w stanie generować kodu w czasie wykonywania (ważny aspekt metaprogramowanie).
JD

„zabawa z typami jest wykonywana w czasie kompilacji ... odpowiednik w Javie lub C # jest w najlepszym przypadku bolesny do napisania i zawsze będzie wolniejszy i rozwiązywany w czasie wykonywania, nawet jeśli typy są znane w czasie kompilacji”. W języku C # dotyczy to tylko typów odwołań i nie dotyczy typów wartości.
JD

1
„Bez względu na optymalizację JIT, nic nie pójdzie tak szybko, jak bezpośredni dostęp wskaźnika do pamięci ... jeśli masz ciągłe dane w pamięci, dostęp do nich za pośrednictwem wskaźników C ++ (tj. Wskaźników C ... dajmy Cezarowi należność) będzie przebiegał szybciej niż w Javie / C # ”. Ludzie zaobserwowali, że Java pokonuje C ++ w teście SOR z testu porównawczego SciMark2 właśnie dlatego, że wskaźniki utrudniają optymalizacje związane z aliasowaniem. blogs.oracle.com/dagastine/entry/sun_java_is_faster_than
JD,

Warto również zauważyć, że .NET specjalizuje się w typach typów ogólnych w bibliotekach łączonych dynamicznie po połączeniu, podczas gdy C ++ nie może, ponieważ szablony muszą zostać rozwiązane przed połączeniem. I oczywiście dużą przewagą typów generycznych nad szablonami są zrozumiałe komunikaty o błędach.
JD

48

Ilekroć mówię o wydajności zarządzanej i niezarządzanej, lubię wskazywać na serię, w której Rico (i Raymond) porównali wersje C ++ i C # słownika chińskiego / angielskiego. Ta wyszukiwarka Google pozwoli ci przeczytać samemu, ale podoba mi się podsumowanie Rico.

Więc czy wstydzę się mojej miażdżącej porażki? Ledwie. Zarządzany kod uzyskał bardzo dobry wynik prawie bez wysiłku. Aby pokonać zarządzanego Raymonda, musiał:

  • Napisz swój własny plik I / O
  • Napisz własną klasę ciągów
  • Napisz swój własny podzielnik
  • Napisz własne mapy międzynarodowe

Oczywiście wykorzystał do tego dostępne biblioteki niższego poziomu, ale to wciąż dużo pracy. Czy możesz nazwać to, co zostało, programem STL? Nie sądzę, myślę, że zachował klasę std :: vector, która ostatecznie nigdy nie była problemem i zachował funkcję find. Prawie wszystko inne przepadło.

Więc tak, zdecydowanie możesz pokonać CLR. Myślę, że Raymond może przyspieszyć swój program.

Co ciekawe, czas potrzebny na przeanalizowanie pliku podany przez wewnętrzne zegary obu programów jest mniej więcej taki sam - 30 ms dla każdego. Różnica dotyczy kosztów ogólnych.

Dla mnie najważniejsze jest to, że wersja niezarządzana wymagała 6 poprawek, aby pokonać wersję zarządzaną, która była prostym portem oryginalnego niezarządzanego kodu. Jeśli potrzebujesz wszystkiego do ostatniego kawałka wydajności (i masz czas i doświadczenie, aby to osiągnąć), będziesz musiał przejść bez zarządzania, ale dla mnie skorzystam z przewagi rzędu wielkości, jaką mam w pierwszych wersjach nad 33. % Zyskuję, jeśli spróbuję 6 razy.


3
link jest martwy, znaleziono wspomniany artykuł tutaj: blogs.msdn.com/b/ricom/archive/2005/05/10/416151.aspx
gjvdkamp

Przede wszystkim, jeśli spojrzymy na kod Raymonda Chena, wyraźnie nie rozumie on C ++ ani struktur danych zbyt dobrze. Jego kod prawie sięga prosto do kodu C niskiego poziomu, nawet w przypadkach, gdy kod C nie przynosi korzyści wydajnościowych (po prostu wydaje się być rodzajem nieufności i być może brakiem wiedzy o tym, jak używać profilerów). Nie udało mu się również zrozumieć najbardziej algorytmicznie poprawnego sposobu implementacji słownika (użył std :: find na litość boską). Jeśli jest coś dobrego w Javie, Pythonie, C # itp. - wszystkie zapewniają bardzo wydajne słowniki ...
stinky472

Próby, a nawet std :: map wypadłyby znacznie lepiej w stosunku do C ++ lub nawet tablicy mieszającej. Wreszcie słownik jest dokładnie tym typem programu, który czerpie największe korzyści z bibliotek i frameworków wysokiego poziomu. Nie pokazuje różnic w języku tak bardzo, jak zaangażowane biblioteki (z których, z radością powiedziałbym, że C # jest znacznie bardziej kompletny i zapewnia znacznie więcej narzędzi dostosowanych do tego zadania). Pokaż program, który operuje dużymi blokami pamięci w porównaniu, na przykład wielkoskalowy kod macierzowy / wektorowy. To załatwi to dość szybko, nawet jeśli tak jak w tym przypadku koderzy nie wiedzą co ...
stinky472

26

Kompilacja pod kątem określonych optymalizacji procesora jest zwykle przeceniana. Po prostu weź program w C ++ i skompiluj go z optymalizacją dla Pentium PRO i uruchom na Pentium 4. Następnie dokonaj ponownej kompilacji z Optimize dla Pentium 4. Spędzałem długie popołudnia robiąc to z kilkoma programami. Ogólne wyniki? Zwykle wzrost wydajności o mniej niż 2-3%. Więc teoretyczne zalety JIT są prawie żadne. Większość różnic w wydajności można zaobserwować tylko podczas korzystania z funkcji przetwarzania danych skalarnych, co i tak będzie wymagało ręcznego dostrojenia, aby osiągnąć maksymalną wydajność. Tego rodzaju optymalizacje są powolne i kosztowne w wykonaniu, co sprawia, że ​​czasami nie nadają się do JIT.

W świecie rzeczywistym i rzeczywistych aplikacjach C ++ jest nadal zwykle szybszy niż java, głównie ze względu na mniejszy rozmiar pamięci, co skutkuje lepszą wydajnością pamięci podręcznej.

Jednak aby wykorzystać wszystkie możliwości C ++, programista musi ciężko pracować. Możesz osiągnąć lepsze wyniki, ale musisz do tego użyć swojego mózgu. C ++ to język, który postanowił zaprezentować Ci więcej narzędzi, pobierając cenę, którą musisz się ich nauczyć, aby móc dobrze używać języka.


4
Nie chodzi o to, że kompilujesz pod kątem optymalizacji procesora, ale kompilujesz pod kątem optymalizacji ścieżki środowiska wykonawczego. Jeśli zauważysz, że metoda jest bardzo często wywoływana z określonym parametrem, możesz wstępnie skompilować tę procedurę z tym parametrem jako stałą, która może (w przypadku wartości logicznej sterującej przepływem) rozłożyć gigantyczne porcje pracy. C ++ nie może zbliżyć się do tego rodzaju optymalizacji.
Bill K

1
Jak więc radzą sobie JIT przy ponownej kompilacji procedur, aby wykorzystać zaobserwowane ścieżki i jakie to ma znaczenie?
David Thornley,

2
@Bill Być może mieszam dwie rzeczy ... ale czy predykcja rozgałęzień wykonywana w czasie wykonywania w potoku instrukcji nie osiąga podobnych celów niezależnie od języka?
Hardy

@Hardy tak, procesor może przewidywać rozgałęzienia niezależnie od języka, ale nie może uwzględniać całej pętli, obserwując, że pętla nie ma na nic żadnego wpływu. Nie zauważy również, że funkcja mult (0) jest na stałe połączona z zwracaniem 0 i po prostu zamienia całe wywołanie metody na if (param == 0) result = 0; i unikaj całego wywołania funkcji / metody. C mógłby zrobić te rzeczy, gdyby kompilator miał kompleksowy przegląd tego, co się dzieje, ale generalnie nie ma wystarczających informacji w czasie kompilacji.
Bill K

21

JIT (Just In Time Compiling) może być niesamowicie szybki, ponieważ optymalizuje pod kątem platformy docelowej.

Oznacza to, że może wykorzystać każdą sztuczkę kompilatora, którą obsługuje Twój procesor, niezależnie od tego, na jakim procesorze programista napisał kod.

Podstawowa koncepcja JIT .NET działa w następujący sposób (mocno uproszczona):

Wywołanie metody po raz pierwszy:

  • Twój kod programu wywołuje metodę Foo ()
  • Środowisko CLR sprawdza typ, który implementuje Foo () i pobiera skojarzone z nim metadane
  • Z metadanych CLR wie, w jakim adresie pamięci jest przechowywany IL (pośredni kod bajtu).
  • Środowisko CLR przydziela blok pamięci i wywołuje JIT.
  • JIT kompiluje IL do kodu natywnego, umieszcza go w przydzielonej pamięci, a następnie zmienia wskaźnik funkcji w metadanych typu Foo (), aby wskazywał na ten kod natywny.
  • Został uruchomiony kod natywny.

Wywołanie metody po raz drugi:

  • Twój kod programu wywołuje metodę Foo ()
  • Środowisko CLR sprawdza typ, który implementuje Foo () i znajduje wskaźnik funkcji w metadanych.
  • Jest uruchamiany kod natywny w tej lokalizacji pamięci.

Jak widać, za drugim razem jest to praktycznie ten sam proces, co C ++, z wyjątkiem zalet optymalizacji w czasie rzeczywistym.

To powiedziawszy, nadal istnieją inne ogólne problemy, które spowalniają zarządzany język, ale JIT bardzo pomaga.


Swoją drogą, Jonathan, myślę, że ktoś nadal przegłosowuje twoje rzeczy. Kiedy głosowałem na ciebie, miałeś -1 w tym poście.
Brian R. Bondy

12

Podoba mi się odpowiedź Oriona Adriana , ale jest jeszcze jeden aspekt.

To samo pytanie zostało postawione dziesiątki lat temu w kwestii języka asemblera i języków „ludzkich”, takich jak FORTRAN. Część odpowiedzi jest podobna.

Tak, program w C ++ może być szybszy niż C # na dowolnym (nietrywialnym?) Algorytmie, ale program w C # często będzie równie szybki lub szybszy niż „naiwna” implementacja w C ++ i zoptymalizowana wersja w C ++ będzie trwało dłużej, a wersja C # może być lepsza od wersji z bardzo małym marginesem. Czy naprawdę warto?

Będziesz musiał odpowiadać na to pytanie indywidualnie.

To powiedziawszy, od dawna jestem fanem C ++ i myślę, że jest to niesamowicie wyrazisty i potężny język - czasami niedoceniany. Ale w przypadku wielu problemów „prawdziwego życia” (dla mnie osobiście oznacza to „takie, za których rozwiązanie dostaję wynagrodzenie”), C # wykona zadanie szybciej i bezpieczniej.

Największa kara, jaką płacisz? Wiele programów .NET i Java to świnie pamięci. Widziałem, jak aplikacje .NET i Java zajmują „setki” megabajtów pamięci, podczas gdy programy C ++ o podobnej złożoności ledwo zajmują „dziesiątki” MB.


7

Nie jestem pewien, jak często okaże się, że kod Java będzie działał szybciej niż C ++, nawet z Hotspotem, ale spróbuję wyjaśnić, jak to się może stać.

Pomyśl o skompilowanym kodzie Javy jako o zinterpretowanym języku maszynowym JVM. Kiedy procesor Hotspot zauważy, że pewne fragmenty skompilowanego kodu będą używane wielokrotnie, przeprowadza optymalizację kodu maszynowego. Ponieważ ręka-tuning Zgromadzenie jest prawie zawsze szybciej niż kod C ++ skompilowany, to OK, aby postać, która programowo dostrojony kod maszyna nie będzie zbyt złe.

Tak więc, w przypadku wysoce powtarzalnego kodu, mogłem zobaczyć, gdzie Hotspot JVM może uruchamiać Javę szybciej niż C ++ ... dopóki nie włączy się zbieranie śmieci. :)


Czy mógłbyś rozwinąć to stwierdzenie Since hand-tuning Assembly is almost always faster than C++ compiled code? Co masz na myśli przez „ręczne dostrajanie zestawu” i „skompilowany kod C ++”?
paercebal

Cóż, opiera się na założeniu, że optymalizator kompilatora przestrzega reguł, a programiści nie. Tak więc zawsze będzie kod, który optymalizator uzna, że ​​nie może optymalnie zoptymalizować, podczas gdy człowiek może, patrząc z szerszej perspektywy lub wiedząc więcej o tym, co naprawdę robi kod. Dodam, że jest to komentarz sprzed 3 lat i wiem więcej o HotSpot niż kiedyś i mogę łatwo zauważyć, że optymalizacja dynamiczna jest BARDZO fajnym sposobem na szybsze działanie kodu.
billjamesdev

1. Optymalizacje z Hotspot lub jakiegokolwiek innego JIT to nadal optymalizacje kompilatora. JIT ma tę przewagę nad statycznym kompilatorem, że może wstawiać niektóre wyniki (często wywoływany kod), a nawet dokonywać optymalizacji w oparciu o procesor wykonawczy, ale nadal jest to optymalizacja kompilatora. . . 2. Myślę, że mówisz o optymalizacji algorytmu, a nie o „precyzyjnym dostrajaniu zespołu”. „Ręczne dostrajanie zespołu przez ludzkiego kodera” nie przyniosło lepszych wyników niż optymalizacje kompilatora od ponad dziesięciu lat. W rzeczywistości człowiek bawiący się montażem zwykle
spycha

Ok, rozumiem, że używam złej terminologii, „optymalizacji kompilatora” zamiast „optymalizacji statycznej”. Chciałbym zwrócić uwagę, że przynajmniej w branży gier, jeszcze niedawno, gdy chodziło o PS2, nadal używaliśmy ręcznie kodowanego zestawu w miejscach, aby „zoptymalizować” pod kątem konkretnych układów, o których wiedzieliśmy, że znajdują się na konsoli; cross-kompilatory dla tych nowych chipów nie są jeszcze tak wyrafinowane, jak te dla architektur x86. Wracając do pierwotnego pytania powyżej: JIT ma tę zaletę, że jest w stanie dokonać pomiarów przed optymalizacją, co jest dobrą rzeczą (TM)
billjamesdev

Zauważ, że większość produkcyjnych GC również używa ręcznie napisanego asemblera, ponieważ C / C ++ go nie wycina.
JD,

6

Ogólnie algorytm programu będzie znacznie ważniejszy dla szybkości działania aplikacji niż język . Możesz zaimplementować słaby algorytm w dowolnym języku, w tym w C ++. Mając to na uwadze, generalnie będziesz w stanie napisać kod działający szybciej w języku, który pomoże Ci zaimplementować bardziej wydajny algorytm.

Języki wyższego poziomu radzą sobie w tym bardzo dobrze, zapewniając łatwiejszy dostęp do wielu wydajnych, gotowych struktur danych i zachęcając do praktyk, które pomogą Ci uniknąć nieefektywnego kodu. Oczywiście czasami mogą również ułatwić napisanie kilku naprawdę powolnego kodu, więc nadal musisz znać swoją platformę.

Ponadto C ++ dogania „nowe” (zwróć uwagę na cudzysłowy) funkcje, takie jak kontenery STL, automatyczne wskaźniki itp. - zobacz na przykład bibliotekę boost. Czasami może się okazać, że najszybszy sposób wykonania jakiegoś zadania wymaga techniki takiej jak arytmetyka wskaźników, która jest zabroniona w języku wyższego poziomu - chociaż zazwyczaj pozwalają one wywołać bibliotekę napisaną w języku, który może ją zaimplementować zgodnie z potrzebami .

Najważniejsze jest, aby znać język, którego używasz, powiązany z nim interfejs API, co potrafi i jakie są jego ograniczenia.


5

Ja też nie wiem ... moje programy w języku Java są zawsze powolne. :-) Tak naprawdę nigdy nie zauważyłem, że programy C # są szczególnie wolne.


4

Oto kolejny ciekawy test porównawczy, który możesz wypróbować na własnym komputerze.

Porównuje ASM, VC ++, C #, Silverlight, aplet Java, Javascript, Flash (AS3)

Demo prędkości wtyczki Roozz

Należy pamiętać, że szybkość javascript jest bardzo różna w zależności od przeglądarki, która go wykonuje. To samo dotyczy Flash i Silverlight, ponieważ te wtyczki działają w tym samym procesie co przeglądarka hostingowa. Ale wtyczka Roozz obsługuje standardowe pliki .exe, które działają we własnym procesie, więc przeglądarka hostująca nie ma wpływu na szybkość.


4

Należy zdefiniować „wydajność lepiej niż…”. Cóż, wiem, pytałeś o prędkość, ale to nie wszystko się liczy.

  • Czy maszyny wirtualne bardziej obciążają czas wykonywania? Tak!
  • Czy jedzą więcej pamięci roboczej? Tak!
  • Czy mają wyższe koszty uruchomienia (inicjalizacja środowiska uruchomieniowego i kompilator JIT)? Tak!
  • Czy wymagają zainstalowania dużej biblioteki? Tak!

I tak dalej, jest stronniczy, tak;)

Dzięki C # i Javie płacisz cenę za to, co dostajesz (szybsze kodowanie, automatyczne zarządzanie pamięcią, duża biblioteka i tak dalej). Ale nie masz zbyt wiele miejsca na targowanie się o szczegóły: weź cały pakiet albo nic.

Nawet jeśli te języki mogą zoptymalizować jakiś kod, aby był wykonywany szybciej niż kod skompilowany, całe podejście jest (IMHO) nieefektywne. Wyobraź sobie, że codziennie jeździsz ciężarówką 5 mil do swojego miejsca pracy! Jest wygodny, dobrze się czujesz, jesteś bezpieczny (strefa ekstremalnego zgniotu), a po pewnym czasie nadepnięcia na gaz będzie nawet tak szybki jak zwykły samochód! Dlaczego nie wszyscy mamy ciężarówkę do jazdy do pracy? ;)

W C ++ dostajesz to, za co płacisz, nie więcej, nie mniej.

Cytując Bjarne Stroustrup: „C ++ to mój ulubiony język do zbierania śmieci, ponieważ generuje tak mało śmieci” tekst linku


Cóż, myślę, że ma dobre pojęcie o swoich wadach, powiedział też: „C ułatwia strzelanie sobie w stopę; C ++ sprawia, że ​​jest trudniej, ale kiedy to robisz,
zdmuchuje

„Czy wymagają zainstalowania ogromnej biblioteki?” Java rozwiązuje ten problem za pomocą układanki projektowej.
toc777,

„W C ++ dostajesz to, za co płacisz, nie więcej, nie mniej”. Przykład licznika: Porównałem implementację drzewa RB w OCaml i C ++ (GNU GCC), która wykorzystywała wyjątek do długiego wyskoku z rekurencji, jeśli dodawany element był już obecny w celu ponownego wykorzystania istniejącego zestawu. OCaml był do 6 razy szybszy niż C ++, ponieważ nie opłaca się sprawdzać destruktorów podczas rozwijania stosu.
JD,

3
@Jon: ale w jakimś (później?) Momencie i tak musi zniszczyć obiekty (przynajmniej musi zwolnić swoją pamięć). Zauważ też, że wyjątki dotyczą wyjątkowych przypadków, przynajmniej w C ++ ta zasada powinna być przestrzegana. Wyjątki C ++ mogą być ciężkie, gdy wystąpią wyjątki, to jest kompromis.
Frunsi,

@Jon: może spróbuj powtórzyć swój test porównawczy z timesna powłoce. Żeby sprawdzał cały program, a nie tylko jeden aspekt. Czy wyniki są podobne?
Frunsi,

3

Kod wykonywalny utworzony z kompilatora Javy lub C # nie jest interpretowany - jest kompilowany do kodu natywnego „just in time” (JIT). Tak więc przy pierwszym napotkaniu kodu w programie Java / C # podczas wykonywania występuje pewien narzut, ponieważ „kompilator środowiska uruchomieniowego” (znany również jako kompilator JIT) zamienia kod bajtowy (Java) lub kod IL (C #) na natywne instrukcje maszynowe. Jednak następnym razem, gdy ten kod zostanie napotkany, gdy aplikacja nadal działa, kod natywny jest wykonywany natychmiast. Wyjaśnia to, jak niektóre programy Java / C # wydają się początkowo wolne, ale im dłużej działają, działają lepiej. Dobrym przykładem jest witryna sieci Web ASP.Net. Podczas pierwszego dostępu do witryny sieci Web może to być nieco wolniejsze, ponieważ kod C # jest kompilowany do kodu natywnego przez kompilator JIT.


3

Kilka dobrych odpowiedzi na konkretne pytanie, które zadałeś. Chciałbym się cofnąć i spojrzeć na szerszą perspektywę.

Należy pamiętać, że na postrzeganie przez użytkownika szybkości pisania oprogramowania wpływa wiele innych czynników niż tylko to, jak dobrze kodegen optymalizuje. Oto kilka przykładów:

  • Ręczne zarządzanie pamięcią jest trudne do wykonania poprawnie (brak wycieków), a jeszcze trudniejsze do wykonania efektywnie (zwolnij pamięć wkrótce po zakończeniu). Ogólnie rzecz biorąc, użycie GC daje większe prawdopodobieństwo stworzenia programu, który dobrze zarządza pamięcią. Czy chcesz pracować bardzo ciężko i opóźniać dostarczanie oprogramowania, próbując prześcignąć GC?

  • Mój C # jest łatwiejszy do odczytania i zrozumienia niż mój C ++. Mam też więcej sposobów, aby przekonać się, że mój kod C # działa poprawnie. Oznacza to, że mogę zoptymalizować moje algorytmy przy mniejszym ryzyku wprowadzania błędów (a użytkownicy nie lubią oprogramowania, które ulega awarii, nawet jeśli robi to szybko!)

  • Mogę tworzyć swoje oprogramowanie szybciej w C # niż w C ++. To uwalnia czas na pracę nad wydajnością i nadal dostarcza oprogramowanie na czas.

  • Łatwiej jest napisać dobry interfejs użytkownika w języku C # niż w C ++, więc bardziej prawdopodobne jest, że będę w stanie przenieść pracę do tła, podczas gdy interfejs użytkownika pozostaje responsywny, lub zapewnić postęp lub interfejs dźwiękowy, gdy program musi na chwilę blokować. To nie przyspiesza niczego, ale sprawia, że ​​użytkownicy są szczęśliwsi z czekania.

Wszystko, co powiedziałem o C #, prawdopodobnie odnosi się do Javy, po prostu nie mam doświadczenia, żeby to powiedzieć na pewno.


3

Jeśli jesteś programistą Java / C # uczącym się C ++, będziesz kusić, aby dalej myśleć w kategoriach Java / C # i tłumaczyć dosłownie składnię C ++. W takim przypadku uzyskasz tylko wspomniane wcześniej zalety kodu natywnego w porównaniu z interpretacją / JIT. Aby uzyskać największy wzrost wydajności w C ++ w porównaniu z Java / C #, musisz nauczyć się myśleć w C ++ i projektować kod specjalnie, aby wykorzystać mocne strony C ++.

Parafrazując Edsgera Dijkstrę : [twój pierwszy język] okalecza umysł nie do wyzdrowienia.
Parafrazując Jeffa Atwooda : możesz pisać [swój pierwszy język] w dowolnym nowym języku.


1
Podejrzewam, że powiedzenie „FORTRAN można pisać w dowolnym języku” pochodzi sprzed kariery Jeffa.
David Thornley

3

Jedną z najważniejszych optymalizacji JIT jest wstawianie metody. Java może nawet wbudować metody wirtualne, jeśli gwarantuje poprawność działania. Ten rodzaj optymalizacji zwykle nie może być przeprowadzony przez standardowe kompilatory statyczne, ponieważ wymaga analizy całego programu, co jest trudne ze względu na oddzielną kompilację (w przeciwieństwie do JIT ma dostępny cały program). Wbudowanie metod poprawia inne optymalizacje, zapewniając większe bloki kodu do optymalizacji.

Standardowa alokacja pamięci w Javie / C # jest również szybsza, a cofanie alokacji (GC) nie jest dużo wolniejsze, ale tylko mniej deterministyczne.


Zauważ, że freei deletenie są deterministyczne, a GC można uczynić deterministycznymi, nie przydzielając.
JD,

3

Jest mało prawdopodobne, aby języki maszyn wirtualnych przewyższały języki skompilowane, ale mogą zbliżyć się na tyle blisko, że nie ma to znaczenia, z (przynajmniej) następujących powodów (mówię tutaj o Javie, ponieważ nigdy nie robiłem C #).

1 / Środowisko wykonawcze Java jest zwykle w stanie wykryć fragmenty kodu, które są często uruchamiane i wykonać kompilację just-in-time (JIT) tych sekcji, aby w przyszłości działały z pełną szybkością kompilacji.

2 / Ogromne części bibliotek Java są kompilowane w taki sposób, że wywołanie funkcji biblioteki powoduje wykonanie skompilowanego kodu, a nie interpretacji. Możesz zobaczyć kod (w C), pobierając OpenJDK.

3 / O ile nie wykonujesz masowych obliczeń, przez większość czasu Twój program czeka na dane wejściowe od bardzo powolnego (względnie mówiącego) człowieka.

4 / Ponieważ wiele sprawdzania poprawności kodu bajtowego Javy jest wykonywanych w czasie ładowania klasy, normalne obciążenie związane z kontrolą w czasie wykonywania jest znacznie zmniejszone.

5 / W najgorszym przypadku kod wymagający dużej wydajności może zostać wyodrębniony do skompilowanego modułu i wywołany z Javy (patrz JNI), aby działał z pełną prędkością.

Podsumowując, kod bajtowy Java nigdy nie przewyższy natywnego języka maszynowego, ale istnieją sposoby na złagodzenie tego problemu. Dużą zaletą Javy (jak ja to widzę) jest OGROMNA standardowa biblioteka i wieloplatformowy charakter.


1
Do punktu 2, „2 / Rozległe części bibliotek Java są kompilowane w taki sposób, że wywołanie funkcji biblioteki powoduje wykonanie skompilowanego kodu, a nie interpretacji”: Czy masz na to cytat? Gdyby było naprawdę tak, jak opisujesz, spodziewałbym się często natrafiać na kod natywny z mojego debugera, ale tak nie jest.
cero

Debuggery Re: cero często używają mniej wydajnych, ale bardziej wyrazistych ścieżek i dlatego nie są dobrym wskaźnikiem dla niczego związanego z wydajnością.
Guvante

2
Jest jeszcze jeden ogromny wzrost wydajności tej biblioteki HUGH - kod biblioteki jest prawdopodobnie lepiej napisany niż to, co wielu programistów napisze samodzielnie (biorąc pod uwagę ograniczony czas i brak specjalistycznej wiedzy) oraz w Javie, z wielu powodów programiści często używają Biblioteka.
Liran Orevi

3

Orion Adrian , pozwól, że odwrócę twój post, aby zobaczyć, jak bezpodstawne są twoje uwagi, ponieważ wiele można powiedzieć o C ++. A powiedzenie, że kompilator Java / C # optymalizuje puste funkcje, naprawdę sprawia, że ​​brzmisz tak, jak ty nie był moim ekspertem w optymalizacji, ponieważ a) dlaczego prawdziwy program miałby zawierać puste funkcje, z wyjątkiem naprawdę złego starszego kodu, b) tak naprawdę nie jest optymalizacja czarnych i krwawych krawędzi.

Poza tym wyrażeniem, bezczelnie narzekałeś na wskaźniki, ale czy obiekty w Javie i C # nie działają w zasadzie jak wskaźniki C ++? Czy nie mogą się pokrywać? Czy nie mogą być nieważne? C (i większość implementacji C ++) ma słowo kluczowe restrykcyjne, oba mają typy wartości, C ++ ma odniesienie do wartości z gwarancją niezerową. Co oferują Java i C #?

>>>>>>>>>>

Ogólnie rzecz biorąc, C i C ++ mogą być równie szybkie lub szybsze, ponieważ kompilator AOT - kompilator, który kompiluje kod przed wdrożeniem, raz na zawsze, na serwerze kompilacji z wieloma rdzeniami o dużej ilości pamięci - może wprowadzać optymalizacje, które program skompilowany w C # nie może, ponieważ ma na to mnóstwo czasu. Kompilator może określić, czy maszyna to Intel czy AMD; Pentium 4, Core Solo lub Core Duo; lub jeśli obsługuje SSE4 itp., a Twój kompilator nie obsługuje wysyłania w czasie wykonywania, możesz rozwiązać ten problem samodzielnie, wdrażając kilka wyspecjalizowanych plików binarnych.

Program AC # jest zwykle kompilowany po uruchomieniu go, aby działał przyzwoicie na wszystkich maszynach, ale nie jest zoptymalizowany tak bardzo, jak mógłby być dla pojedynczej konfiguracji (tj. Procesora, zestawu instrukcji, innego sprzętu) i musi spędzać trochę czasu pierwszy. Funkcje takie jak rozszczepianie pętli, inwersja pętli, automatyczna wektoryzacja, optymalizacja całego programu, rozszerzanie szablonów, IPO i wiele innych, są bardzo trudne do rozwiązania w całości i całkowicie w sposób, który nie denerwuje użytkownika końcowego.

Ponadto niektóre funkcje językowe pozwalają kompilatorowi w C ++ lub C na przyjmowanie założeń dotyczących kodu, co pozwala mu na optymalizację pewnych części, które nie są bezpieczne dla kompilatora Java / C #. Kiedy nie masz dostępu do pełnego identyfikatora typu leków generycznych lub gwarantowanego przepływu programu, istnieje wiele optymalizacji, które po prostu nie są bezpieczne.

Również C ++ i C wykonują wiele alokacji stosu naraz z tylko jedną inkrementacją rejestru, co z pewnością jest bardziej wydajne niż alokacje Javas i C #, jeśli chodzi o warstwę abstrakcji między modułem odśmiecania pamięci a kodem.

Teraz nie mogę mówić o Javie w tym następnym punkcie, ale wiem, że na przykład kompilatory C ++ faktycznie usuwają metody i wywołania metod, gdy wie, że treść metody jest pusta, wyeliminuje typowe podwyrażenia, może spróbować ponownie aby znaleźć optymalne wykorzystanie rejestru, nie wymusza sprawdzania granic, autowektoryzuje pętle i wewnętrzne pętle oraz odwraca wewnętrzne do zewnętrznych, usuwa warunkowe z pętli, dzieli i przerywa pętle. Rozszerzy std :: vector na natywne tablice zerowe, tak jak zrobiłbyś to w C. Dokonuje optymalizacji między procedurami. Konstruuje wartości zwracane bezpośrednio w witrynie wywołującej. Będzie zwijać i propagować wyrażenia. Zmieni kolejność danych w sposób przyjazny dla pamięci podręcznej. Będzie robić wątki skokowe. Pozwala na pisanie znaczników promieni czasu kompilacji bez narzutu czasu wykonania. Spowoduje to bardzo kosztowne optymalizacje oparte na wykresach. Zredukuje siłę, jeśli zastąpi niektóre kody składniowo całkowicie nierównym, ale semantycznie równoważnym kodem (stary "xor foo, foo" jest po prostu najprostszą, choć przestarzałą optymalizacją tego rodzaju). Jeśli o to poprosisz, możesz pominąć standardy zmiennoprzecinkowe IEEE i włączyć jeszcze więcej optymalizacji, takich jak zmiana kolejności operandów zmiennoprzecinkowych. Po wymasowaniu i zmasakrowaniu kodu może powtórzyć cały proces, ponieważ często pewne optymalizacje stanowią podstawę do jeszcze niektórych optymalizacji. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie. gdyby zastępował pewne kody składniowo całkowicie nierównym, ale semantycznie równoważnym kodem (stary „xor foo, foo” jest po prostu najprostszą, choć przestarzałą optymalizacją tego rodzaju). Jeśli o to poprosisz, możesz pominąć standardy zmiennoprzecinkowe IEEE i włączyć jeszcze więcej optymalizacji, takich jak zmiana kolejności operandów zmiennoprzecinkowych. Po wymasowaniu i zmasakrowaniu kodu może powtórzyć cały proces, ponieważ często pewne optymalizacje stanowią podstawę do jeszcze niektórych optymalizacji. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie. gdyby zastępował pewne kody składniowo całkowicie nierównym, ale semantycznie równoważnym kodem (stary „xor foo, foo” jest po prostu najprostszą, choć przestarzałą optymalizacją tego rodzaju). Jeśli o to poprosisz, możesz pominąć standardy zmiennoprzecinkowe IEEE i włączyć jeszcze więcej optymalizacji, takich jak zmiana kolejności operandów zmiennoprzecinkowych. Po wymasowaniu i zmasakrowaniu kodu może powtórzyć cały proces, ponieważ często pewne optymalizacje stanowią podstawę do jeszcze niektórych optymalizacji. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie. Jeśli o to poprosisz, możesz pominąć standardy zmiennoprzecinkowe IEEE i włączyć jeszcze więcej optymalizacji, takich jak zmiana kolejności operandów zmiennoprzecinkowych. Po wymasowaniu i zmasakrowaniu kodu może powtórzyć cały proces, ponieważ często pewne optymalizacje stanowią podstawę do jeszcze niektórych optymalizacji. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie. Jeśli o to poprosisz, możesz pominąć standardy zmiennoprzecinkowe IEEE i włączyć jeszcze więcej optymalizacji, takich jak zmiana kolejności operandów zmiennoprzecinkowych. Po wymasowaniu i zmasakrowaniu kodu może powtórzyć cały proces, ponieważ często pewne optymalizacje stanowią podstawę do jeszcze niektórych optymalizacji. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie. Może też po prostu spróbować ponownie z potasowanymi parametrami i zobaczyć, jak inny wariant osiąga wyniki w swoim wewnętrznym rankingu. I będzie używać tego rodzaju logiki w całym kodzie.

Jak widać, istnieje wiele powodów, dla których niektóre implementacje C ++ lub C będą szybsze.

To wszystko powiedziawszy, wiele optymalizacji można wprowadzić w C ++, które zniweczą wszystko, co można zrobić w C #, szczególnie w dziedzinie obliczania liczb, w czasie rzeczywistym i prawie metalowym, ale nie tylko tam. Nie musisz nawet dotykać ani jednego wskaźnika, aby przejść długą drogę.

Więc w zależności od tego, co piszesz, wybrałbym jedną lub drugą. Ale jeśli piszesz coś, co nie jest zależne od sprzętu (sterownik, gra wideo itp.), Nie martwiłbym się o wydajność C # (znowu nie mogę mówić o Javie). Wystarczy.

<<<<<<<<<<

Ogólnie niektóre uogólnione argumenty mogą brzmieć fajnie w określonych postach, ale generalnie nie brzmią na pewno wiarygodnie.

W każdym razie, aby zawrzeć pokój: AOT jest świetny, podobnie jak JIT . Jedyną poprawną odpowiedzią może być: To zależy. A prawdziwi inteligentni ludzie wiedzą, że i tak możesz wykorzystać to, co najlepsze z obu światów.


2

Może się to zdarzyć tylko wtedy, gdy interpreter języka Java tworzy kod maszynowy, który jest faktycznie lepiej zoptymalizowany niż kod maszynowy generowany przez kompilator dla pisanego przez Ciebie kodu C ++, do tego stopnia, że ​​kod C ++ jest wolniejszy niż język Java i koszt interpretacji.

Jednak szanse, że tak się stanie, są dość niskie - chyba że Java ma bardzo dobrze napisaną bibliotekę, a ty masz własną źle napisaną bibliotekę C ++.


Uważam również, że istnieje również pewna waga języka, pracując na niższym poziomie, z mniejszą abstrakcją, będziesz rozwijał program, który będzie szybszy. Nie ma to związku z kwestiami dotyczącymi samego wykonania kodu bajtowego.
Brian R. Bondy

2

W rzeczywistości C # tak naprawdę nie działa na maszynie wirtualnej, tak jak robi to Java. IL jest kompilowany do języka asemblera, który jest całkowicie rodzimym kodem i działa z taką samą prędkością jak kod natywny. Możesz przeprowadzić wstępne JIT aplikacji .NET, która całkowicie usuwa koszt JIT, a następnie uruchamiasz całkowicie natywny kod.

Spowolnienie z .NET nadejdzie nie dlatego, że kod .NET jest wolniejszy, ale dlatego, że robi znacznie więcej za kulisami, aby robić takie rzeczy, jak zbieranie śmieci, sprawdzanie referencji, przechowywanie kompletnych ramek stosu itp. Może to być dość potężne i pomocne, gdy tworzenie aplikacji, ale wiąże się to również z kosztami. Zauważ, że możesz zrobić to wszystko w programie C ++ (większość podstawowych funkcji .NET to w rzeczywistości kod .NET, który możesz wyświetlić w programie ROTOR). Jeśli jednak ręcznie napisałeś tę samą funkcjonalność, prawdopodobnie skończyłbyś z dużo wolniejszym programem, ponieważ środowisko wykonawcze .NET zostało zoptymalizowane i precyzyjnie dostrojone.

To powiedziawszy, jedną z mocnych stron zarządzanego kodu jest to, że można go w pełni zweryfikować, tj. możesz sprawdzić, czy kod nigdy nie uzyska dostępu do pamięci innego procesu ani nie zrobi żadnych rzeczy przed wykonaniem go. Firma Microsoft dysponuje prototypem badawczym w pełni zarządzanego systemu operacyjnego, który nieoczekiwanie wykazał, że w 100% zarządzane środowisko może działać znacznie szybciej niż jakikolwiek nowoczesny system operacyjny, wykorzystując tę ​​weryfikację do wyłączania funkcji zabezpieczeń, które nie są już potrzebne zarządzanym programom (w niektórych przypadkach mówimy około 10x). Radio SE ma świetny odcinek opowiadający o tym projekcie.


1

W niektórych przypadkach kod zarządzany może być w rzeczywistości szybszy niż kod natywny. Na przykład algorytmy usuwania pamięci typu „mark-and-sweep” pozwalają środowiskom takim jak JRE lub CLR na zwolnienie dużej liczby krótkotrwałych (zwykle) obiektów w jednym przebiegu, podczas gdy większość obiektów sterty C / C ++ jest zwalnianych jeden na czas.

Z Wikipedii :

Z wielu praktycznych powodów algorytmy intensywnie przydzielające / zwalniające zaimplementowane w językach ze śmieciami mogą być w rzeczywistości szybsze niż ich odpowiedniki wykorzystujące ręczną alokację sterty. Głównym tego powodem jest to, że moduł odśmiecania pamięci umożliwia systemowi wykonawczemu amortyzację operacji alokacji i zwalniania alokacji w potencjalnie korzystny sposób.

To powiedziawszy, napisałem dużo C # i dużo C ++ i przeprowadziłem wiele testów porównawczych. Z mojego doświadczenia wynika, C ++ jest o wiele szybciej niż C #, na dwa sposoby: (1) jeśli wziąć jakiś kod, który został napisany w języku C #, port go do C ++ natywny kod ma tendencję do być szybciej. O ile szybciej? Cóż, jest bardzo różny, ale nie jest niczym niezwykłym, że widzi się 100% poprawę prędkości. (2) W niektórych przypadkach wyrzucanie elementów bezużytecznych może znacznie spowolnić zarządzaną aplikację. NET CLR wykonuje okropną robotę z dużymi stosami (powiedzmy> 2 GB) i może spędzać dużo czasu w GC - nawet w aplikacjach, które mają niewiele - lub nawet nie mają - obiektów o pośrednim okresie życia.

Oczywiście, w większości przypadków, z którymi się spotkałem, języki zarządzane są wystarczająco szybkie, z długiej perspektywy, a konserwacja i kompromis w zakresie kodowania dla dodatkowej wydajności C ++ po prostu nie jest dobry.


1
Problem polega na tym, że w przypadku długotrwałych procesów, takich jak serwer WWW, twoja pamięć z czasem stanie się tak pofragmentowana (w programie napisanym w C ++), że będziesz musiał zaimplementować coś, co przypomina zbieranie śmieci (lub restartuj co jakiś czas, zobacz IIS ).
Tony BenBrahim

3
Nie zauważyłem tego w dużych programach Unix, które mają działać wiecznie. Zwykle są pisane w C, co jest nawet gorsze w zarządzaniu pamięcią niż C ++.
David Thornley

Oczywiście pytanie brzmi, czy porównujemy implementację programu w kodzie zarządzanym i niezarządzanym, czy też teoretyczną najwyższą wydajność języka. Oczywiście niezarządzany kod zawsze może być co najmniej tak szybki, jak zarządzany, ponieważ w najgorszym przypadku można po prostu napisać niezarządzany program, który zrobiłby dokładnie to samo, co kod zarządzany! Jednak większość problemów z wydajnością ma charakter algorytmiczny, a nie mikro. Ponadto nie optymalizujesz kodu zarządzanego i niezarządzanego w ten sam sposób, więc „C ++ w C #” zwykle nie będzie działać dobrze.
kyoryu

2
W C / C ++ Państwo mogą przeznaczyć krótkotrwałe obiektów na stosie, a ty kiedy jego właściwe. W kodzie zarządzanym nie możesz , nie masz wyboru. Również w C / C ++ Państwo mogą przeznaczyć wykazy obiektów w obszarach contigous (new Foo [100]), w kodzie zarządzanym nie można. Więc twoje porównanie jest nieważne. Cóż, ta moc wyborów obciąża deweloperów, ale w ten sposób uczą się poznać świat, w którym żyją (pamięć ......).
Frunsi

@frunsi: "w C / C ++ możesz przydzielać listy obiektów w przyległych obszarach (nowy Foo [100]), w kodzie zarządzanym nie możesz". To jest niepoprawne. Lokalne typy wartości są przydzielane na stosie, a nawet można przypisywać ich tablice w stosie w języku C #. Istnieją nawet systemy produkcyjne napisane w C #, które są całkowicie bezalokacyjne w stanie ustalonym.
JD,


1

W rzeczywistości maszyna JVM HotSpot firmy Sun używa wykonywania „w trybie mieszanym”. Interpretuje kod bajtowy metody, dopóki nie ustali (zwykle za pomocą pewnego rodzaju licznika), że określony blok kodu (metoda, pętla, blok try-catch itp.) Będzie często wykonywany, a następnie JIT go kompiluje. Czas wymagany do skompilowania metody JIT często trwa dłużej niż w przypadku interpretacji metody, jeśli jest to metoda rzadko uruchamiana. Wydajność jest zwykle wyższa w „trybie mieszanym”, ponieważ JVM nie marnuje czasu na kod JIT, który jest rzadko, jeśli w ogóle, uruchamiany. C # i .NET tego nie robią. .NET JIT wszystko, co często marnuje czas.


1

Przeczytaj o Dynamo HP Labs , interpreterze PA-8000, który działa na PA-8000 i często uruchamia programy szybciej niż natywnie. Wtedy wcale nie będzie to zaskakujące!

Nie myśl o tym jako o „kroku pośrednim” - uruchomienie programu obejmuje już wiele innych kroków w dowolnym języku.

Często sprowadza się do:

  • programy mają gorące punkty, więc nawet jeśli wolniej wykonujesz 95% całego kodu, który musisz uruchomić, nadal możesz być konkurencyjny pod względem wydajności, jeśli jesteś szybszy na gorących 5%

  • HLL wie więcej o twoich zamiarach niż LLL, jak C / C ++, dzięki czemu może generować bardziej zoptymalizowany kod (OCaml ma jeszcze więcej, aw praktyce często jest nawet szybszy)

  • kompilator JIT ma wiele informacji, których nie ma kompilator statyczny (na przykład rzeczywiste dane, które masz tym razem)

  • kompilator JIT może wykonywać optymalizacje w czasie wykonywania, których nie mogą robić tradycyjne konsolidatory (takie jak zmiana kolejności gałęzi, aby typowy przypadek był płaski lub wbudowane wywołania bibliotek)

Podsumowując, C / C ++ są dość kiepskimi językami pod względem wydajności: jest stosunkowo mało informacji o typach danych, nie ma informacji o danych i nie ma dynamicznego środowiska uruchomieniowego, które pozwala na wiele na drodze optymalizacji w czasie wykonywania.


1

Możesz uzyskać krótkie serie, gdy Java lub CLR są szybsze niż C ++, ale ogólnie wydajność jest gorsza przez cały okres użytkowania aplikacji: zobacz www.codeproject.com/KB/dotnet/RuntimePerformance.aspx, aby uzyskać niektóre wyniki.



1

Rozumiem, że C / C ++ tworzy natywny kod do uruchomienia na określonej architekturze maszyny. I odwrotnie, języki takie jak Java i C # działają na maszynie wirtualnej, która oddziela architekturę natywną. Logicznie wydaje się niemożliwe, aby Java lub C # dorównywała szybkością C ++ z powodu tego pośredniego kroku, jednak powiedziano mi, że najnowsze kompilatory („hot spot”) mogą osiągnąć tę prędkość lub nawet ją przekroczyć.

To jest nielogiczne. Korzystanie z reprezentacji pośredniej nie powoduje z natury degradacji wydajności. Na przykład llvm-gcc kompiluje C i C ++ przez LLVM IR (który jest wirtualną maszyną z nieskończonym rejestrem) do kodu natywnego i osiąga doskonałą wydajność (często przewyższając GCC).

Być może jest to bardziej pytanie kompilatora niż pytanie o język, ale czy ktoś może wyjaśnić prostym językiem angielskim, w jaki sposób jeden z tych języków maszyn wirtualnych może działać lepiej niż język ojczysty?

Oto kilka przykładów:

  • Maszyny wirtualne z kompilacją JIT ułatwiają generowanie kodu w czasie wykonywania (np. System.Reflection.EmitNa .NET), dzięki czemu można skompilować wygenerowany kod w locie w językach takich jak C # i F #, ale muszą uciec się do pisania stosunkowo wolnego interpretera w C lub C ++. Na przykład, aby zaimplementować wyrażenia regularne.

  • Części maszyny wirtualnej (np. Bariera zapisu i alokator) są często napisane w asemblerze ręcznie kodowanym, ponieważ C i C ++ nie generują wystarczająco szybkiego kodu. Jeśli program kładzie nacisk na te części systemu, to prawdopodobnie może przewyższać wszystko, co można napisać w C lub C ++.

  • Dynamiczne łączenie kodu natywnego wymaga zgodności z ABI, co może utrudniać wydajność i eliminuje optymalizację całego programu, podczas gdy łączenie jest zwykle odraczane na maszynach wirtualnych i może korzystać z optymalizacji całego programu (takich jak zreifikowane typy generyczne .NET).

Chciałbym również odnieść się do niektórych problemów z wysoko ocenianą odpowiedzią Paercebala powyżej (ponieważ ktoś ciągle usuwa moje komentarze do jego odpowiedzi), która przedstawia spolaryzowany pogląd przeciwny do zamierzonego:

Przetwarzanie kodu będzie wykonywane w czasie kompilacji ...

Stąd metaprogramowanie szablonów działa tylko wtedy, gdy program jest dostępny w czasie kompilacji, co często nie ma miejsca, np. Niemożliwe jest napisanie konkurencyjnej biblioteki wyrażeń regularnych w waniliowym C ++, ponieważ nie jest w stanie generować kodu w czasie wykonywania (ważny aspekt metaprogramowanie).

... zabawa z typami jest wykonywana w czasie kompilacji ... odpowiednik w Javie lub C # jest w najlepszym przypadku bolesny w pisaniu i zawsze będzie wolniejszy i rozwiązany w czasie wykonywania, nawet jeśli typy są znane w czasie kompilacji.

W języku C # dotyczy to tylko typów odwołań i nie dotyczy typów wartości.

Bez względu na optymalizację JIT, nic nie pójdzie tak szybko, jak bezpośredni dostęp wskaźnika do pamięci ... jeśli masz ciągłe dane w pamięci, dostęp do nich przez wskaźniki C ++ (tj. Wskaźniki C ... dajmy Cezarowi należność) będzie przebiegał szybciej niż w Javie / C #.

Ludzie zaobserwowali, że Java pokonuje C ++ w teście SOR z testu porównawczego SciMark2 właśnie dlatego, że wskaźniki utrudniają optymalizacje związane z aliasowaniem.

Warto również zauważyć, że .NET specjalizuje się w typach typów ogólnych w bibliotekach połączonych dynamicznie po połączeniu, podczas gdy C ++ nie może, ponieważ szablony muszą zostać rozwiązane przed połączeniem. I oczywiście dużą przewagą typów generycznych nad szablonami są zrozumiałe komunikaty o błędach.


0

Oprócz tego, co powiedzieli inni, z mojego zrozumienia .NET i Java są lepsze w alokacji pamięci. Na przykład mogą kompaktować pamięć, gdy jest ona pofragmentowana, podczas gdy C ++ nie może (natywnie, ale może, jeśli używasz sprytnego garbage collectora).


Lub jeśli używasz lepszego alokatora C ++ i / lub puli obiektów. Z punktu widzenia C ++ jest to dalekie od magii i może sprowadzać się do tego, że „alokacja sterty” stanie się równie szybka alokacją stosu.
paercebal

Jeśli zawsze będziesz alokować wszystko na stercie, to .NET i Java mogą nawet działać lepiej niż C / C ++. Ale po prostu nie zrobisz tego w C / C ++.
Frunsi,

0

W przypadku wszystkiego, co wymaga dużej szybkości, JVM po prostu wywołuje implementację C ++, więc bardziej chodzi o to, jak dobre są ich biblioteki, niż o to, jak dobra jest JVM dla większości rzeczy związanych z systemem operacyjnym. Zbieranie śmieci skraca pamięć o połowę, ale użycie niektórych bardziej wyszukanych funkcji STL i Boost przyniesie ten sam efekt, ale z wielokrotnie większym potencjałem błędów.

Jeśli używasz tylko bibliotek C ++ i wielu jego funkcji wysokiego poziomu w dużym projekcie z wieloma klasami, prawdopodobnie skończysz wolniej niż przy użyciu JVM. Z wyjątkiem znacznie bardziej podatnych na błędy.

Jednak zaletą C ++ jest to, że pozwala na optymalizację, w przeciwnym razie utkniesz z tym, co robi kompilator / jvm. Jeśli stworzysz własne kontenery, napiszesz własne zarządzanie pamięcią, które jest wyrównane, użyj SIMD i upuść do asemblacji tu i tam, możesz przyspieszyć co najmniej 2x-4x razy w porównaniu z tym, co większość kompilatorów C ++ zrobi samodzielnie. W przypadku niektórych operacji 16x-32x. To przy użyciu tych samych algorytmów, jeśli zastosujesz lepsze algorytmy i zrównoleglenie, wzrosty mogą być dramatyczne, czasem tysiące razy szybsze niż powszechnie stosowane metody.


0

Patrzę na to z kilku różnych punktów.

  1. Biorąc pod uwagę nieskończony czas i zasoby, czy kod zarządzany lub niezarządzany będzie szybszy? Oczywiście odpowiedź jest taka, że ​​niezarządzany kod zawsze może przynajmniej powiązać kod zarządzany w tym aspekcie - tak jak w najgorszym przypadku, po prostu zakodowałbyś na stałe rozwiązanie kodu zarządzanego.
  2. Jeśli weźmiesz program w jednym języku i bezpośrednio przetłumaczysz go na inny, o ile gorzej będzie on działał? Prawdopodobnie dużo, dla dowolnych dwóch języków. Większość języków wymaga różnych optymalizacji i ma różne problemy. Mikro wydajność często polega na znajomości tych szczegółów.
  3. Biorąc pod uwagę ograniczony czas i zasoby, który z dwóch języków zapewni lepszy wynik? Jest to najciekawsze pytanie, ponieważ chociaż zarządzany język może generować nieco wolniejszy kod (biorąc pod uwagę program napisany rozsądnie dla tego języka), ta wersja prawdopodobnie zostanie wykonana wcześniej, co pozwoli na więcej czasu spędzonego na optymalizacji.

0

Bardzo krótka odpowiedź: biorąc pod uwagę ustalony budżet, osiągniesz lepszą wydajność aplikacji java niż aplikacja C ++ (względy zwrotu z inwestycji) Ponadto platforma Java ma bardziej przyzwoite profile, które pomogą Ci szybciej zlokalizować hotspoty

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.