Powtarzającym się motywem na SE, który zauważyłem w wielu pytaniach, jest ciągły argument, że C ++ jest szybszy i / lub bardziej wydajny niż języki wyższego poziomu, takie jak Java. Kontrargumentem jest to, że współczesna JVM lub CLR może być równie wydajna dzięki JIT i tak dalej w przypadku rosnącej liczby zadań, a C ++ jest jeszcze bardziej wydajny, jeśli wiesz, co robisz i dlaczego robisz to w określony sposób zasłuży na wzrost wydajności. To oczywiste i ma sens.
Chciałbym poznać podstawowe wyjaśnienie (jeśli istnieje coś takiego ...), dlaczego i jak niektóre zadania są szybsze w C ++ niż JVM lub CLR? Czy to po prostu dlatego, że C ++ jest skompilowany w kodzie maszynowym, podczas gdy JVM lub CLR nadal mają narzut przetwarzania podczas kompilacji JIT?
Kiedy próbuję zbadać ten temat, znajduję tylko te same argumenty, które przedstawiłem powyżej, bez żadnych szczegółowych informacji na temat dokładnego zrozumienia, w jaki sposób C ++ można wykorzystać do obliczeń o wysokiej wydajności.
źródło
Odpowiedzi:
Chodzi o pamięć (nie JIT). „Przewaga JIT nad C” ogranicza się głównie do optymalizacji połączeń wirtualnych lub nie-wirtualnych poprzez inline, coś, nad czym CPU BTB już ciężko pracuje.
W nowoczesnych maszynach dostęp do pamięci RAM jest naprawdę powolny (w porównaniu do wszystkiego, co robi procesor), co oznacza, że aplikacje wykorzystujące pamięci podręczne w jak największym stopniu (co jest łatwiejsze, gdy używana jest mniejsza pamięć) mogą być nawet sto razy szybsze niż te, które nie. Istnieje wiele sposobów, w jakie Java wykorzystuje więcej pamięci niż C ++ i utrudnia pisanie aplikacji w pełni wykorzystujących pamięć podręczną:
Niektóre inne czynniki związane z pamięcią, ale nie z pamięcią podręczną:
Niektóre z tych rzeczy to kompromisy (brak konieczności ręcznego zarządzania pamięcią jest warty rezygnacji z dużej wydajności dla większości ludzi), niektóre są prawdopodobnie wynikiem starań, aby Java była prosta, a niektóre są błędami projektowymi (choć być może tylko z perspektywy czasu , a mianowicie UTF-16 był kodowaniem o stałej długości podczas tworzenia Java, co sprawia, że decyzja o jego wyborze jest znacznie bardziej zrozumiała).
Warto zauważyć, że wiele z tych kompromisów jest bardzo różnych dla Java / JVM niż dla C # / CIL. .NET CIL ma struktury typu referencyjnego, alokację / przekazywanie stosu, spakowane tablice struktur i generyczne wystąpienia instancji typu.
źródło
Częściowo, ale ogólnie, zakładając absolutnie fantastyczny najnowocześniejszy kompilator JIT, właściwy kod C ++ nadal działa lepiej niż kod Java z DWÓCH głównych powodów:
1) Szablony C ++ zapewniają lepsze możliwości pisania kodu, który jest zarówno ogólny, jak i wydajny . Szablony zapewniają programistom C ++ bardzo przydatną abstrakcję, która ma narzut ZERO. (Szablony są w zasadzie pisaniem kaczych w czasie kompilacji). Dla kontrastu, najlepsze, co można uzyskać dzięki rodzajom Java, to zasadniczo funkcje wirtualne. Funkcje wirtualne zawsze mają narzut związany z czasem działania i generalnie nie można ich wstawiać.
Ogólnie rzecz biorąc, większość języków, w tym Java, C #, a nawet C, umożliwia wybór między wydajnością a ogólnością / abstrakcją. Szablony C ++ dają oba (kosztem dłuższych czasów kompilacji).
2) Fakt, że standard C ++ nie ma wiele do powiedzenia na temat układu binarnego skompilowanego programu C ++, daje kompilatorom C ++ znacznie większą swobodę niż kompilator Java, pozwalając na lepszą optymalizację (kosztem większych trudności w debugowaniu czasami). ) W rzeczywistości sama specyfikacja języka Java wymusza obniżenie wydajności w niektórych obszarach. Na przykład nie można mieć ciągłej tablicy obiektów w Javie. Możesz mieć tylko ciągłą tablicę wskaźników obiektów(referencje), co oznacza, że iteracja po tablicy w Javie zawsze pociąga za sobą koszty pośrednie. Jednak semantyka wartości C ++ umożliwia tablice ciągłe. Inną różnicą jest fakt, że C ++ pozwala na przydzielanie obiektów na stosie, podczas gdy Java nie, co oznacza, że w praktyce, ponieważ większość programów C ++ ma tendencję do przydzielania obiektów na stosie, koszt przydziału jest często bliski zeru.
Jednym z obszarów, w którym C ++ może pozostawać w tyle za Javą, jest każda sytuacja, w której wiele małych obiektów musi zostać przydzielonych na stercie. W takim przypadku system śmieciowy Javy prawdopodobnie spowoduje lepszą wydajność niż standardowa
new
idelete
w C ++, ponieważ Java GC umożliwia masowe zwolnienie. Ale znowu, programista C ++ może to zrekompensować za pomocą puli pamięci lub alokatora płyt, podczas gdy programista Java nie ma możliwości skorzystania z wzorca alokacji pamięci, dla którego środowisko Java nie jest zoptymalizowane.Zobacz także tę doskonałą odpowiedź, aby uzyskać więcej informacji na ten temat.
źródło
std::vector<int>
jest dynamiczna tablica zaprojektowana tylko dla ints, a kompilator jest w stanie odpowiednio ją zoptymalizować. AC #List<int>
to wciąż tylkoList
.List<int>
używaint[]
, a nieObject[]
jak Java. Zobacz stackoverflow.com/questions/116988/…vector<N>
gdzie, w konkretnym przypadkuvector<4>
, należy użyć mojej ręcznie kodowanej implementacji SIMDTo, czego inne odpowiedzi (jak dotąd 6) zapomniałem wspomnieć, ale uważam za bardzo ważne, aby odpowiedzieć na to pytanie, jest jedną z bardzo podstawowych filozofii projektowych C ++, którą sformułował i zastosował Stroustrup od pierwszego dnia:
Nie płacisz za to, czego nie używasz.
Istnieją inne ważne podstawowe zasady projektowania, które znacznie ukształtowały C ++ (takie, że nie powinieneś być zmuszany do określonego paradygmatu), ale nie płacisz za to, czego nie używasz, jest jednym z najważniejszych.
W swojej książce The Design and Evolution of C ++ (zwykle określanej jako [D&E]) Stroustrup opisuje, jakie były potrzeby, które sprawiły, że wymyślił C ++. Moimi własnymi słowami: W swojej pracy doktorskiej (związanej z symulacjami sieci, IIRC) zaimplementował system w SIMULI, który bardzo mu się podobał, ponieważ język był bardzo dobry, pozwalając mu wyrażać swoje myśli bezpośrednio w kodzie. Jednak powstały program działał o wiele za wolno i aby uzyskać dyplom, przepisał rzecz w BCPL, poprzedniku C. Pisanie kodu w BCPL opisuje jako ból, ale wynikowy program był wystarczająco szybki, aby dostarczyć wyniki, które pozwoliły mu ukończyć doktorat.
Potem chciał języka, który umożliwiłby przetłumaczenie rzeczywistych problemów na kod tak bezpośrednio, jak to możliwe, ale także pozwoliłby, aby kod był bardzo wydajny.
W tym celu stworzył coś, co później stanie się C ++.
Tak więc powyższy cel nie jest tylko jedną z kilku podstawowych zasad projektowania, jest bardzo zbliżony do racji bytu C ++. I można go znaleźć prawie wszędzie w języku: funkcje są tylko
virtual
wtedy, gdy chcesz (ponieważ wywoływanie funkcji wirtualnych wiąże się z niewielkim narzutem) POD są inicjowane automatycznie, gdy wyraźnie o to poprosisz, wyjątki kosztują tylko wydajność, gdy faktycznie wyrzuć je (podczas gdy było to jawnym celem projektowym, aby konfiguracja / czyszczenie ramek stosów było bardzo tanie), brak GC działającego, kiedy ma na to ochotę itp.C ++ wyraźnie postanowił nie zapewniać ci pewnych udogodnień („czy muszę tutaj uczynić tę metodę wirtualną?”) W zamian za wydajność („nie, nie wiem, a teraz kompilator może
inline
to zrobić i zoptymalizować cała sprawa! ”) i, co nie jest zaskoczeniem, rzeczywiście przyniosło to wzrost wydajności w porównaniu z wygodniejszymi językami.źródło
Czy znasz artykuł badawczy Google na ten temat?
Z wniosku:
Jest to przynajmniej częściowe wyjaśnienie w sensie „ponieważ kompilatory C ++ w świecie rzeczywistym wytwarzają szybszy kod niż kompilatory Java za pomocą miar empirycznych”.
źródło
To nie jest duplikat twoich pytań, ale zaakceptowana odpowiedź odpowiada na większość twoich pytań: nowoczesna recenzja Javy
Podsumowując:
Tak więc, w zależności od tego, z jakim innym językiem porównujesz C ++, możesz otrzymać lub nie tę samą odpowiedź.
W C ++ masz:
Są to cechy lub skutki uboczne definicji języka, która sprawia, że teoretycznie jest bardziej wydajna pod względem pamięci i szybkości niż jakikolwiek język, który:
Agresywne wstawianie kompilatora w C ++ zmniejsza lub eliminuje wiele pośrednich. Zdolność do generowania niewielkiego zestawu zwartych danych sprawia, że jest przyjazny dla pamięci podręcznej, jeśli nie rozłożysz tych danych w całej pamięci zamiast je spakować (oba są możliwe, C ++ pozwala tylko wybrać). RAII sprawia, że zachowanie pamięci C ++ jest przewidywalne, co eliminuje wiele problemów w przypadku symulacji w czasie rzeczywistym lub pół-w czasie rzeczywistym, które wymagają dużej prędkości. Problemy związane z lokalizacją można ogólnie podsumować w ten sposób: im mniejszy program / dane, tym szybsze wykonanie. C ++ zapewnia różnorodne sposoby upewnienia się, że twoje dane są tam, gdzie chcesz (w puli, tablicy lub czymkolwiek) i że są zwarte.
Oczywiście istnieją inne języki, które mogą zrobić to samo, ale są one po prostu mniej popularne, ponieważ nie zapewniają tylu narzędzi abstrakcyjnych jak C ++, więc są mniej przydatne w wielu przypadkach.
źródło
Chodzi głównie o pamięć (jak powiedział Michael Borgwardt) z odrobiną nieefektywności JIT.
Jedną z rzeczy, o których nie wspomniano, jest pamięć podręczna - aby w pełni korzystać z pamięci podręcznej, dane muszą być rozmieszczone w sposób ciągły (tj. Wszystkie razem). Teraz w systemie GC pamięć jest przydzielana na stercie GC, co jest szybkie, ale gdy pamięć się zużyje, GC będzie regularnie uruchamiać i usuwać nieużywane bloki, a następnie kompaktować pozostałe razem. Teraz oprócz oczywistego spowolnienia przenoszenia używanych bloków razem, oznacza to, że dane, których używasz, mogą się nie skleić. Jeśli masz tablicę 1000 elementów, chyba że przydzielisz je wszystkie naraz (a następnie zaktualizujesz ich zawartość zamiast usuwać i tworzyć nowe - które zostaną utworzone na końcu stosu), zostaną one rozrzucone po całym stosie, a zatem wymaga kilku trafień pamięci, aby odczytać je wszystkie w pamięci podręcznej procesora. Aplikacja AC / C ++ najprawdopodobniej przydzieli pamięć dla tych elementów, a następnie zaktualizujesz bloki danymi. (ok, istnieją struktury danych takie jak lista, które zachowują się bardziej jak przydziały pamięci GC, ale ludzie wiedzą, że są one wolniejsze niż wektory).
Możesz to zobaczyć po prostu poprzez zamianę dowolnych obiektów StringBuilder na String ... Konstruktorzy String pracują, wstępnie przydzielając pamięć i wypełniając ją, i jest to znana sztuczka wydajnościowa dla systemów Java / .NET.
Nie zapominaj, że paradygmat „usuń stare i przydziel nowe kopie” jest bardzo intensywnie wykorzystywany w Javie / C #, po prostu dlatego, że ludzie mówią, że alokacje pamięci są naprawdę szybkie z powodu GC, a więc model rozproszonej pamięci jest wszędzie używany ( z wyjątkiem konstruktorów łańcuchów, oczywiście), więc wszystkie biblioteki marnują pamięć i zużywają jej dużo, z których żadna nie korzysta z ciągłości. Za to obwiniaj szum wokół GC - powiedzieli ci, że pamięć jest wolna, lol.
Sam GC jest oczywiście kolejnym hitem - kiedy działa, musi nie tylko zamiatać stertę, ale także musi uwolnić wszystkie nieużywane bloki, a następnie musi uruchomić wszelkie finalizatory (chociaż robiono to osobno Następnym razem z zatrzymaną aplikacją) (nie wiem, czy nadal jest to hit, ale wszystkie czytane przeze mnie dokumenty mówią, że używaj finalizatorów tylko, jeśli to naprawdę konieczne), a następnie musi przesunąć te bloki na pozycję, aby stos był zagęszczony i zaktualizuj odniesienie do nowej lokalizacji bloku. Jak widać, to dużo pracy!
Doskonałe trafienia dla pamięci C ++ sprowadzają się do alokacji pamięci - gdy potrzebujesz nowego bloku, musisz przejść stertę w poszukiwaniu następnego wolnego miejsca, które jest wystarczająco duże, z mocno rozdrobnioną stertą, nie jest to tak szybkie jak GC „po prostu alokuj kolejny blok na końcu”, ale myślę, że nie jest on tak wolny, jak cała praca związana z zagęszczaniem GC, i można go złagodzić za pomocą wielu stosów bloków o stałej wielkości (znanych również jako pule pamięci).
Jest więcej ... jak ładowanie zestawów z GAC, które wymaga sprawdzania bezpieczeństwa, ścieżek sond (włącz sxstrace i po prostu spójrz, co się dzieje!) I innych innych nadinżynierii, które wydają się być znacznie bardziej popularne w java / .net niż C / C ++.
źródło
„Czy to po prostu dlatego, że C ++ jest kompilowany w asemblerze / kodzie maszynowym, podczas gdy Java / C # wciąż ma narzut przetwarzania kompilacji JIT w czasie wykonywania?” Zasadniczo tak!
Szybka uwaga, Java ma więcej kosztów ogólnych niż tylko kompilacja JIT. Na przykład, robi o wiele więcej sprawdzania dla ciebie (tak to robi rzeczy jak
ArrayIndexOutOfBoundsExceptions
iNullPointerExceptions
). Śmieciarka to kolejny znaczny narzut.Jest to dość szczegółowe porównanie tutaj .
źródło
Pamiętaj, że poniższe zestawienie porównuje jedynie różnicę między kompilacją natywną a kompilacją JIT i nie obejmuje specyfiki konkretnego języka lub frameworka. Mogą istnieć uzasadnione powody, aby wybrać konkretną platformę poza tym.
Gdy twierdzimy, że kod macierzysty jest szybszy, mówimy o typowym przypadku użycia kodu skompilowanego w sposób natywny w porównaniu do kodu skompilowanego w JIT, w którym typowe użycie aplikacji skompilowanej w JIT ma być uruchamiane przez użytkownika, z natychmiastowymi rezultatami (np. Brak najpierw czeka na kompilatorze). W takim przypadku nie sądzę, aby ktokolwiek mógł z prostą miną twierdzić, że skompilowany kod JIT może dopasować lub pokonać kod natywny.
Załóżmy, że mamy program napisany w jakimś języku X i możemy go skompilować za pomocą natywnego kompilatora i ponownie za pomocą kompilatora JIT. Każdy przepływ pracy obejmuje te same etapy, które można uogólnić jako (Kod -> Przedstawiciel pośredni -> Kod maszynowy -> Wykonanie). Duża różnica między dwoma to, które etapy widzi użytkownik, a które programista. W przypadku kompilacji natywnej programista widzi wszystko oprócz etapu wykonania, ale w przypadku rozwiązania JIT kompilacja do kodu maszynowego jest postrzegana przez użytkownika, oprócz wykonywania.
Twierdzenie, że A jest szybsze niż B, odnosi się do czasu potrzebnego na uruchomienie programu, jak widzi użytkownik . Jeśli założymy, że oba fragmenty kodu działają identycznie na etapie wykonywania, musimy założyć, że przepływ pracy JIT jest wolniejszy dla użytkownika, ponieważ musi on także zobaczyć czas T kompilacji do kodu maszynowego, gdzie T> 0. Tak , aby każda możliwość, aby przepływ pracy JIT działał tak samo jak natywny przepływ pracy, dla użytkownika, musimy skrócić czas wykonywania kodu, tak aby wykonanie + kompilacja do kodu maszynowego były niższe niż tylko etap wykonania natywnego przepływu pracy. Oznacza to, że musimy lepiej zoptymalizować kod w kompilacji JIT niż w kompilacji natywnej.
Jest to jednak raczej niewykonalne, ponieważ aby wykonać niezbędne optymalizacje w celu przyspieszenia wykonywania, musimy poświęcić więcej czasu na kompilację do etapu kodu maszynowego, a zatem za każdym razem, gdy zaoszczędzimy w wyniku zoptymalizowania kodu, zostanie utracony, ponieważ dodajemy go do kompilacji. Innymi słowy, „powolność” rozwiązania opartego na JIT nie wynika wyłącznie z dodatkowego czasu na kompilację JIT, ale kod wygenerowany przez tę kompilację działa wolniej niż rozwiązanie rodzime.
Posłużę się przykładem: Zarejestruj przydział. Ponieważ dostęp do pamięci jest kilka tysięcy razy wolniejszy niż dostęp do rejestrów, najlepiej, gdy jest to możliwe, chcemy korzystać z rejestrów i mieć jak najmniej dostępów do pamięci, ale mamy ograniczoną liczbę rejestrów i musimy przelać stan do pamięci, gdy jest to potrzebne rejestr. Jeśli użyjemy algorytmu alokacji rejestru, którego obliczenie zajmuje 200 ms, w wyniku czego zaoszczędzimy 2 ms czasu wykonania - nie wykorzystujemy najlepiej czasu kompilatora JIT. Rozwiązania takie jak algorytm Chaitin, który może generować wysoce zoptymalizowany kod, są nieodpowiednie.
Rolą kompilatora JIT jest jednak zachowanie najlepszej równowagi między czasem kompilacji a jakością produkowanego kodu, z dużym naciskiem na szybki czas kompilacji, ponieważ nie chcesz, aby użytkownik czekał. Wydajność wykonywanego kodu jest mniejsza w przypadku JIT, ponieważ natywny kompilator nie jest związany (dużo) czasem w optymalizowaniu kodu, więc można swobodnie korzystać z najlepszych algorytmów. Możliwość, że ogólna kompilacja + wykonanie kompilatora JIT może pobić tylko czas wykonania natywnie skompilowanego kodu, wynosi 0.
Ale nasze maszyny wirtualne nie ograniczają się tylko do kompilacji JIT. Korzystają z technik kompilacji Ahead-of-time, buforowania, wymiany na gorąco i optymalizacji adaptacyjnych. Zmodyfikujmy więc nasze twierdzenie, że wydajność jest tym, co widzi użytkownik, i ograniczmy ją do czasu potrzebnego na wykonanie programu (załóżmy, że skompilowaliśmy AOT). Możemy skutecznie sprawić, aby kod wykonawczy był równoważny natywnemu kompilatorowi (a może lepiej?). Wielkim twierdzeniem dla maszyn wirtualnych jest to, że mogą one być w stanie wytworzyć kod lepszej jakości niż natywny kompilator, ponieważ ma on dostęp do większej ilości informacji - informacji o uruchomionym procesie, takich jak częstotliwość wykonywania określonej funkcji. Maszyna wirtualna może następnie zastosować adaptacyjne optymalizacje do najbardziej niezbędnego kodu za pomocą wymiany na gorąco.
Jest jednak problem z tym argumentem - zakłada on, że optymalizacja sterowana profilem i tym podobne jest czymś wyjątkowym dla maszyn wirtualnych, co nie jest prawdą. Możemy zastosować go również do kompilacji natywnej - kompilując naszą aplikację z włączonym profilowaniem, rejestrując informacje, a następnie ponownie kompilując aplikację z tym profilem. Prawdopodobnie warto również zauważyć, że wymiana kodu na gorąco nie jest czymś, co może zrobić tylko kompilator JIT, możemy to zrobić dla kodu natywnego - chociaż rozwiązania oparte na JIT są łatwiej dostępne i znacznie łatwiejsze dla programisty. Główne pytanie brzmi zatem: czy maszyna wirtualna może nam dostarczyć informacji, których natywna kompilacja nie może uzyskać, co może zwiększyć wydajność naszego kodu?
Sam tego nie widzę. Możemy zastosować większość technik typowej maszyny wirtualnej również do kodu natywnego - chociaż proces jest bardziej zaangażowany. Podobnie możemy zastosować wszelkie optymalizacje natywnego kompilatora z powrotem do maszyny wirtualnej, która korzysta z kompilacji AOT lub optymalizacji adaptacyjnych. Rzeczywistość jest taka, że różnica między rodzimym kodem uruchomionym a maszyną wirtualną nie jest tak duża, jak nam się wydaje. Ostatecznie prowadzą do tego samego rezultatu, ale przyjmują inne podejście, aby się tam dostać. Maszyna wirtualna używa iteracyjnego podejścia do tworzenia zoptymalizowanego kodu, w którym natywny kompilator oczekuje go od samego początku (i można go ulepszyć za pomocą iteracyjnego podejścia).
Programista C ++ może argumentować, że potrzebuje optymalizacji od samego początku i nie powinien czekać na maszynę wirtualną, aby dowiedzieć się, jak to zrobić, jeśli w ogóle. Jest to prawdopodobnie ważny punkt w naszej obecnej technologii, ponieważ obecny poziom optymalizacji w naszych maszynach wirtualnych jest gorszy od tego, co oferują natywne kompilatory - ale nie zawsze tak będzie, jeśli rozwiązania AOT w naszych maszynach wirtualnych poprawią się itp.
źródło
Ten artykuł jest streszczeniem zestawu postów na blogu, które próbują porównać szybkość c ++ vs c # oraz problemów, które musisz rozwiązać w obu językach, aby uzyskać kod o wysokiej wydajności. Podsumowanie brzmi: „Twoja biblioteka ma większe znaczenie niż cokolwiek innego, ale jeśli jesteś w c ++, możesz to przezwyciężyć”. lub „nowoczesne języki mają lepsze biblioteki i dzięki temu osiągają szybsze wyniki przy mniejszym wysiłku”, w zależności od filozofii.
źródło
Myślę, że prawdziwym pytaniem nie jest „co jest szybsze?” ale „który ma najlepszy potencjał do zwiększenia wydajności?”. Patrząc na te warunki, C ++ wyraźnie wygrywa - jest skompilowany do natywnego kodu, nie ma JITtingu, jest niższy poziom abstrakcji itp.
To dalekie od pełnej historii.
Ponieważ C ++ jest kompilowany, wszelkie optymalizacje kompilatora muszą być wykonywane w czasie kompilacji, a optymalizacje kompilatora odpowiednie dla jednej maszyny mogą być całkowicie niepoprawne dla innej. Jest tak również w przypadku, gdy wszelkie globalne optymalizacje kompilatora mogą i będą faworyzować niektóre algorytmy lub wzorce kodowe nad innymi.
Z drugiej strony, program JITted zoptymalizuje się w czasie JIT, więc może wyciągnąć pewne sztuczki, których nie może skompilować program wstępnie skompilowany, i może dokonać bardzo szczegółowych optymalizacji dla komputera, na którym faktycznie działa i kodu, na którym faktycznie działa. Po przejściu przez początkowy narzut JIT może w niektórych przypadkach być szybszy.
W obu przypadkach rozsądna implementacja algorytmu i inne przypadki, że programista nie jest głupi, będą prawdopodobnie znacznie ważniejszymi czynnikami - na przykład, możliwe jest na przykład napisanie całkowicie pozbawionego mózgu kodu ciągowego w C ++, który będzie otoczony przez nawet interpretowany język skryptowy.
źródło
-march=native
). - „to niższy poziom abstrakcji” nie jest tak naprawdę prawdą. C ++ używa tak samo abstrakcyjnych poziomów jak Java (lub w rzeczywistości wyższych: programowanie funkcjonalne? Metaprogramowanie szablonu?), Po prostu implementuje abstrakcje mniej „czysto” niż Java.Kompilacja JIT faktycznie ma negatywny wpływ na wydajność. Jeśli zaprojektujesz „idealny” kompilator i „idealny” kompilator JIT, pierwsza opcja zawsze zyska na wydajności.
Zarówno Java, jak i C # są tłumaczone na języki pośrednie, a następnie kompilowane w natywnym kodzie w czasie wykonywania, co zmniejsza wydajność.
Ale teraz różnica nie jest tak oczywista dla C #: Microsoft CLR produkuje inny natywny kod dla różnych procesorów, dzięki czemu kod jest bardziej wydajny dla komputera, na którym działa, co nie zawsze jest wykonywane przez kompilatory C ++.
PS C # jest napisany bardzo skutecznie i nie ma wielu warstw abstrakcji. Nie dotyczy to Javy, która nie jest tak wydajna. Tak więc w tym przypadku, z jego eleganckim CLR, programy C # często wykazują lepszą wydajność niż programy C ++. Aby uzyskać więcej informacji na temat .Net i CLR , zobacz „CLR przez C #” Jeffreya Richtera .
źródło