Dlaczego C jest tak szybki i dlaczego inne języki nie są tak szybkie lub szybsze? [Zamknięte]


208

Słuchając podcastu StackOverflow, pojawia się myśl, że „prawdziwi programiści” piszą w C, i że C jest o wiele szybszy, ponieważ jest „blisko maszyny”. Pozostawiając poprzednie stwierdzenie dla innego postu, co jest specjalnego w C, które pozwala mu być szybsze niż inne języki? Lub inaczej: co powstrzyma inne języki przed kompilacją do wersji binarnej, która działa tak samo szybko jak C?


6
Czy możesz wymienić, który konkretny program o tym mówił? Chciałbym to usłyszeć.
Giovanni Galbo

2
Naprawdę zaskoczony, jak źle odpowiedziano na to pytanie (większość odpowiedzi ignoruje podstawowe różnice między językami kompilowanymi i interpretowanymi itp., Wiem o JIT yada yada yada) i ile osób zajmuje pozycję w obronie swojego ulubionego języka (chłopiec FORTRAN potrzebuje wziąć pigułkę).
Tim Ring,

Nie zapomnij o asemblerze. Nic nie jest szybsze ani bardziej kompaktowe niż exe montowane w zespole. Asembler jest prawie całkowicie binarny, więc nie jest uprzedzony najszybszym językiem.
KKZiomek

3
C jest najszybszy, ponieważ jest to prędkość światła i względność?
Spotkaj się Taraviya

Oczywiście błędem jest, że C jest najszybszym językiem programu. Żaden język programowania nie zbliża się do prędkości FORTH. FORTH służy do wyzwalania bomb nuklearnych, jest to język programowania w większości satelitów, główny język programu w Międzynarodowej Stacji Kosmicznej, a także w CERN i ITER. Porównywałem prędkość między Microsoft C (różne wersje) i FORTH. Ziewaj do C ...
Scoobeedo Cool

Odpowiedzi:


200

Nie ma nic szczególnego w C. Jest to jeden z powodów, dla których jest szybki.

Nowsze języki obsługujące zbieranie śmieci , dynamiczne pisanie i inne funkcje ułatwiające programistom pisanie programów.

Problem polega na tym, że istnieje dodatkowy narzut przetwarzania, który obniży wydajność aplikacji. C nie ma tego, co oznacza, że ​​nie ma narzutu, ale to oznacza, że ​​programista musi być w stanie przydzielić pamięć i zwolnić je, aby zapobiec wyciekom pamięci , i musi radzić sobie ze statycznym typowaniem zmiennych.

To powiedziawszy, wiele języków i platform, takich jak Java (z jej wirtualną maszyną Java ) i .NET (z jego wspólnym środowiskiem uruchomieniowym) poprawiły wydajność na przestrzeni lat dzięki takim rozwiązaniom, jak kompilacja just-in-time, która wytwarza natywny kod maszynowy z bytecode w celu osiągnięcia wyższej wydajności.


3
wyrzucanie elementów bezużytecznych może być szybsze niż ręczne zarządzanie pamięcią (w przypadku programów krótkotrwałych i / lub dużej ilości pamięci). GC umożliwia prosty i szybki przydział, a program nie poświęca czasu na zwalnianie rzeczy.
Kornel

2
Programy C zazwyczaj przydzielają i zwalniają pamięć według potrzeb. To jest nieefektywne. Dobra maszyna wirtualna będzie alokować i zwalniać w dużych porcjach, co w wielu przypadkach daje duży wzrost wydajności.
skaffman

60
Nic nie stoi na przeszkodzie, aby program C wykonywał ten sam przydział fragmentów i wyrzucanie elementów bezużytecznych, poza tym, że jest „twardy”.
ephemient

Bardzo dobrze powiedziane, ale jak lubi Rob Allen, C zapewnia również mniej abstrakcji niż Java lub .NET, co skutkuje mniejszą liczbą tłumaczeń (co jest mniej prawdziwe dzisiaj z powodu kompilacji just-in-time (JIT), jak powiedziałeś)
Gab Royer

5
PorneL, ręczne zarządzanie i rozsądne przydziały zawsze przewyższają każdy system GC, jeśli są używane prawidłowo i dużo uwagi jest poświęcone, masz absolutną wiedzę o swoich wzorcach użytkowania, GC nie, a systemy GC dodają narzut
Ion Todirel

89

Projektanci C dokonali kompromisu. To znaczy, podjęli decyzję o podniesieniu prędkości ponad bezpieczeństwo. C nie chce

  • Sprawdź granice indeksu tablicy
  • Sprawdź niezainicjowane wartości zmiennych
  • Sprawdź, czy nie ma wycieków pamięci
  • Sprawdź zerowe odwołanie do wskaźnika

Podczas indeksowania do tablicy w Javie wymaga ona wywołania metody na maszynie wirtualnej, sprawdzania powiązań i innych sprawdzeń poprawności. Jest to ważne i absolutnie w porządku , ponieważ zwiększa bezpieczeństwo tam, gdzie jest to konieczne. Ale w C nawet dość trywialne rzeczy nie są bezpieczne. Na przykład C nie wymaga memcpy do sprawdzenia, czy regiony do skopiowania nakładają się. To nie zaprojektowany jako język do programowania duży aplikacji biznesowych.

Ale te decyzje projektowe są nie błędy w języku C . Są one zaprojektowane, ponieważ pozwalają kompilatorom i autorom bibliotek uzyskać wszelką wydajność z komputera. Oto duch C, w jaki sposób wyjaśnia to C Rationale :

Kod C może być nieprzenośny. Chociaż starał się dać programistom możliwość pisania prawdziwie przenośnych programów, Komitet nie chciał zmusić programistów do przenośnego pisania, aby wykluczyć użycie C jako `` wysokiego poziomu asemblera '': możliwość pisania specyficznego dla maszyny kod jest jedną z mocnych stron C.

Zachowaj ducha C. Komitet utrzymywał, że głównym celem jest zachowanie tradycyjnego ducha C. Istnieje wiele aspektów ducha C, ale istotą jest sentyment społeczności do podstawowych zasad, na których opiera się język C. Niektóre aspekty ducha C można streścić w frazach takich jak

  • Zaufaj programistom.
  • Nie powstrzymuj programisty przed robieniem tego, co trzeba zrobić.
  • Utrzymuj język mały i prosty.
  • Podaj tylko jeden sposób wykonania operacji.
  • Zrób to szybko, nawet jeśli nie ma gwarancji, że będzie przenośny.

Ostatnie przysłowie wymaga małego wyjaśnienia. Potencjał wydajnego generowania kodu jest jedną z najważniejszych zalet C. Aby pomóc uniknąć eksplozji kodu w przypadku operacji, która wydaje się być bardzo prostą operacją, wiele operacji definiuje się tak, jak robi to sprzęt komputera docelowego, a nie przez ogólna zasada abstrakcyjna. Przykład tej gotowości do życia z tym, co robi maszyna, można znaleźć w regułach rządzących rozszerzaniem obiektów char do użycia w wyrażeniach: to, czy wartości obiektów char rozszerzają się na liczby podpisane, czy niepodpisane, zazwyczaj zależy od tego, która operacja bajtowa jest większa wydajny na maszynie docelowej.


51
C sprawdza, czy deref zeruje wskaźnik, gwałtownie zawieszając się :-). Od czasu do czasu sprawdza także indeksy tablic poza zakresem i niezainicjowane zmienne, psując ramki i dane stosu. Niestety sprawdza je w czasie wykonywania.
paxdiablo

18
Nie powiedziałbym, że C nie jest bezpieczne, co brzmi jak to, co sugerujesz. Zakłada, że ​​nie jesteś idiotą. Jeśli skierujesz broń w dół i zestrzelisz się w stopę, C z przyjemnością to zrobi i pozwoli ci to zrobić, ponieważ zakłada, że ​​jesteś mądrzejszy niż jest w rzeczywistości. To niekoniecznie zła rzecz.
Bob Somers

19
@Bob: Dokładnie. Mówienie C nie jest bezpieczne, ponieważ pozwala robić niebezpieczne rzeczy, jest jak mówienie, że samochód nie jest bezpieczny, ponieważ pozwoli ci zjechać z klifu. C jest tak samo bezpieczny jak osoba prowadząca pojazd (ale jest wielu niebezpiecznych kierowców).
Robert Gamble

5
Bob, popełnianie błędów takich jak przepełnienie bufora nie oznacza, że ​​jesteś idiotą. To po prostu oznacza, że ​​nadal jesteś człowiekiem. Zdaję sobie sprawę, że C i C ++ nie są złe (bardzo je lubię).
Johannes Schaub - litb

4
@ JohannesSchaub-litb C jest idealny do programowania aplikacji na dużą skalę. Jeśli trudno jest tworzyć projekty większe niż świat

75

Jeśli spędzasz miesiąc, aby zbudować coś w C, które działa w 0,05 sekundy, a ja spędzam dzień pisząc to samo w Javie i działa w 0,10 sekundy, to czy C jest naprawdę szybsze?

Ale aby odpowiedzieć na twoje pytanie, dobrze napisany kod C będzie generalnie działał szybciej niż dobrze napisany kod w innych językach, ponieważ część pisania „dobrze” kodu C obejmuje robienie ręcznych optymalizacji na poziomie zbliżonym do maszynowego.

Chociaż kompilatory są naprawdę bardzo sprytne, nie są jeszcze w stanie twórczo wymyślić kodu, który konkuruje z algorytmami masowanymi ręcznie (zakładając, że „ręce” należą do dobrego programisty w języku C).

Edytować:

Wiele komentarzy jest w stylu „Piszę w C i nie myślę o optymalizacjach”.

Ale biorąc konkretny przykład z tego postu :

W Delphi mógłbym napisać:

function RemoveAllAFromB(a, b: string): string;
var
  before, after :string;
begin
  Result := b;
  if 0 < Pos(a,b) then begin
    before := Copy(b,1,Pos(a,b)-Length(a));
    after := Copy(b,Pos(a,b)+Length(a),Length(b));
    Result := before + after;
    Result := RemoveAllAFromB(a,Result);  //recursive
  end;
end;

i w CI napisz to:

char *s1, *s2, *result; /* original strings and the result string */
int len1, len2; /* lengths of the strings */
for (i = 0; i < len1; i++) {
   for (j = 0; j < len2; j++) {
     if (s1[i] == s2[j]) {
       break;
     }
   }
   if (j == len2) {  /* s1[i] is not found in s2 */
     *result = s1[i]; 
     result++; /* assuming your result array is long enough */
   }
}

Ale ile jest optymalizacji w wersji C? Podejmujemy wiele decyzji dotyczących implementacji, o których nie myślę w wersji Delphi. Jak wdrażany jest ciąg? W Delphi nie widzę tego. W C zdecydowałem, że będzie to wskaźnik do tablicy liczb całkowitych ASCII, którą nazywamy znakami. W C testujemy istnienie postaci pojedynczo. W Delphi używam Poz.

A to tylko mały przykład. W dużym programie programista C musi podejmować tego rodzaju decyzje niskiego poziomu przy każdej linii kodu. Daje to ręcznie wykonany, zoptymalizowany ręcznie plik wykonywalny.


45
Szczerze mówiąc, nie ma wiele, które zajęłyby jeden miesiąc w C, co zajęłoby tylko jeden dzień w Javie, a wykonanie zajmuje tylko 0,05 sekundy (tj. Mały program).
dreamlax

12
Programowałem w C od lat i prawie nigdy nie muszę dokonywać żadnych optymalizacji, o których sugerujesz. Przeniesiłem wiele programów do C (głównie z Perla) i ogólnie widzę 10-krotny wzrost prędkości i znaczne zmniejszenie zużycia pamięci bez żadnych ręcznie kodowanych optymalizacji.
Robert Gamble

1
Oczywiście są pewne rzeczy, które mogą wymagać znacznie dłuższego czasu programowania w C z powodu braku istniejących udogodnień, więc jest to kompromis między wydajnością komputera a wydajnością programisty (między innymi), dlatego nie wszyscy programujemy wszystko w C.
Robert Gamble

4
Stworzyłem programy C ++, które przetwarzają tysiące linii danych w krótszym czasie niż programy Java lub .NET. Jest to jedna z frustracji, które odczuwam w przypadku bardziej nowoczesnych języków. C jest świetny dla programów lean, które wymagają minimalnych wymagań w czasie wykonywania. PowerBasic jest także do tego świetny.
bruceatk

35
Mówisz, że program, który zajmuje miesiąc w C i jest dwa razy szybszy niż program napisany w Javie, którego pisanie zajmuje tylko jeden dzień, nie jest wart? Co jeśli ten program musi być uruchamiany ponad 500 000 000 razy dziennie? Bycie dwukrotnie szybszym jest niezwykle ważne. Jeśli działa na tysiącach lub milionach procesorów, oszczędności w dodatkowym miesiącu programowania, aby osiągnąć dwukrotnie wyższą wydajność, będą ogromne. Zasadniczo przed wybraniem platformy programistycznej musisz znać / zrozumieć skalę wdrożenia.
nicerobot

49

Nie widziałem go już, więc powiem to: C wydaje się być szybciej, ponieważ prawie wszystko jest napisany w C .

Java jest zbudowana na C, Python na C (lub Java, .NET itp.), Perl jest itp. System operacyjny jest napisany w C, maszyny wirtualne są napisane w C, kompilatory są napisane w C, tłumacze są napisani w C. Niektóre rzeczy są nadal napisane w języku asemblera, który zwykle jest jeszcze szybszy. Coraz więcej rzeczy jest pisanych w czymś innym, co samo jest napisane w C.

Każda instrukcja napisana w innych językach (nie asemblerowych) jest zwykle implementowana pod spodem jako kilka instrukcji w C, które są kompilowane do natywnego kodu maszynowego. Ponieważ te inne języki zwykle istnieją w celu uzyskania wyższego poziomu abstrakcji niż C, te dodatkowe stwierdzenia wymagane w C zwykle koncentrują się na zwiększeniu bezpieczeństwa, złożoności i obsłudze błędów. Często są to dobre rzeczy, ale mają swój koszt , a ich nazwy to szybkość i rozmiar .

Osobiście pisałem w dosłownie kilkudziesięciu językach obejmujących większość dostępnego spektrum i osobiście szukałem magii, o której wspominacie:

Jak mogę dostać moje ciasto i je też zjeść? Jak mogę grać z abstrakcjami na wysokim poziomie w moim ulubionym języku, a następnie zejść do ziarenka C w celu uzyskania szybkości?

Po kilku latach badań moja odpowiedź brzmi: Python (na C). Możesz rzucić okiem. Nawiasem mówiąc, możesz również przejść do Asemblera z Pythona (z niewielką pomocą specjalnej biblioteki).

Z drugiej strony zły kod można napisać w dowolnym języku . Dlatego kod C (lub asembler) nie jest automatycznie szybszy. Podobnie, niektóre sztuczki optymalizacyjne mogą zbliżyć części kodu języka wyższego poziomu do poziomu wydajności surowego C. Jednak w przypadku większości aplikacji program spędza większość czasu na ludziach lub sprzęcie, więc różnica naprawdę nie ma znaczenia.

Cieszyć się.


10
To tak naprawdę nie dotyczy języków skompilowanych w JIT. To nie jest tak, że mój C # jest kompilowany do IL, która jest tłumaczona na C, który jest kompilowany do kodu maszynowego. Nie, IL jest kompilowany przez JIT - a język implementacji JIT jest w tym momencie nieistotny. Po prostu produkuje kod maszynowy.
Jon Skeet

3
Boże, zabraniaj mi kwestionować legendarnego Jona Skeeta, ale wydaje się całkowicie istotne, że produkowany kod maszynowy jest przeznaczony dla C # zamiast C, więc jest „wyższy poziom”, ma więcej funkcji, ma kontrole bezpieczeństwa itp. I będzie bądź zatem wolniejszy niż „równoważny” C.
Rob Williams

3
@Jon: Chciałem powiedzieć coś podobnego, ale ta kwestia jest w pewnym sensie słuszna, ponieważ wiele podstawowych komponentów biblioteki .NET jest naprawdę napisanych w C, a zatem mają ograniczenia prędkości C. Ciekawie będzie zobaczyć, jak to się zmieni w przyszłości.
Konrad Rudolph

1
Wydaje się to niewłaściwe, kompilator / interpreter / vms innego języka są często, ale nie zawsze, napisane w c (lub przynajmniej dla najniższej warstwy), ponieważ c jest dość szybkie (aw wielu przypadkach najszybsze).
Roman A. Taycher

2
Ta odpowiedź nie jest prawdziwa. Jak wspomniano powyżej, nie dotyczy to języków JIT, ale również nie dotyczy języków z ich własnymi kompilatorami (które, jeśli włożono by w nie niezwykły wysiłek, mogłyby wygenerować szybszy kod niż współczesne kompilatory C). Jedyną pozostałą klasą języków są języki interpretowane, a te nie są wolniejsze niż C tylko dlatego, że są napisane w C, ale ponieważ narzut związany z tłumaczeniem, bez względu na to, jak je pokroić, a nawet jeśli tłumacz został napisany w asemblerze , Jest wielki.
Score_Under

38

Jest tam wiele pytań - głównie na te, na które nie mam kwalifikacji. Ale w tym ostatnim:

co powstrzyma inne języki przed kompilacją do binarnego, który działa tak samo szybko jak C?

Jednym słowem, abstrakcja.

C to tylko jeden lub dwa poziomy abstrakcji od języka maszynowego. Języki Java i .Net znajdują się na minimum 3 poziomach abstrakcji od asemblera. Nie jestem pewien co do Pythona i Ruby.

Zazwyczaj im więcej zabawek dla programistów (złożone typy danych itp.), Tym bardziej jesteś w języku maszynowym i tym więcej trzeba wykonać tłumaczenia.

Jestem tu i tam, ale to jest podstawowa istota.

Aktualizacja ------- Ten post zawiera kilka dobrych komentarzy z dodatkowymi szczegółami.


3
Z technicznego punktu widzenia Java i .Net są nieskończenie oderwane od języka maszynowego maszyny, na której działają. Działają na maszynach wirtualnych. Nawet w przypadku JIT oryginalny kod musi być masowo masowany, aby uzyskać coś przypominającego natywny kod maszynowy.
jmucchiello

1
Kod .net nie działa na maszynie wirtualnej. Działa jako instrukcje natywne na dowolnej platformie procesorowej (32-bit x86, 64-bit x86 lub IA64).
Robert C. Barth

11
@Robert: .net nie używa maszyny wirtualnej. Kod .net jest kompilowany do kodu bajtowego, który jest wykonywany przez maszynę wirtualną. Maszyna wirtualna konwertuje kod bajtowy na instrukcje natywne w czasie wykonywania.
Robert Gamble

3
To bardzo ważne, aby pamiętać, że Java i inne abstrakcje język OO wpłynęły zestawów instrukcji procesora. Nowsze procesory mają instrukcje, dzięki którym Java działa szybciej, jeśli wirtualna maszyna języka Java zna te optymalizacje i używa ich. Nie jest ogromny, ale jest pomocny.
Adam Davis,


35

C nie jest tak szybki, że model kosztów tego C jest przejrzysty . Jeśli program C jest wolny, działa w oczywisty sposób: wykonując wiele instrukcji. W porównaniu z kosztem operacji w C operacje wysokiego poziomu na obiektach (szczególnie odbicie) lub łańcuchach mogą mieć koszty, które nie są oczywiste.

Dwa języki, które generalnie kompilują się do plików binarnych, które są równie szybkie jak C, to Standard ML (przy użyciu kompilatora MLton ) i Objective Caml . Jeśli przejrzysz grę z testami porównawczymi, zauważysz, że w przypadku niektórych testów porównawczych, takich jak drzewa binarne, wersja OCaml jest szybsza niż C. (Nie znalazłem żadnych wpisów MLton). Ale nie traktuj strzelaniny zbyt poważnie; jak mówi gra, wyniki często odzwierciedlają wysiłek włożony w dostosowanie kodu.


Można napisać nieoczywisty kosztowny kod w dowolnym języku. Tyle, że w niektórych językach najpierw musisz napisać wewnętrzny wariant Lisp lub Forth…
Donal Fellows

Również Rust odpowiada C w testach porównawczych.
surowy

18

C nie zawsze jest szybszy.

C jest wolniejszy niż, na przykład Modern Fortran.

W niektórych przypadkach C jest często wolniejszy niż Java. (Zwłaszcza po uruchomieniu kompilatora JIT w kodzie)

C pozwala na aliasing wskaźnika, co oznacza, że ​​niektóre dobre optymalizacje nie są możliwe. Zwłaszcza gdy masz wiele jednostek wykonawczych, powoduje to przeciąganie pobierania danych. Ow.

Założenie, że arytmetyka wskaźników działa naprawdę powoduje powolną nadmierną wydajność w niektórych rodzinach procesorów (szczególnie PIC!). Kiedyś ssała dużą na segmentowanym x86.

Zasadniczo, gdy otrzymujesz jednostkę wektorową lub kompilator równoległy, C śmierdzi, a nowoczesny Fortran działa szybciej.

Sztuczki programisty C, takie jak thunking (modyfikowanie pliku wykonywalnego w locie), powodują zatrzymanie pobierania procesora.

Masz dryf?

A nasz dobry przyjaciel, x86, wykonuje zestaw instrukcji, który obecnie ma niewielki związek z rzeczywistą architekturą procesora. Rejestry cienia, optymalizatory przechowujące ładunki, wszystko w CPU. Zatem C jest blisko wirtualnego metalu. Prawdziwy metal, Intel nie pozwala ci zobaczyć. (Historycznie procesory VLIW były trochę bolesne, więc może nie jest tak źle.)

Jeśli programujesz w C na wysokowydajnym procesorze DSP (może TI DSP?), Kompilator musi wykonać pewne trudne czynności, aby rozwinąć C w wielu równoległych jednostkach wykonawczych. W takim przypadku C nie jest bliski metalowi, ale jest blisko kompilatora, który dokona optymalizacji całego programu. Dziwne.

Na koniec niektóre procesory (www.ajile.com) obsługują sprzętowe kody bajtowe Java. C użyłby PITA na tym CPU.


1
Kiedy ostatnio napisano thunking, w C? Nowoczesne x86 jest interfejsem do projektu głównie RISC, ale to niewiele ma wspólnego z VLIW ...
Calyth

7
Znaczna część twojego postu ignoruje istnienie C99. Ponadto wiele kompilatorów C / C ++ oferuje jako rozszerzenie rozszerzenie C99 (nie zapewnia aliasingu wskaźnika).
Evan Teran

Zakładam, że wszyscy śledzą / przechodzą do śledzenia 25 najlepszych CWE / SANS i unikają tworzenia nowych projektów w C. Więc nie ma zielonych pól C, tak mało, albo C99.
Tim Williscroft,

2
Czy możesz pokazać przykład, gdy c jest wolniejszy niż współczesny Fortenberry?
Adam

Nie jestem pewien, czy kiedykolwiek był czas, kiedy kompilatory C konkurowały bardzo dobrze z najlepszymi kompilatorami Fortran. Oczywiście jest dużo kodu, którego nie chciałbyś pisać w FORTRAN 77 (nie mówiąc już o 66), ale nowsze standardy Fortran są coraz bardziej przyjemne.
tfb

11

co powstrzyma inne języki przed kompilacją do binarnego, który działa tak samo szybko jak C?

Nic. Współczesne języki, takie jak Java lub .NET, są bardziej zorientowane na wydajność programisty niż na wydajność. Sprzęt jest teraz tani. Również kompilacja do pośredniej reprezentacji daje wiele bonusów, takich jak bezpieczeństwo, przenośność itp. .NET CLR może korzystać z innego sprzętu - na przykład nie trzeba ręcznie optymalizować / rekompilować programu, aby użyć zestawu instrukcji SSE.


tutaj argumentowałbym na przenośność. Jeśli chcesz naprawdę przenośnego kodu, napiszesz go w C, a nie w innym języku. Mamy kod działający w około 25 systemach operacyjnych. Począwszy od dos i threadX i kończąc na Linux / XP pokaż mi inny język, który może to zrobić :)
Ilya

1
@Ilya Nie zgadzam się. Równie łatwe jest pisanie nieprzenośnego kodu w C. Spójrz, jak bolesne było portowanie do wersji 64-bitowej. Wersje językowe kodu bajtowego mogą działać na różnych platformach, jeśli masz odpowiedni interpreter kodu bajtowego.
Calyth

1
@IIya, przenośny kod C jest raczej wyjątkiem niż regułą. Przeniesiłem kod C między różne platformy sprzętowe / programowe i wiem, że to koszmar.
aku

Nawet w przypadku słów na PC tak nie jest. Spójrz w rzeczywistość na większość aplikacji międzyplatformowych napisanych w c / c ++, trochę w Javie. W przypadku wbudowanego programowania niskiego poziomu nie ma innych przypadków. C jest de facto najbardziej przenośnym językiem.
Ilya,

@aku -> PORTOWANIE zły kod może być katastrofą, zgadzam się. Pisanie przenośnego kodu w ADVANCE - C jest najlepszym wyborem. Powiedziałbym, że C ++ jest opcją, ale przechodząc na platformę wbudowaną, zawsze znajdziesz porządny kompilator C, dla c ++ możesz znaleźć siebie bez kompilatora.
Ilya,

8

Głównymi czynnikami są to, że jest to język o typie statycznym i skompilowany do kodu maszynowego. Ponadto, ponieważ jest to język niskiego poziomu, na ogół nie robi nic, o czym mu nie powiesz.

Są to inne czynniki, które przychodzą na myśl.

  • Zmienne nie są inicjowane automatycznie
  • Brak ograniczeń sprawdzania tablic
  • Niesprawdzona manipulacja wskaźnikiem
  • Brak sprawdzania przepełnienia liczb całkowitych
  • Zmienne o typie statycznym
  • Wywołania funkcji są statyczne (chyba że używasz wskaźników funkcji)
  • Autorzy kompilatorów mieli dużo czasu na ulepszenie kodu optymalizacyjnego. Ponadto, ludzie programują w C w celu uzyskania najlepszej wydajności, więc istnieje presja na optymalizację kodu.
  • Części specyfikacji języka są zdefiniowane w implementacji, więc kompilatory mogą robić rzeczy w najbardziej optymalny sposób

Jednak większość języków o typie statycznym można skompilować równie szybko lub szybciej niż C, zwłaszcza jeśli mogą przyjąć założenia, że ​​C nie może z powodu aliasingu wskaźnika itp.


C niski poziom? Wydaje mi się, że ma to teraz znaczenie względne, w porównaniu do Javy tak, ale w porównaniu do zestawu nie. Dobry post, zmusił mnie do myślenia.
Mark

Masz rację, to zdecydowanie względne. Chodzi mi o to, że jest „blisko maszyny” i nie pomaga ci w zarządzaniu pamięcią ani śledzeniu rozmiarów macierzy.
Matthew Crumley,

2
C to język niskiego poziomu. C zawsze był językiem niskiego poziomu. Ręcznie przetłumaczysz kod C na asembler.
Robert C. Barth

2
@Robert: C był kiedyś uważany za język wysokiego poziomu, ponieważ w porównaniu do asemblera (który był bardzo powszechny), tak było. Jest uważany za język niskiego poziomu w porównaniu z większością obecnie używanych języków.
Robert Gamble

Szczerze mówiąc, jest to bardzo stronnicza odpowiedź. Cholera w pobliżu wszystkich programistów C sprawdza granice itp. Jednak C wciąż jest DUŻO szybszy niż C ++.
MarcusJ

8

Chyba zapomniałeś, że język asemblera to także język :)

Ale poważnie, programy C są szybsze tylko wtedy, gdy programista wie, co robi. Możesz łatwo napisać program C, który działa wolniej niż programy napisane w innych językach, które wykonują to samo zadanie.

Powodem, dla którego C jest szybszy, jest to, że jest zaprojektowany w ten sposób. Pozwala ci robić wiele rzeczy „niższego poziomu”, które pomagają kompilatorowi zoptymalizować kod. Lub, powiedzmy, programista jest odpowiedzialny za optymalizację kodu. Ale często jest to dość trudne i podatne na błędy.

Inne języki, jak już wspomniano, koncentrują się bardziej na produktywności programisty. Powszechnie uważa się, że czas programisty jest znacznie droższy niż czas maszynowy (nawet w dawnych czasach). Dlatego rozsądne jest zminimalizowanie czasu, jaki programiści spędzają na pisaniu i debugowaniu programów zamiast czasu działania programów. Aby to zrobić, poświęcisz nieco to, co możesz zrobić, aby przyspieszyć program, ponieważ wiele rzeczy jest zautomatyzowanych.


3
Chociaż, jeśli napisałeś program raz w C i ponownie w asemblerze, wersja C prawdopodobnie byłaby szybsza, ponieważ kompilator jest mądrzejszy od ciebie.
mk12

7

W przeważającej części każda instrukcja C odpowiada bardzo niewielu instrukcjom asemblera. Zasadniczo piszesz kod maszynowy wyższego poziomu, więc masz kontrolę nad prawie wszystkim, co robi procesor. Wiele innych skompilowanych języków, takich jak C ++, ma wiele prostych instrukcji, które mogą przekształcić się w znacznie więcej kodu, niż myślisz (funkcje wirtualne, konstruktory kopiowania itp.). A języki interpretowane, takie jak Java lub Ruby, mają kolejną warstwę instrukcje, których nigdy nie widzisz - maszyna wirtualna lub tłumacz.


Niektóre z tych języków wysokiego poziomu są dumne z tego, że są w stanie usunąć większość cruft, które dodały. Rzeczy takie jak usuwanie kopii, optymalizacja wartości zwracanej, przenoszenie konstrukcji / przypisania itp. W C ++.
cmaster

Wszystko sprowadza się więc do liczby instrukcji asemblacji wygenerowanych z kodu. Im więcej instrukcji montażu w wierszu kodu wysokiego poziomu, tym bardziej spada wydajność?
ENDEESA

Może to być nadmierne uproszczenie, ale istnieje bezpośredni związek między liczbą instrukcji asemblera. Procesor ma minimalną ilość czasu na wykonanie każdej instrukcji. IMHO, język, który pozwala łatwo zrozumieć, ile instrukcji piszesz, pomaga pisać bardziej wydajny kod. (Istnieją inne koszty czasu procesora, takie jak brak rozgałęzienia i pamięci podręcznej, ale nawet tam, mniej abstrakcji pomaga wyjaśnić, co robi procesor).
AShelly


7

C ++ jest średnio szybszy (tak jak początkowo był w dużej mierze nadzbiorem C, choć istnieją pewne różnice). Jednak w przypadku określonych testów porównawczych często istnieje inny język, który jest szybszy.

https://benchmarksgame-team.pages.debian.net/benchmarksgame/

fannjuch-redux był najszybszy w Scali

n-bodyi fastabyli szybsi w Adzie.

spectral-norm był najszybszy w Fortranie.

reverse-complement, mandelbrotI pidigitsbyły najszybsze w ATS.

regex-dna był najszybszy w JavaScript.

chameneou-redux był najszybszy to Java 7.

thread-ring był najszybszy w Haskell.

Pozostałe testy porównawcze były najszybsze w C lub C ++.


„ponieważ jest to super zestaw C” - Nie, C ++ nie jest nadzbiorem C.
PP

1
extern "C"nie ma nic wspólnego z C ++ będącym nadzbiorem C.
PP

2
To tak, jakby powiedzieć, że system("bash script.sh");działa dla każdego skryptu bash, a zatem C jest nadzbiorem bash. extern "C"zapewnia połączenie C w C ++ z powodu zmiany nazwy. Podczas gdy wywoływanie X jako nadzbiór Y oznacza, że ​​wszystko, co można zrobić w Y, można również zrobić w X, co nie jest prawdą w C ++. Istnieje wiele konstrukcji językowych, które są poprawne w C, ale nie w C ++.
PP

1
@PP W standardzie C ++ nie ma nic, co upoważniałoby bashdo korzystania z dostępnego programu wiersza poleceń. Gdyby to zrobił i zawierał wersję / specyfikację bash, która powinna być obsługiwana, uznałbym to za nadzbiór.
Peter Lawrey,

1
bardzo łatwo jest napisać kod C, który nie jest kodem C ++, np.struct foo { int: this; }; typedef float foo;
Jasen

6

Wiele z tych odpowiedzi podaje uzasadnione powody, dla których C jest lub nie jest szybsze (ogólnie lub w określonych scenariuszach). Jest niezaprzeczalne, że:

  • Wiele innych języków zapewnia automatyczne funkcje, które uważamy za oczywiste. Na przykład sprawdzanie granic, sprawdzanie typu w czasie wykonywania i automatyczne zarządzanie pamięcią nie są darmowe. Z tymi funkcjami wiążą się co najmniej pewne koszty, o których nie możemy myśleć ani nawet nie zdawać sobie sprawy podczas pisania kodu, który korzysta z tych funkcji.
  • Krok od źródła do maszyny często nie jest tak bezpośredni w innych językach, jak w C.
  • OTOH, powiedzenie, że skompilowany kod C działa szybciej niż inny kod napisany w innych językach, jest uogólnieniem, które nie zawsze jest prawdziwe. Kontrprzykłady można łatwo znaleźć (lub wymyślić).

Mimo to, zauważyłem coś jeszcze, co, jak sądzę, wpływa bardziej na porównawczą wydajność C w porównaniu do wielu innych języków bardziej niż jakikolwiek inny czynnik. To znaczy:

Inne języki często ułatwiają pisanie kodu, który działa wolniej. Często jest to wspierane przez filozofie projektowania języka. Następstwo: programiści C częściej piszą kod, który nie wykonuje niepotrzebnych operacji.

Jako przykład rozważ prosty program Windows, w którym tworzone jest pojedyncze okno główne. Wersja AC wypełniłaby WNDCLASS[EX]strukturę, która byłaby przekazana RegisterClass[Ex], a następnie wywołała CreateWindow[Ex]i wprowadziła pętlę komunikatów. Wysoce uproszczony i skrócony kod wygląda następująco:

WNDCLASS wc;
MSG      msg;

wc.style         = 0;
wc.lpfnWndProc   = &WndProc;
wc.cbClsExtra    = 0;
wc.cbWndExtra    = 0;
wc.hInstance     = hInstance;
wc.hIcon         = NULL;
wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
wc.lpszMenuName  = NULL;
wc.lpszClassName = "MainWndCls";

RegisterClass(&wc);

CreateWindow("MainWndCls", "", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
             CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

while(GetMessage(&msg, NULL, 0, 0)){
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

Odpowiednikiem programu w języku C # może być tylko jeden wiersz kodu:

Application.Run(new Form());

Ten jeden wiersz kodu zapewnia wszystkie funkcje, które zrobiło prawie 20 wierszy kodu C, i dodaje pewne rzeczy, które pominęliśmy, takie jak sprawdzanie błędów. Bogatsza, pełniejsza biblioteka (w porównaniu do tych używanych w typowym projekcie C) wykonała dla nas dużo pracy, uwalniając nasz czas na pisanie wielu fragmentów kodu, które są dla nas krótkie, ale wymagają wielu kroków za kulisami.

Ale bogata biblioteka umożliwiająca łatwe i szybkie rozszerzanie kodu nie jest moim celem. Moja uwaga jest bardziej widoczna, gdy zaczynasz badać, co faktycznie dzieje się, gdy nasz mały liniowiec faktycznie wykonuje. Czasami dla zabawy włącz dostęp do źródła .NET w Visual Studio 2008 lub nowszym i przejdź do prostej linijki powyżej. Jednym z zabawnych małych klejnotów, które można napotkać, jest ten komentarz na forum dla Control.CreateParams:

// In a typical control this is accessed ten times to create and show a control.
// It is a net memory savings, then, to maintain a copy on control.
// 
if (createParams == null) {
    createParams = new CreateParams(); 
} 

Dziesięć razy . Informacje w przybliżeniu równoważne sumie tego, co jest przechowywane w WNDCLASSEXstrukturze i tego, co jest przekazywane, CreateWindowExjest pobierane z Controlklasy dziesięć razy, zanim zostanie zapisane w WNDCLASSEXstrukturze i przekazane do RegisterClassExi CreateWindowEx.

Podsumowując, liczba instrukcji wykonanych w celu wykonania tego bardzo podstawowego zadania wynosi 2–3 rzędy wielkości więcej w C # niż w C. Część tego wynika z użycia bogatej w funkcje biblioteki, która jest koniecznie uogólniona, w porównaniu z nasz prosty kod C, który robi dokładnie to, czego potrzebujemy i nic więcej. Ale częściowo wynika to z faktu, że modularna, zorientowana obiektowo natura frameworku .NET pozwala na wiele powtórzeń wykonywania, których często można uniknąć dzięki podejściu proceduralnemu.

Nie próbuję wybrać C # lub .NET. Nie mówię też, że modularyzacja, uogólnienie, funkcje biblioteki / języka, OOP itp. Są złymi rzeczami . Zwykle zajmowałem się głównie programowaniem w C, później w C ++, a ostatnio w C #. Podobnie przed C korzystałem głównie z montażu. I z każdym krokiem „wyżej” idzie mój język, piszę lepsze, łatwiejsze w utrzymaniu, bardziej niezawodne programy w krótszym czasie. Z reguły jednak wykonują się nieco wolniej.


2
To jest problem API, a nie problem językowy.
Arafangion

1
@Arafangion: Rozumiem, co mówisz, ale w pewnym sensie nie ma sensu. Bogata w funkcje biblioteka jest włączona (i poniekąd wymagana) przez bogaty w funkcje język. I to nie tylko biblioteka. Biblioteka jest tylko przykładem powszechnego użycia języka. Typowy kod aplikacji w dowolnym języku na ogół przypomina biblioteki zwykle używane w tym języku. To naprawdę bardziej nastawienie na język. Na przykład języki OO zwykle poświęcają więcej czasu na przydzielanie, konstruowanie, niszczenie i zwalnianie obiektów niż języki z mniejszą obsługą OOP.
P Daddy

Przyznaję, że dany wybór języka na ogół implikuje określoną platformę i bibliotekę, dlatego napisałem ten komentarz (aby czytelnik był bardziej świadomy), ale to powiedziawszy, używając (na przykład) C ++ w systemie Windows jest zupełnie inna bestia, powiedzmy, C ++ na Linuksie i inna od C ++ na Androida. Innym przykładem jest Python - mamy CPython, Jython, PyPy i IronPython - z których wszystkie używają bardzo różnych bibliotek.
Arafangion

Ale używając dowolnego z tych Pythonów, programiści mają tendencję do pisania aplikacji w określony sposób. Na przykład mogą czytać i pisać z pliku tekstowego, tworząc nowe obiekty z odczytanymi danymi. Natomiast w języku C programista najprawdopodobniej dokonałby jednorazowej alokacji tablicy struktur oraz odczytał i zapisał te struktury z pliku binarnego. Jest to oczywiście po prostu wymyślony przykład, który próbuje zilustrować punkt, który staram się wypowiedzieć na temat sposobu myślenia .
P Daddy

6

Nie sądzę, żeby ktokolwiek wspominał o tym, że włożono dużo więcej wysiłku w kompilatory C niż jakikolwiek inny kompilator, być może z wyjątkiem Javy.

C jest niezwykle zoptymalizowany z wielu już podanych powodów - więcej niż prawie jakikolwiek inny język. Więc jeśli włożymy tyle samo wysiłku w kompilatory w innych językach, C prawdopodobnie nadal będzie na topie.

Myślę, że istnieje co najmniej jeden język kandydujący, który z wysiłkiem można zoptymalizować lepiej niż C, a zatem możemy zobaczyć implementacje, które produkują szybsze pliki binarne. Myślę o cyfrowym marsie D, ponieważ twórca zadbał o zbudowanie języka, który mógłby być lepiej zoptymalizowany niż C. Mogą istnieć inne języki, które mają taką możliwość. Jednak nie mogę sobie wyobrazić, że każdy język będzie miał kompilatory więcej niż zaledwie kilka procent szybsze niż najlepsze kompilatory C. Chciałbym się mylić.

Myślę, że prawdziwy „nisko wiszący owoc” będzie w językach, które zostały zaprojektowane tak, aby były ŁATWE dla ludzi w celu optymalizacji. Utalentowany programista może sprawić, że każdy język będzie działał szybciej - ale czasami musisz robić absurdalne rzeczy lub używać nienaturalnych konstrukcji, aby tak się stało. Chociaż zawsze będzie to wymagało wysiłku, dobry język powinien generować stosunkowo szybki kod bez obsesji na punkcie tego, jak napisany jest program.

Ważne jest również (przynajmniej dla mnie), aby najgorszy kod był zwykle szybki. W sieci istnieje wiele „dowodów” na to, że Java jest tak szybka lub szybsza niż C, ale opiera się ona na przykładach kompletacji. Nie jestem wielkim fanem C, ale wiem, że WSZYSTKO, co piszę w C, będzie działało dobrze. W Javie „prawdopodobnie” będzie działać w granicach 15% prędkości, zwykle w granicach 25%, ale w niektórych przypadkach może być znacznie gorzej. Wszelkie przypadki, gdy jest tak samo szybkie lub w granicach kilku procent, są zwykle spowodowane spędzaniem większości czasu w kodzie biblioteki, który i tak jest mocno zoptymalizowany C.


5

To jest właściwie utrwalone kłamstwo. Chociaż prawdą jest, że programy C są często szybsze, nie zawsze tak jest, szczególnie jeśli programista C nie jest w tym zbyt dobry.

Jedną z wielkich rażących dziur, o których ludzie często zapominają, jest to, że program musi blokować jakieś operacje wejścia / wyjścia, na przykład dane wprowadzone przez użytkownika w dowolnym programie GUI. W takich przypadkach nie ma znaczenia, jakiego języka używasz, ponieważ jesteś ograniczony szybkością, z jaką dane mogą przychodzić, a nie szybkością przetwarzania. W tym przypadku nie ma większego znaczenia, jeśli używasz C, Java, C #, a nawet Perla; po prostu nie możesz iść szybciej niż dane mogą wejść.

Inną ważną rzeczą jest to, że korzystanie z odśmiecania pamięci i niestosowanie odpowiednich wskaźników pozwala maszynie wirtualnej na wykonanie szeregu optymalizacji niedostępnych w innych językach. Na przykład JVM jest w stanie przesuwać obiekty na stercie, aby ją zdefragmentować. To sprawia, że ​​przyszłe alokacje są znacznie szybsze, ponieważ można po prostu użyć następnego indeksu zamiast szukać go w tabeli. Współczesne maszyny JVM również nie muszą faktycznie zwalniać pamięci; zamiast tego po prostu przesuwają żywe obiekty, gdy GC, a pamięć z martwych obiektów jest odzyskiwana zasadniczo za darmo.

Przywołuje to również interesującą kwestię dotyczącą C, a tym bardziej w C ++. Jest coś w rodzaju filozofii projektowania: „Jeśli nie potrzebujesz, nie płacisz za to”. Problem polega na tym, że jeśli tego chcesz, w końcu płacisz za to przez nos. Na przykład implementacja vtable w Javie jest znacznie lepsza niż implementacje C ++, więc wywołania funkcji wirtualnych są znacznie szybsze. Z drugiej strony, nie masz wyboru, jak korzystać z funkcji wirtualnych w Javie i nadal coś kosztują, ale w programach, które używają wielu funkcji wirtualnych, obniżony koszt sumuje się.


1
„Implementacja vtable w Javie jest znacznie lepsza niż implementacje C ++, więc wywołania funkcji wirtualnych są znacznie szybsze”. Jak, u licha, możesz jechać szybciej niż MOV EAX, [ECX]; CALL [EAX + someindex]; ? Jeśli nie możesz wywołać funkcji bez wyszukiwania, wygląda to całkiem optymalnie.
Frans-Willem

@Frans - kompilator JIT (taki jak Java HotSpot) może wstawiać wyszukiwanie vtable, jeśli stwierdzi, że dany obiekt jest zawsze danego typu. C ++ zrobi to również, jeśli zna te same informacje w czasie kompilacji, ale łatwiej jest przeprowadzić tę optymalizację za pomocą kodu bajtowego Java niż instrukcji maszynowych x86.
Tom

6
@James - Argumentowanie „We / Wy zmniejsza wydajność” nie unieważnia stwierdzenia „C jest szybszy niż inne języki”. To nie jest rażąca dziura, to jest kłótnia słomy.
Tom

Lepiej byłoby użyć jako przykładu obsługi ciągów C (a także standardowych bibliotek C), ponieważ jest to obszar, w którym C jest słaby. Większość innych języków radzi sobie lepiej, nawet przy uproszczonym kodzie startowym.
Donal Fellows

@DonalFellows funkcje mem * mogą być szybsze niż funkcje str * w niektórych zadaniach, ale obsługa ciągów znaków jest wydajna, jeśli zachowa się ostrożność. czy masz na myśli konkretny punkt odniesienia?
Jasen

4

Nie chodzi tylko o język, ale o narzędzia i biblioteki. Dostępne biblioteki i kompilatory dla C są znacznie starsze niż dla nowszych języków. Możesz pomyśleć, że to spowolni je, ale au contraire.

Te biblioteki zostały napisane w czasie, gdy moc obliczeniowa i pamięć były na wagę złota. Musiały być napisane bardzo skutecznie, aby w ogóle pracować. Deweloperzy kompilatorów C również długo pracowali przy różnego rodzaju sprytnych optymalizacjach dla różnych procesorów. Dojrzałość C i szerokie zastosowanie przysparza znaczącej przewagi nad innymi językami w tym samym wieku. Daje też C przewagę prędkości w porównaniu z nowszymi narzędziami, które nie kładą tak dużego nacisku na wydajność jak C musiał.


4

Brak abstrakcji sprawia, że ​​C jest szybszy. Jeśli napiszesz instrukcję wyjściową, wiesz dokładnie, co się dzieje. Jeśli napiszesz instrukcję wyjściową w java, kompiluje się ona do pliku klasy, który następnie uruchamia się na maszynie wirtualnej, wprowadzając warstwę abstrakcji. Brak funkcji obiektowych jako części języka zwiększa także jego szybkość, aby generować mniej kodu. Jeśli używasz C jako języka zorientowanego obiektowo, wtedy wykonujesz całe kodowanie dla takich rzeczy, jak klasy, niewiedza itp. Oznacza to, że zamiast tego utwórz coś wystarczająco uogólnionego dla każdego z ilością kodu i wydajnością wymagającą tylko pisania czego potrzebujesz, aby wykonać zadanie.


4

Niesamowite jest to, że stary „C / C ++ musi być szybszy niż Java, ponieważ Java jest interpretowana” mit wciąż żyje i ma się dobrze. Istnieją artykuły sprzed kilku lat , a także nowsze , które wyjaśniają pojęciami lub pomiarami, dlaczego po prostu nie zawsze tak jest .

Obecne implementacje maszyn wirtualnych (a przy okazji nie tylko JVM) mogą korzystać z informacji zebranych podczas wykonywania programu, aby dynamicznie dostroić kod podczas jego działania, używając różnych technik:

  • renderowanie częstych metod w kodzie maszynowym,
  • wprowadzanie małych metod,
  • regulacja blokady

oraz szereg innych korekt opartych na wiedzy o tym, co faktycznie robi kod i na faktycznych cechach środowiska, w którym działa.


Zgadzam się, że Java dokonała znacznych ulepszeń wydajności w ciągu ostatnich kilku lat, które zbliżają ją do C pod względem surowej wydajności, ale zajmie to trochę czasu, aby znieść fakt, że był tak wolny przez tak długi czas. Ale kto w ogóle mówił o Javie?
Robert Gamble

Java jest „innym językiem”, do którego odwołuje się OP, prawda?
Robert C. Barth

@Robert: „inne języki”, liczba mnoga, brak wzmianki o żadnym konkretnym języku innym niż C. Jak możesz z tego odczytać „Java”?
Robert Gamble

@Roberd: Kilka odpowiedzi, które zostały opublikowane, zanim napotkałem pytanie, mówiło o Javie (lub innych językach, których implementacja często odbywa się za pośrednictwem interpretera lub maszyny wirtualnej).
joel.neely

3
@Joel - Jeśli znasz docelowy sprzęt, większość optymalizacji, które JVM może wykonać w środowisku wykonawczym, można również wykonać przy użyciu optymalizacji opartej na profilach w C lub C ++. To ogromna różnica i ogólnie popycha C i C ++ z powrotem na prowadzenie, ponieważ nie muszą oni „uczyć się” podczas wykonywania.
Tom

4

Najszybszym działającym kodem byłby starannie wykonany ręcznie kod maszynowy. Asembler będzie prawie tak samo dobry. Oba są na bardzo niskim poziomie i zajmuje dużo pisania kodu. C jest nieco powyżej asemblera. Nadal masz możliwość kontrolowania rzeczy na bardzo niskim poziomie w rzeczywistej maszynie, ale jest wystarczająca abstrakcja, dzięki czemu pisanie jej jest szybsze i łatwiejsze niż w asemblerze. Inne języki, takie jak C # i JAVA, są jeszcze bardziej abstrakcyjne. Podczas gdy asembler i kod maszynowy nazywane są językami niskiego poziomu, C # i JAVA (i wiele innych) nazywane są językami wysokiego poziomu. C jest czasem nazywany językiem średniego poziomu.


Czytając twoją odpowiedź, tylko dwa słowa w całym akapicie przyciągały moje oczy, jak magnes przyciągający metale. Słowo to JAVA, dwa razy w akapicie. Nigdy wcześniej go nie widziałem napisane
wielkimi literami

3

Nie wierz komuś na słowo, spójrz na demontaż zarówno C, jak i wybranego języka w dowolnej części kodu o kluczowym znaczeniu dla wydajności. Myślę, że możesz po prostu spojrzeć w oknie demontażu w czasie wykonywania w Visual Studio, aby zobaczyć zdemontowany .Net. Powinno być to możliwe, jeśli jest to trudne dla Javy przy użyciu windbg, ale jeśli zrobisz to z .Net, wiele problemów będzie takich samych.

Nie lubię pisać w C, jeśli nie muszę, ale myślę, że wiele twierdzeń zawartych w tych odpowiedziach, które mówią o szybkości języków innych niż C, można odrzucić, po prostu rozkładając tę ​​samą procedurę w C i w wybranym przez Ciebie języku wyższego poziomu, szczególnie jeśli w grę wchodzi dużo danych, co jest powszechne w aplikacjach o kluczowym znaczeniu dla wydajności. Fortran może być wyjątkiem w dziedzinie wiedzy specjalistycznej, nie wiem. Czy to wyższy poziom niż C?

Za pierwszym razem porównałem kod JITed z natywnym kodem, rozwiązałem wszystkie pytania, czy kod .Net może działać porównywalnie z kodem C. Dodatkowy poziom abstrakcji i wszystkie kontrole bezpieczeństwa wiążą się ze znacznymi kosztami. Java miałaby prawdopodobnie takie same koszty, ale nie wierz mi na słowo, wypróbuj coś, w którym wydajność ma kluczowe znaczenie. (Czy ktoś wie wystarczająco dużo o JITed Java, aby znaleźć skompilowaną procedurę w pamięci? Z pewnością powinno to być możliwe)


2

1) Jak powiedzieli inni, C robi dla ciebie mniej. Bez inicjowania zmiennych, bez sprawdzania granic tablic, bez zarządzania pamięcią itp. Te funkcje w innych językach kosztują pamięć i cykle procesora, których C nie wydaje.

2) Odpowiedzi stwierdzające, że C jest mniej abstrakcyjne, a zatem szybsze są tylko w połowie poprawne. Technicznie rzecz biorąc, jeśli masz „wystarczająco zaawansowany kompilator” dla języka X, wówczas język X może zbliżyć się do prędkości C. bezpośrednio do języka asemblera, który nawet naiwny kompilator może wykonać przyzwoitą robotę. Do czegoś takiego jak Python potrzebujesz bardzo zaawansowanego kompilatora do przewidywania prawdopodobnych typów obiektów i generowania kodu maszynowego w locie - semantyka C jest na tyle prosta, że ​​prosty kompilator może sobie poradzić.


2

W dobrych czasach istniały tylko dwa rodzaje języków: kompilowany i interpretowany.

Języki skompilowane wykorzystywały „kompilator” do odczytywania składni języka i konwertowania go na identyczny kod języka asemblerowego, który mógłby nie tylko bezpośrednio na CPU. Języki interpretowane wykorzystywały kilka różnych schematów, ale zasadniczo składnia języka została przekonwertowana na formę pośrednią, a następnie uruchomiona w „tłumaczu”, środowisku do wykonywania kodu.

Tak więc w pewnym sensie istniała inna „warstwa” - interpreter - między kodem a maszyną. I, jak zawsze w przypadku komputera, więcej oznacza więcej zasobów. Tłumacze byli wolniejsi, ponieważ musieli wykonywać więcej operacji.

Ostatnio widzieliśmy więcej języków hybrydowych, takich jak Java, które wykorzystują zarówno kompilator, jak i interpreter, aby działały. Jest to skomplikowane, ale JVM jest szybszy, bardziej wyrafinowany i znacznie bardziej zoptymalizowany niż stare interpretery, więc oznacza znacznie lepszą zmianę działania (z biegiem czasu) bliżej tylko prosto skompilowanego kodu. Oczywiście nowsze kompilatory mają również więcej fantazyjnych sztuczek optymalizacyjnych, więc mają tendencję do generowania znacznie lepszego kodu niż kiedyś. Ale większość optymalizacji, najczęściej (choć nie zawsze), powoduje pewien rodzaj kompromisu, tak że nie zawsze są one szybsze we wszystkich okolicznościach. Jak wszystko inne, nic nie przychodzi za darmo, więc optymalizatorzy muszą skądś się pochwalić (chociaż często wykorzystuje to procesor czasu kompilacji, aby zaoszczędzić czas pracy procesora).

Wracając do C, jest to prosty język, który można skompilować w dość zoptymalizowany zestaw, a następnie uruchomić bezpośrednio na maszynie docelowej. W C, jeśli zwiększysz liczbę całkowitą, jest więcej niż prawdopodobne, że jest to tylko jeden krok asemblera w procesorze, jednak w Javie może to być znacznie więcej (i może obejmować również trochę wyrzucania elementów bezużytecznych): -) C oferuje abstrakcję, która jest znacznie bliższa maszynie (asembler jest najbliższy), ale ostatecznie musisz wykonać znacznie więcej pracy, aby go uruchomić i nie jest tak chroniony, łatwy w użyciu lub przyjazny dla błędów. Większość innych języków zapewnia większą abstrakcję i dba o więcej podstawowych informacji, ale w zamian za ich zaawansowaną funkcjonalność wymagają więcej zasobów do działania. W miarę uogólniania niektórych rozwiązań musisz obsługiwać szerszy zakres komputerów,

Paweł.


„W C, jeśli zwiększysz liczbę całkowitą, jest więcej niż prawdopodobne, że jest to tylko jeden krok asemblera w CPU”. Nie do końca prawda. Jeśli tej liczby całkowitej nie ma w rejestrze procesora, musisz mieć kod maszynowy, aby pobrać go z pamięci, zwiększyć i zapisać z powrotem w pamięci. O tym samym można się spodziewać po uruchomieniu kodu Java. I nie mam pojęcia, dlaczego „++ i” sam wyzwalałby cykl GC.
quant_dev

@quant_dev: „musisz ... musisz ... pobrać go z pamięci, zwiększyć, zapisać z powrotem ...”. Może, może nie. Na przykład x86 ma instrukcje działające na danych w pamięci. ++imożna skompilować do „add [ebp - 8], 1”. Nie wspominając już o tym, że pobieranie, przyrost, przechowywanie wciąż się nie dzieje, ale procesor zajmuje się tym i jest to tylko jedna instrukcja, jak powiedział Paul.
P Daddy

... co wciąż nie ma znaczenia dla POV wydajności, ponieważ może to być tylko jedna instrukcja, ale nadal wymaga oczekiwania na przesłanie danych do procesora. Pamięć podręczna tęskni i tak dalej.
quant_dev

Nie, nie powiedziałbym, że to nie ma znaczenia. Jedna instrukcja procesora zwykle zajmuje mniej bajtów kodu niż wiele instrukcji procesora, co zapewnia lepszą wydajność pamięci podręcznej w segmencie kodu. Jedna instrukcja procesora zajmuje również mniej miejsca na potoku procesora, a wiele kroków (pobieranie, przyrost, przechowywanie) może być obsługiwane - być może równolegle - przez osobne etapy potoku. W rzeczywistości niektóre części operacji można nawet pominąć, jeśli można je połączyć z innymi operacjami w rurociągu. Na przykład zapamiętywanie wartości można połączyć z kolejnym ładowaniem tej samej wartości.
P Daddy

2

Pomijając zaawansowane techniki optymalizacji, takie jak optymalizacja „ gorących punktów” , wstępnie skompilowane meta-algorytmy i różne formy paralelizmu , podstawowa szybkość języka silnie koreluje z ukrytą złożonością za kulisami wymaganymi do obsługi operacji, które zwykle być określone w wewnętrznych pętlach .

Być może najbardziej oczywistym jest sprawdzanie poprawności pośrednich odwołań do pamięci - takich jak sprawdzanie wskaźników nulli sprawdzanie indeksów względem granic tablicy. Większość języków wysokiego poziomu wykonuje te kontrole niejawnie, ale C nie. Jednak niekoniecznie jest to fundamentalne ograniczenie tych innych języków - wystarczająco sprytny kompilator może być w stanie usunąć te kontrole z wewnętrznych pętli algorytmu poprzez jakąś formę ruchu kodu niezmiennego w pętli .

Bardziej fundamentalną zaletą C (iw podobnym stopniu blisko spokrewnionego C ++) jest silne poleganie na alokacji pamięci opartej na stosie , która z natury jest szybka w alokacji, dezalokacji i dostępie. W C (i C ++) główny stos wywołań może być używany do alokacji prymitywów, tablic i agregatów ( struct/class ).

Podczas gdy C oferuje możliwość dynamicznej alokacji pamięci o dowolnym rozmiarze i czasie życia (przy użyciu tak zwanej „sterty”), domyślnie unika się tego (zamiast tego stosuje się stos).

Kuszące jest, że czasami można zreplikować strategię alokacji pamięci C w środowiskach wykonawczych innych języków programowania. Zostało to zademonstrowane przez asm.js , który pozwala na przetłumaczenie kodu napisanego w C lub C ++ na podzbiór JavaScript i bezpieczne działanie w środowisku przeglądarki internetowej - z niemal natywną prędkością.


Na marginesie, innym obszarem, w którym C i C ++ przewyższają większość innych języków pod względem szybkości, jest możliwość płynnej integracji z natywnymi zestawami instrukcji maszynowych. Godnym uwagi przykładem jest (zależna od kompilatora i platformy) dostępność funkcji SIMD, które wspierają konstrukcję niestandardowych algorytmów, które wykorzystują obecnie niemal wszechobecny sprzęt do przetwarzania równoległego - przy jednoczesnym wykorzystaniu abstrakcji alokacji danych zapewnianej przez język (niższy -poziomem przydziału rejestru zarządza kompilator).


1
Niektóre z zalet alokacji pamięci C mogą być prawdopodobnie replikowane również w innych językach przez sprytne kompilatory (patrz tutaj ). Mam jednak wrażenie, że jest to strukturalnie bardzo trudne do wykonania - szczególnie w przypadku nieprymitywnych typów danych. W tym poście wspomniano o pomyśle nie uciekającego obiektu, który można przydzielić do stosu jako optymalizację w Javie.
nobar

2

Znalazłem odpowiedź na link o tym, dlaczego niektóre języki są szybsze, a niektóre wolniejsze, mam nadzieję, że to wyjaśni więcej o tym, dlaczego C lub C ++ jest szybszy niż inne. Istnieją również inne języki, które są szybsze niż C, ale nie możemy użyj ich wszystkich. Niektóre wyjaśnienia -

Jednym z głównych powodów, dla których Fortran pozostaje ważny, jest to, że jest szybki: procedury zrywania liczb pisane w Fortranie są zwykle szybsze niż równoważne procedury napisane w większości innych języków. Języki konkurujące z Fortranem w tej przestrzeni - C i C ++ - są używane, ponieważ są konkurencyjne w tej wydajności.

Rodzi to pytanie: dlaczego? Co takiego jest w C ++ i Fortranie, co czyni je szybkimi i dlaczego przewyższają inne popularne języki, takie jak Java lub Python?

Tłumaczenie ustne a kompilacja Istnieje wiele sposobów kategoryzacji i definiowania języków programowania, zgodnie ze stylem programowania, który zachęcają i oferowanymi funkcjami. Jeśli chodzi o wydajność, największym rozróżnieniem są języki interpretowane i skompilowane.

Podział nie jest trudny; raczej jest spektrum. Z jednej strony mamy tradycyjne skompilowane języki, grupę obejmującą Fortran, C i C ++. W tych językach istnieje dyskretny etap kompilacji, który tłumaczy kod źródłowy programu na postać wykonywalną, z której może korzystać procesor.

Ten proces kompilacji składa się z kilku etapów. Kod źródłowy jest analizowany i analizowany. W tym momencie można wykryć podstawowe błędy kodowania, takie jak literówki i błędy ortograficzne. Analizowany kod służy do generowania reprezentacji w pamięci, która również może być używana do wykrywania błędów - tym razem błędów semantycznych, takich jak wywoływanie nieistniejących funkcji lub próba wykonywania operacji arytmetycznych na ciągach tekstowych.

Ta reprezentacja w pamięci jest następnie używana do sterowania generatorem kodu, częścią, która wytwarza kod wykonywalny. Optymalizacja kodu w celu poprawy wydajności generowanego kodu jest przeprowadzana w różnych momentach tego procesu: optymalizacje wysokiego poziomu mogą być przeprowadzane na reprezentacji kodu, a optymalizacje niższego poziomu są wykorzystywane na wyjściu generatora kodu.

Właściwie wykonanie kodu następuje później. Cały proces kompilacji jest po prostu używany do stworzenia czegoś, co można wykonać.

Na drugim końcu mamy tłumaczy. Interpretatory będą zawierać etap parsowania podobny do etapu kompilatora, ale jest on następnie wykorzystywany do kierowania bezpośrednim wykonaniem, a program jest uruchamiany natychmiast.

Najprostszy interpreter zawiera w sobie kod wykonywalny odpowiadający różnym funkcjom obsługiwanym przez język - będzie więc miał funkcje dodawania liczb, łączenia ciągów znaków, cokolwiek innego ma dany język. Podczas analizy kodu wyszukuje odpowiednią funkcję i wykonuje ją. Zmienne utworzone w programie będą przechowywane w pewnego rodzaju tabeli odnośników, która mapuje ich nazwy na dane.

Najbardziej ekstremalnym przykładem stylu interpretera jest coś w rodzaju pliku wsadowego lub skryptu powłoki. W tych językach kod wykonywalny często nie jest nawet wbudowany w sam interpreter, ale raczej w osobne, samodzielne programy.

Dlaczego to wpływa na wydajność? Zasadniczo każda warstwa pośrednia zmniejsza wydajność. Na przykład najszybszym sposobem dodania dwóch liczb jest posiadanie obu tych liczb w rejestrach w procesorze i użycie instrukcji dodawania procesora. To właśnie potrafią skompilowane programy; mogą umieszczać zmienne w rejestrach i korzystać z instrukcji procesora. Jednak w interpretowanych programach ten sam dodatek może wymagać dwóch odnośników w tabeli zmiennych w celu pobrania wartości do dodania, a następnie wywołania funkcji w celu wykonania dodania. Ta funkcja może bardzo dobrze korzystać z tej samej instrukcji procesora, której używa skompilowany program, aby wykonać rzeczywiste dodawanie, ale cała dodatkowa praca przed użyciem instrukcji może spowolnić proces.

Jeśli chcesz dowiedzieć się więcej, sprawdź Źródło


1

Niektóre algorytmy C ++ są szybsze niż C, a niektóre implementacje algorytmów lub wzorców projektowych w innych językach mogą być szybsze niż C.

Kiedy ludzie mówią, że C jest szybki, a następnie przechodzą do rozmowy o jakimś innym języku, zwykle używają wydajności C jako punktu odniesienia.


2
„Niektóre algorytmy C ++ są szybsze niż C” nie ma sensu. Dowolny „algorytm C ++” można zapisać w języku C i odwrotnie. Algorytmy są niezależne od języka. C ++ zasadniczo dodaje funkcje do C - i żadna z tych nowych funkcji nie prowadzi do szybszych algorytmów (choć mogą być łatwiejsze do napisania).
Joseph Garvin

1
Klasycznym upomnieniem jest algorytm std :: sort .. std :: sort jest szybszy niż jakikolwiek algorytm sortowania w C - jedynym sposobem na uzyskanie takiej samej wydajności w C jest kodowanie go wszędzie tam, gdzie chcesz lub używanie makr - i nawet wtedy kompilator ma mniej informacji do optymalizacji.
Arafangion

1

Dzięki nowoczesnym kompilatorom optymalizującym jest mało prawdopodobne, aby czysty program w C był o wiele szybszy niż skompilowany kod .net, jeśli w ogóle. Dzięki zwiększeniu produktywności, które zapewniają platformy takie jak .net, możesz robić rzeczy w ciągu dnia, który zwykle zajmował tygodnie lub miesiące w zwykłym C. W połączeniu z niskim kosztem sprzętu w porównaniu z wynagrodzeniem dewelopera, pisanie jest ZNACZNIE tańsze. rzeczy w języku wysokiego poziomu i rzucaj sprzęt przy dowolnej powolności.

Jeff i Joel rozmawiają o tym, że C jest językiem „prawdziwego programisty”, ponieważ w C. nie ma trzymania się za ręce. Musisz przydzielić własną pamięć, zwolnić tę pamięć, wykonać własne sprawdzenie granic itp. Nie ma czegoś takiego jako nowy obiekt (); Nie ma wyrzucania elementów bezużytecznych, klas, OOP, struktur encji, LINQ, właściwości, atrybutów, pól ani niczego podobnego. Musisz znać takie rzeczy, jak arytmetyka wskaźników i jak wyłuskać wskaźnik. A jeśli o to chodzi, wiedz i rozumiesz, czym jest wskaźnik. Musisz wiedzieć, co to jest ramka stosu i jaki jest wskaźnik instrukcji. Musisz znać model pamięci architektury procesora, nad którym pracujesz. Istnieje wiele niejawnego zrozumienia architektury mikrokomputera (zwyklemikrokomputer, nad którym pracujesz) podczas programowania w języku C, który po prostu nie występuje ani nie jest konieczny podczas programowania w języku C # lub Java. Wszystkie te informacje zostały rozładowane do programisty kompilatora (lub VM).


„Rzuć więcej sprzętu na problem” działa tylko w środowiskach, w których jest to rzeczywiście możliwe. Rynek wbudowany stanowi doskonały przykład (i jest to ogromny rynek).
Bob Somers

Jeff i Joel blogują wyłącznie na temat systemów biznesowych, a nie systemów osadzonych, więc uzasadnione jest założenie, że w tym kontekście zadano to pytanie.
Robert C. Barth

1) Kod .net działa tak szybko, jak kod C? Czy kiedykolwiek napisałeś program w języku C? 2) Mentalność „rzucaj więcej sprzętu na problem” polega na tym, że mój dwurdzeniowy 2-GHz rdzeń 1,3 GHz ledwo nadąża za Windows XP, podczas gdy mój 800-MHz 512 MB ma najnowszą wersję Ubuntu.
Robert Gamble

Tak, napisałem C. To nie jest tak wspaniałe, jak się wydaje. A projekty kosztują za dużo. To prosty przypadek ekonomii. Przez lata korzystałem z Win2k na Pentium Pro 180 MHz z 768 MB RAM jako serwer poczty i sieci. Poszło dobrze. Niepotwierdzone dowody nic nie znaczą.
Robert C. Barth

C nie jest „chwalebny”, ale jest szybki, napisałem wystarczająco dużo kodu C i C #, aby wiedzieć, że C prawie zawsze znacznie szybciej niż C # podczas wykonywania tego samego zadania. W przypadku niektórych zadań opracowywanie w językach wyższego poziomu zajmuje więcej czasu, ale chodzi o użycie odpowiedniego narzędzia do pracy, a czasem C.
Robert Gamble

1

Różnica między automatycznym i ręcznym, języki wyższego poziomu są abstrakcjami w ten sposób zautomatyzowanymi. C / C ++ są ręcznie kontrolowane i obsługiwane, nawet kod sprawdzania błędów jest czasem pracą ręczną.

C i C ++ są również językami kompilowanymi, co oznacza, że ​​żaden z nich nie działa wszędzie, języki te muszą być dostosowane do sprzętu, z którym pracujesz, dodając w ten sposób dodatkową warstwę gotcha. Chociaż jest to obecnie nieco dziwne, ponieważ kompilatory C / C ++ stają się coraz bardziej popularne na wszystkich platformach. Możesz wykonywać kompilacje między platformami. Nadal nie jest to sytuacja wszędzie uruchamiana, po prostu instruujesz kompilator A, aby kompilował z kompilatorem B ten sam kod innej architektury.

Języki dolnej linii C nie są łatwe do zrozumienia ani uzasadnienia, dlatego też nazywane są językami systemowymi. Wyszli przed wszystkimi tymi nonsensami na wysokim poziomie abstrakcji. Dlatego też nie są one używane do programowania stron internetowych. Po prostu nie nadają się do tego zadania, ich sposób rozwiązywania złożonych problemów, których nie można rozwiązać za pomocą konwencjonalnych narzędzi językowych.

To dlatego dostajesz szalone rzeczy, takie jak (mikrostruktury, sterowniki, fizyka kwantowa, gry AAA, systemy operacyjne), są rzeczy, do których C i C ++ są po prostu dobrze dostosowane. Najważniejsze obszary to szybkość i chrupanie liczb.



1

Istnieje wiele powodów, w tym:

  • Kompiluje się w asemblerze.
  • Jest on wpisany statycznie.
  • Brak wywozu śmieci.
  • Brak obsługi błędów.
  • Wiele języków programowania dodaje nowe funkcje. Częścią filozofii C jest prostota zamiast dodawania kolejnych funkcji.

Dlaczego miałby być szybszy, ponieważ nie jest obiektowo zorientowany?
sb27

A) programowanie obiektowe stwarza potrzebę wyrzucania elementów bezużytecznych, B) duże funkcje, takie jak programowanie obiektowe, zwiększają złożoność kompilatora,
Sapphire_Brick

A) Nie. Zobacz C ++ lub Rust. B) Tak, ale daje także Kompilatorowi możliwości nowych optymalizacji.
sb27

A) Rust ma funkcję czyszczenia pamięci w czasie kompilacji, a c ++ ma funkcję czyszczenia pamięci dla klas, dlatego ma destrukty, B) zoptymalizowana złożoność to wciąż złożoność
Sapphire_Brick

A) To nie jest wyrzucanie elementów bezużytecznych, zarządzanie pamięcią musi być wykonane, nawet jeśli program jest tworzony w zestawie B) Nie. Więcej abstrakcji umożliwia optymalizatorowi dokonywanie lepszych założeń.
sb27
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.