Pisanie w C dla wydajności? [Zamknięte]

32

Wiem, że dość często słyszałem, że C zazwyczaj ma przewagę wydajności nad C ++. Tak naprawdę nie myślałem o niczym innym, dopóki nie zdałem sobie sprawy, że MSVC wydaje się nawet nie obsługiwać najnowszego standardu C, ale najnowszy obsługuje C99 (o ile mi wiadomo).

Planowałem napisać bibliotekę z kodem do renderowania w OpenGL, aby móc go ponownie użyć. Planowałem napisać bibliotekę w C, ponieważ wzrost wydajności jest mile widziany, jeśli chodzi o grafikę.

Ale czy naprawdę byłoby warto? Kod korzystający z biblioteki prawdopodobnie zostałby napisany w C ++, a ja wolę kodować ogólnie w C ++.

Jeśli jednak spowodowałoby to nawet niewielką różnicę w wydajności, prawdopodobnie wybrałbym C.

Można również zauważyć, że ta biblioteka byłaby czymś, co zrobiłbym do pracy w systemie Windows / OS X / Linux i prawdopodobnie skompilowałbym wszystko natywnie (MSVC dla Windows, Clang lub GCC dla OS X i GCC dla Linux .. . lub ewentualnie kompilatory Intela do wszystkiego).

Rozejrzałem się i znalazłem pewne testy porównawcze i takie, ale wszystko, co widziałem, dotyczyło GCC, a nie MSVC i Clanga. Ponadto testy porównawcze nie wspominają o standardach używanych języków. Ktoś ma jakieś przemyślenia na ten temat?

EDYTOWAĆ:Chciałem po prostu podzielić się moim poglądem na to pytanie po kilku latach doświadczenia. W końcu napisałem projekt, o który zadawałem to pytanie w C ++. Rozpocząłem inny projekt mniej więcej w tym samym czasie w C, ponieważ chcieliśmy uzyskać jak najmniejszą wydajność, którą mogliśmy i potrzebowaliśmy, aby projekt był możliwy do połączenia w C. Kilka miesięcy temu dotarłem do punktu, w którym naprawdę potrzebowałem map i zaawansowanych manipulacja sznurkiem. Wiedziałem o możliwościach tego w standardowej bibliotece C ++ i ostatecznie doszedłem do wniosku, że te struktury w standardowej bibliotece prawdopodobnie przewyższą i będą bardziej stabilne niż mapy i łańcuchy, które mógłbym zaimplementować w C w rozsądnym czasie. Wymóg bycia linkowalnym w C został łatwo spełniony przez napisanie interfejsu C do kodu C ++, co zostało zrobione szybko z nieprzezroczystymi typami. Przepisywanie biblioteki w C ++ wydawało się przebiegać znacznie szybciej niż podczas pisania w C i było mniej podatne na błędy, zwłaszcza wycieki pamięci. Byłem także w stanie korzystać ze standardowej biblioteki wątków bibliotecznych, co było znacznie łatwiejsze niż stosowanie specyficznych dla platformy implementacji. Ostatecznie uważam, że napisanie biblioteki w C ++ przyniosło ogromne korzyści przy możliwie niskim koszcie wydajności. Nie przetestowałem jeszcze wersji C ++, ale wierzę, że może być nawet możliwe, że osiągnąłem pewną wydajność, używając standardowych struktur danych biblioteki niż te, które napisałem. Wierzę, że pisanie biblioteki w C ++ przyniosło ogromne korzyści przy możliwie niskim koszcie wydajności. Nie przetestowałem jeszcze wersji C ++, ale wierzę, że może być nawet możliwe, że osiągnąłem pewną wydajność, używając standardowych struktur danych biblioteki niż te, które napisałem. Wierzę, że pisanie biblioteki w C ++ przyniosło ogromne korzyści przy możliwie niskim koszcie wydajności. Nie przetestowałem jeszcze wersji C ++, ale wierzę, że może być nawet możliwe, że osiągnąłem pewną wydajność, używając standardowych struktur danych biblioteki niż te, które napisałem.

danielunderwood
źródło
9
Najnowsze wsparcie MSVC to tak naprawdę C89.
detly
4
@detly W Visual Studio 2013 obsługiwana jest ogromna większość funkcji C99 . Nie jest to pełne wsparcie, ale postawiłbym w praktyce, że można go używać do pisania C99.
congusbongus
4
@ danielu13 - Gdzie dokładnie słyszałeś, że C ma przewagę wydajności nad C ++?
Ramhound
1
@ Sebastian-LaurenţiuPlesciuc Nie sądzę, że te linki są w rzeczywistości pomocne. Pierwszym można by dobrze przeciwdziałać z prawie takimi samymi pytaniami programiści.stackexchange.com/ q/113295/76444, ale na korzyść c ++ zamiast c jak w twoim linku. W przypadku drugiego linku jest to po prostu linus torvalds. Mam nadzieję, że wszyscy już wiedzą, że on naprawdę lubi nienawidzić c ++ i nie dotknąłby go kijem, ale jego ranty na temat c ++ są mało obiektywne, są pełne osobistych opinii i stronniczości i tak naprawdę nie odzwierciedlają rzeczywistości języka . Przynajmniej taka jest moja opinia .
user1942027
1
@RaphaelMiedl Wspomniałem również, że zostało to napisane w 2007 roku, który jest dość dawno temu, kompilatory C ++ i język C ++ ewoluowały od tego czasu. Niezależnie od tego programista decyduje, jakiego języka użyć.
Sebastian-Laurenţiu Plesciuc

Odpowiedzi:

89

Sądzę, że ludzie często twierdzą, że C jest szybszy niż C ++, ponieważ łatwiej jest uzasadnić wydajność w C. C ++ nie jest z natury wolniejszy ani szybszy, ale niektóre kody C ++ mogą ukrywać ukryte kary za wydajność. Na przykład mogą istnieć kopie i niejawne konwersje, które nie są natychmiast widoczne, gdy patrzymy na jakiś fragment kodu C ++.

Weźmy następujące oświadczenie:

foo->doSomething(a + 5, *c);

Załóżmy dalej, że doSomethingma następujący podpis:

void doSomething(int a, long b);

Teraz spróbujmy przeanalizować możliwy wpływ tego konkretnego stwierdzenia na wydajność.

W C implikacje są dość jasne. foomoże być tylko wskaźnikiem do struktury i doSomethingmusi być wskaźnikiem do funkcji. *cdereferencje długo i a + 5jest dodawanie liczb całkowitych. Jedyna niepewność wynika z rodzaju a: Jeśli nie jest to int, nastąpi pewna konwersja. ale poza tym łatwo jest oszacować wpływ tego pojedynczego wyrażenia na wydajność.

Teraz przejdźmy do C ++. To samo oświadczenie może teraz mieć bardzo różne cechy wydajności:

  1. doSomethingmoże to być nie-wirtualna funkcja członka (tania), funkcja wirtualnego członka (nieco droższa) std::function, lambda ... itd. Co gorsza, foomoże to być przeciążenie typu klasy operator->z pewną operacją o nieznanej złożoności. Aby więc obliczyć koszt połączenia doSomething, należy teraz znać dokładną naturę fooi doSomething.
  2. amoże być liczbą całkowitą lub odniesieniem do liczby całkowitej (dodatkowa pośrednia) lub typem klasy, który implementuje operator+(int). Operator może nawet zwrócić inny typ klasy, do którego domyślnie można przekształcić int. Ponownie, koszt wydajności nie wynika z samego oświadczenia.
  3. cmoże być implementacją typu klasy operator*(). Może to być również odniesienie do long*itp.

Dostajesz obraz. Ze względu na C ++ 's funkcje języka, to jest o wiele trudniejsze do oszacowania kosztów wydajności pojedynczej instrukcji, niż jest to w C. Teraz w uzupełnieniu, abstrakcje jak std::vector, std::stringsą powszechnie stosowane w C ++, które posiadają właściwości działania własne, i alokacji pamięci hide dynamiczne ( zobacz także odpowiedź @ Iana).

Podsumowując: Ogólnie rzecz biorąc, nie ma różnicy w możliwej wydajności możliwej do osiągnięcia przy użyciu C lub C ++. Ale w przypadku kodu naprawdę krytycznego dla wydajności ludzie często wolą używać C, ponieważ istnieje znacznie mniej możliwych ukrytych kar za wydajność.

śmiertelna gitara
źródło
1
Znakomita odpowiedź. Do tego nawiązywałem w mojej odpowiedzi, ale wyjaśniłeś to znacznie lepiej.
Ian Goldby
4
To naprawdę powinna być zaakceptowana odpowiedź. Wyjaśnia, dlaczego istnieją takie stwierdzenia, jak „C jest szybszy niż C ++”. C może być szybszy lub wolniejszy niż C ++, ale zwykle o wiele łatwiej jest dowiedzieć się, dlaczego określony fragment kodu C jest szybki / wolny, co zwykle ułatwia również optymalizację.
Leo
Nie biorę niczego z tej doskonałej odpowiedzi (za którą otrzymałem +1 ode mnie), ale kompilatorem (-ami) mogą być jabłka i pomarańcze w tym porównaniu. Może generować identyczny kod dla C vs. C ++ lub nie. Oczywiście to samo można powiedzieć o dowolnych dwóch kompilatorach lub opcjach kompilatora, nawet podczas kompilacji fizycznie tego samego programu z tymi samymi założeniami języka źródłowego.
JRobert
4
Dodałbym, że większość środowisk uruchomieniowych C ++ jest ogromna w porównaniu z równoważnym środowiskiem wykonawczym C, co miałoby znaczenie, gdybyś miał ograniczoną pamięć.
James Anderson
@JamesAnderson Jeśli jesteś naprawdę , że pamięć ograniczone, to prawdopodobnie nie potrzebują czasu pracy na wszystkich :)
Navin
30

Kod napisany w C ++ może być szybszy niż w C, dla niektórych typów zadań.

Jeśli wolisz C ++, użyj C ++. Wszelkie problemy z wydajnością będą nieznaczne w porównaniu z algorytmicznymi decyzjami twojego oprogramowania.

Jaka jest nazwa?
źródło
6
Może być szybszy, ale może nie być szybszy z tego samego powodu.
Rob
Czy możesz podać kilka przykładów zoptymalizowanego kodu napisanego w C ++, który jest szybszy niż zoptymalizowany C?
1
@TomDworzański: jednym z przykładów jest to, że przy użyciu szablonów decyzje dotyczące ścieżek kodu mogą być ustalane w czasie kompilacji i ostatecznie zakodowane w ostatecznym pliku binarnym, a nie warunkowe i rozgałęzione, co byłoby wymagane, gdyby został napisany w c, a także zdolność aby uniknąć wywołań funkcji poprzez wstawianie.
whatsisname
23

Jedną z zasad projektowania C ++ jest to, że nie płacisz za funkcje, których nie używasz. Tak więc, jeśli napiszesz kod w C ++ i unikniesz funkcji, które nie istnieją w C, to wynikowy skompilowany kod powinien mieć równoważną wydajność (choć będziesz musiał to zmierzyć).

Korzystanie z klas jest na przykład niewielkie, w porównaniu do struktur i wielu powiązanych funkcji. Funkcje wirtualne będą kosztować nieco więcej i będziesz musiał zmierzyć wydajność, aby sprawdzić, czy ma to znaczenie dla Twojej aplikacji. To samo dotyczy każdej innej funkcji języka C ++.

Greg Hewgill
źródło
3
Narzut związany z wysyłką funkcji wirtualnej jest prawie nieistotny, chyba że zaszedłeś WAY za burtę podczas rozkładania i wirtualizacji. Tabele vtables będą małe w porównaniu z resztą kodu i danych, a indeksowana gałąź poprzez vtable dodaje kilka zegarów do każdej rutynowej inwokacji. Biorąc pod uwagę, że rutynowe inwokacje, wszystkie, wezwania do powrotu, będą w dowolnym miejscu od kilkuset do kilku milionów zegarów, gałąź vtable zostanie zakopana w hałasie.
John R. Strohm
6
Struktury są klasami w C ++.
prawej strony
2
@rightfold: Jasne, ale wciąż możesz pisać kod C ++, który przekazuje wskaźniki do struktur bez użycia thisfunkcji języka wskaźnika. To wszystko co powiedziałem.
Greg Hewgill
4
@John Rzeczywistym kosztem nie jest pośrednictwo (chociaż jestem prawie pewien, że to też trochę psuje przy pobieraniu niektórych procesorów), ale fakt, że nie można wstawiać funkcji wirtualnych (przynajmniej w C ++), co wyklucza wiele innych możliwości optymalizacje. I tak, może to mieć ogromny wpływ na wydajność.
Voo
2
@ Voo Aby być uczciwym, to samo można powiedzieć o równoważnym kodzie C (kod, który ręcznie emuluje polimorfizm środowiska wykonawczego). Największą różnicą jest to, że uważam, że kompilatorowi łatwiej byłoby ustalić, czy wspomnianą funkcję można wstawić w C ++.
Thomas Eding
14

Jednym z powodów, dla których języki wyższego poziomu są czasami wolniejsze, jest to, że mogą ukryć za kulisami znacznie więcej zarządzania pamięcią niż języki niższego poziomu.

Każdy język (lub biblioteka, API itp.), Który wyodrębnia szczegóły niskiego poziomu, może potencjalnie ukrywać kosztowne operacje. Na przykład w niektórych językach po prostu przycięcie końcowych białych znaków z ciągu powoduje przydzielenie pamięci i kopię ciągu. W szczególności przydzielanie pamięci i kopiowanie może stać się kosztowne, jeśli będą się powtarzać w ciasnej pętli.

Jeśli napiszesz tego rodzaju kod w C, byłoby to oczywiste oczywiste. W C ++ być może mniej, bo alokacje i kopiowanie można gdzieś wyodrębnić do klasy. Mogą być nawet ukryte za niewinnie wyglądającym przeciążonym operatorem lub konstruktorem kopii.

Więc użyj C ++, jeśli chcesz. Ale nie daj się zwieść pozornej wygodzie abstrakcji, gdy nie wiesz, co kryje się pod nimi.

Oczywiście użyj profilera, aby dowiedzieć się, co naprawdę spowalnia Twój kod.

Ian Goldby
źródło
5

Jeśli chodzi o to, co warto, zwykle piszę biblioteki w C ++ 11 dla rozszerzonego zestawu funkcji. Lubię być w stanie korzystać z takich rzeczy, jak wspólne wskaźniki, wyjątki, ogólne programowanie i inne funkcje tylko w C ++. Lubię C ++ 11, ponieważ zauważyłem, że spora jego część jest obsługiwana na wszystkich platformach, na których mi zależy. Visual Studio 2013 ma wiele podstawowych funkcji językowych i implementacji bibliotek gotowych do użycia i podobno pracuje nad dodaniem pozostałych. Jak dobrze wiesz, zarówno Clang, jak i GCC obsługują również cały zestaw funkcji.

Powiedziawszy to, niedawno przeczytałem o naprawdę świetnej strategii dotyczącej rozwoju biblioteki, która moim zdaniem jest bezpośrednio związana z twoim zapytaniem. Artykuł zatytułowany jest „Styl obsługi błędów AC, który dobrze się gra z wyjątkami C ++” Stefanu Du Toit określa tę strategię jako wzór „klepsydry”. Pierwszy akapit tego artykułu:

Napisałem dużo kodu bibliotecznego, używając tak zwanego wzorca „klepsydry”: implementuję bibliotekę (w moim przypadku zwykle używając C ++), zawijam ją w C API, który staje się jedynym punktem wejścia do biblioteki, następnie zawiń ten C API w C ++ lub innym języku (językach), aby zapewnić bogatą abstrakcję i wygodną składnię. Jeśli chodzi o natywny kod wieloplatformowy, interfejsy API C zapewniają niezrównaną stabilność ABI i przenośność na inne języki za pośrednictwem FFI. Ograniczam nawet API do podzbioru C, który, jak wiem, jest przenośny dla szerokiej gamy FFI i izoluje bibliotekę przed przeciekającymi zmianami w wewnętrznych strukturach danych - oczekuj więcej na ten temat w przyszłych postach na blogu.


Teraz, aby rozwiązać Twój główny problem: wydajność.

Sugerowałbym, podobnie jak wiele innych odpowiedzi tutaj, że pisanie kodu w dowolnym języku działałoby równie dobrze z punktu widzenia wydajności. Z osobistego punktu widzenia pisanie poprawnego kodu w C ++ jest łatwiejsze ze względu na funkcje językowe, ale myślę, że to osobiste preferencje. Tak czy inaczej, kompilatory są naprawdę inteligentne i mają tendencję do pisania lepszego kodu niż ty. To znaczy, że kompilator prawdopodobnie zoptymalizuje Twój kod lepiej niż możesz.

Wiem, że wielu programistów tak mówi, ale pierwszą rzeczą, którą powinieneś zrobić, to napisać kod, a następnie profilować go i dokonywać optymalizacji tam, gdzie sugeruje to twój profiler. Twój czas będzie znacznie lepiej spędzony na tworzeniu funkcji, a następnie na ich optymalizacji, gdy zobaczysz, gdzie są twoje wąskie gardła.


Teraz kilka zabawnych lektur na temat tego, jak funkcje językowe i optymalizacje mogą naprawdę działać na twoją korzyść:

std :: unique_ptr ma zerowy narzut

constexp pozwala na obliczenia w czasie kompilacji

semantyka przenoszenia zapobiega niepotrzebnym obiektom tymczasowym

vmrob
źródło
std::unique_ptr has zero overheadNie może to być prawdą (technicznie rzecz biorąc), ponieważ musi zostać wywołany jego konstruktor, jeśli stos rozwinie się z powodu wyjątku. Wskaźnik surowy nie ma tego narzutu i nadal jest poprawny, jeśli Twój kod prawdopodobnie nie wyrzuci. Kompilator nie będzie w stanie tego udowodnić w ogólnym przypadku.
Thomas Eding
2
@ThomasEding Miałem na myśli rozmiar i ogólne obciążenie środowiska wykonawczego w odniesieniu do kodu wolnego od wyjątków. Popraw mnie, jeśli się mylę, ale istnieją modele wykonania, które powodują zerowe obciążenie środowiska wykonawczego, gdy nie są zgłaszane wyjątki, które nadal pozwalają na propagowanie wyjątków w razie potrzeby. Mimo to, kiedy można kiedykolwiek wprowadzić wyjątek w konstruktorze unique_ptr? Jest zadeklarowany noexcept, a więc przynajmniej obsługuje wszystkie wyjątki, ale nie mogę sobie wyobrazić, jakiego rodzaju wyjątek mógłby zostać w ogóle rzucony.
vmrob
vmrob: Przepraszam ... Chciałem napisać „destruktor” zamiast „konstruktor”. Chciałem też napisać „pewnie nie rzucę”. Eeee!
Thomas Eding
2
@ThomasEding Wiesz, nie sądzę, że miałoby to nawet znaczenie, gdyby destruktor rzucił wyjątek. Tak długo, jak destruktor nie wprowadza żadnych nowych wyjątków, nadal pozostaje zerową destrukcją. Co więcej, uważam, że cały destruktor jest przystosowany do pojedynczego usuwania / darmowego połączenia z optymalizacjami.
vmrob
4

Różnica w wydajności między C ++ i C nie wynika z niczego w języku, mówiąc ściśle, ale z tego, co kusi cię do zrobienia. To jest jak karta kredytowa kontra gotówka. Nie powoduje to, że wydajesz więcej, ale i tak robisz, chyba że jesteś bardzo zdyscyplinowany.

Oto przykład programu napisanego w C ++, który został następnie agresywnie dostrojony. Musisz wiedzieć, jak wykonywać agresywne dostrajanie wydajności, niezależnie od języka. Metoda, której używam, to losowe wstrzymywanie, jak pokazano na tym filmie .

Kosztowne rzeczy, które kusi C ++, to nadmierne zarządzanie pamięcią, programowanie w stylu powiadomień, ufanie programowi w przeciwieństwie do wielowarstwowych bibliotek abstrakcji (jak powiedział @Ian), ukrywanie spowolnienia itp.

Mike Dunlavey
źródło
2

C nie ma żadnej przewagi wydajności w porównaniu do C ++, jeśli robisz to samo w obu językach. Możesz wziąć dowolny stary kod C napisany przez dowolnego porządnego programistę C i przekształcić go w prawidłowy i równoważny kod C ++, który będzie działał równie szybko (chyba że zarówno ty, jak i twój kompilator wiecie, co robi słowo kluczowe „ogranicz” i używacie go skutecznie, ale większość ludzi nie).

C ++ może mieć znacznie inną wydajność, wolniejszą lub szybszą, jeśli (1) używasz standardowej biblioteki C ++ do robienia rzeczy, które można wykonać znacznie szybciej i łatwiej bez korzystania z biblioteki, lub (2) jeśli używasz standardowej biblioteki C ++ robić rzeczy o wiele łatwiej i szybciej niż przez ponowną implementację biblioteki w złym C.

gnasher729
źródło
1
wydaje się, że nie oferuje to nic istotnego w porównaniu z tym, co wyjaśniono w 6 wcześniejszych odpowiedziach
gnat
Myślę, że ta odpowiedź wspomina o ważnym punkcie, o którym nikt inny nie wspomniał. Na pierwszy rzut oka wydaje się, że C ++ jest nadzbiorem C, więc jeśli możesz napisać szybką implementację C, to powinieneś być w stanie napisać implementację C ++, która jest równoważna. Jednak C99 obsługuje słowo kluczowe ograniczające, które pozwala uniknąć niezamierzonego aliasingu wskaźnika. C ++ nie ma takiego wsparcia. Zdolność do unikania aliasingu wskaźnika jest ważną cechą Fortran, która sprawia, że ​​jest on użyteczny w aplikacjach o wysokiej wydajności. Oczekuję, że w podobnych domenach można także wycisnąć lepszą wydajność z C99 niż C ++.
user27539,