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 using
ograniczać istnienia swoich obiektów. A C ++ nie ma finally
klauzuli. 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:
- 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 )
- RAII pozostaje niezrównany ( GC nadal może przeciekać (tak, musiałem sobie poradzić z tym problemem) i obsługuje tylko pamięć. Nawet C #
using
nie jest tak łatwe i wydajne, ponieważ pisanie poprawnych implementacji Dispose jest trudne )
- C #
readonly
i Java final
nigdzie 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 using
sł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: