Schreiben in C für die Leistung? [geschlossen]

32

Ich weiß, dass ich ziemlich oft gehört habe, dass C typischerweise einen Leistungsvorteil gegenüber C ++ hat. Ich habe nicht wirklich darüber nachgedacht, bis mir klar wurde, dass MSVC nicht einmal den neuesten Standard von C zu unterstützen scheint, aber der neueste, der es unterstützt, C99 (soweit ich weiß).

Ich hatte vor, eine Bibliothek mit Code zu schreiben, der in OpenGL gerendert werden soll, damit ich ihn wiederverwenden kann. Ich hatte vor, die Bibliothek in C zu schreiben, da jede Leistungssteigerung in Bezug auf Grafiken willkommen ist.

Aber wäre es das wirklich wert? Der Code, der die Bibliothek verwendet, wird wahrscheinlich in C ++ geschrieben, und ich bevorzuge generell den Code in C ++.

Wenn es jedoch auch nur einen kleinen Leistungsunterschied geben würde, würde ich wahrscheinlich mit C gehen.

Es kann auch angemerkt werden, dass diese Bibliothek etwas wäre, das ich für die Arbeit unter Windows / OS X / Linux machen würde, und ich würde wahrscheinlich alles nativ kompilieren (MSVC für Windows, Clang oder GCC für OS X und GCC für Linux). .oder möglicherweise Intels Compiler für alles).

Ich habe mich umgesehen und einige Benchmarks und dergleichen gefunden, aber alles, was ich gesehen habe, hat sich eher mit GCC als mit MSVC und Clang befasst. Außerdem erwähnen die Benchmarks nicht die Standards der verwendeten Sprachen. Hat jemand irgendwelche Gedanken dazu?

BEARBEITEN:Ich wollte nur nach ein paar Jahren mehr Erfahrung meinen Standpunkt zu dieser Frage teilen. Am Ende habe ich das Projekt geschrieben, für das ich diese Frage in C ++ gestellt habe. Ich habe ein anderes Projekt ungefähr zur selben Zeit in C gestartet, als wir versucht haben, eine möglichst geringe Performance zu erzielen. Vor ein paar Monaten hatte ich den Punkt erreicht, an dem ich wirklich Karten brauchte und mich weiterentwickelte String-Manipulation. Ich kannte die Fähigkeiten dafür in der C ++ - Standardbibliothek und kam schließlich zu dem Schluss, dass diese Strukturen in der Standardbibliothek wahrscheinlich besser abschneiden und stabiler sind als Maps und Strings, die ich in angemessener Zeit in C implementieren könnte. Die Anforderung, in C verlinkbar zu sein, wurde leicht erfüllt, indem eine C-Schnittstelle zum C ++ - Code geschrieben wurde, was mit undurchsichtigen Typen schnell erledigt wurde. Das Umschreiben der Bibliothek in C ++ schien viel schneller zu gehen als beim Schreiben in C und war weniger anfällig für Fehler, insbesondere für Speicherverluste. Ich konnte auch die Standardbibliotheks-Threading-Bibliothek verwenden, die viel einfacher war als die Verwendung plattformspezifischer Implementierungen. Letztendlich, glaube ich, hat das Schreiben der Bibliothek in C ++ zu großen Vorteilen bei möglicherweise geringen Leistungskosten geführt. Ich habe die C ++ - Version noch nicht verglichen, aber ich glaube, dass es sogar möglich ist, dass ich durch die Verwendung von Standard-Bibliotheksdatenstrukturen eine gewisse Leistung erzielt habe, als ich sie geschrieben habe. Ich glaube, dass das Schreiben der Bibliothek in C ++ zu großen Vorteilen bei möglicherweise geringen Leistungskosten geführt hat. Ich habe die C ++ - Version noch nicht verglichen, aber ich glaube, dass es sogar möglich ist, dass ich durch die Verwendung von Standard-Bibliotheksdatenstrukturen eine gewisse Leistung erzielt habe, als ich sie geschrieben habe. Ich glaube, dass das Schreiben der Bibliothek in C ++ zu großen Vorteilen bei möglicherweise geringen Leistungskosten geführt hat. Ich habe die C ++ - Version noch nicht verglichen, aber ich glaube, dass es sogar möglich ist, dass ich durch die Verwendung von Standard-Bibliotheksdatenstrukturen eine gewisse Leistung erzielt habe, als ich sie geschrieben habe.

danielunderwood
quelle
9
Die neueste MSVC-Unterstützung ist C89.
Detly
4
@detly In Visual Studio 2013 werden die meisten C99-Funktionen unterstützt . Es ist keine vollständige Unterstützung, aber ich würde wetten, dass es in der Praxis in Ordnung ist, es zum Schreiben von C99 zu verwenden.
Congusbongus
4
@ danielu13 - Wo genau hast du gehört, dass C einen Leistungsvorteil gegenüber C ++ hat?
Ramhound
1
@ Sebastian-LaurenţiuPlesciuc Ich glaube nicht, dass diese Links wirklich hilfreich sind. Dem ersten Fall könnte man mit fast der gleichen Frage wie in Ihrem Link, programers.stackexchange.com/q/113295/76444, aber zugunsten von c ++ anstelle von c , gut begegnen . Für Ihren 2. Link ist es nur ein Scherz von Linus Torvalds. Ich hoffe, dass mittlerweile jeder weiß, dass er c ++ wirklich gerne hasst und es nicht mit einem Stock anfassen würde, aber seine Äußerungen über c ++ sind kaum objektiv, sie sind voller persönlicher Meinung und Voreingenommenheit und spiegeln nicht wirklich die Realität der Sprache wider . Zumindest ist das meine Meinung .
user1942027
1
@RaphaelMiedl Auch ich erwähnte, dass dies im Jahr 2007 geschrieben wurde, was ziemlich lange her ist. C ++ - Compiler und die C ++ - Sprache haben sich seitdem weiterentwickelt. Unabhängig davon ist es Sache des Programmierers, die zu verwendende Sprache auszuwählen.
Sebastian-Laurenţiu Plesciuc

Antworten:

89

Ich denke , die Menschen oft behaupten , dass C schneller als C ++ ist , weil es einfacher ist Grund über die Leistung in C. C ++ ist nicht von Natur aus langsamer oder schneller, aber bestimmte C ++ Code könnte versteckte Leistungseinbußen verschleiern. Beispielsweise kann es Kopien und implizite Konvertierungen geben, die bei der Betrachtung eines Teils des C ++ - Codes nicht sofort sichtbar sind.

Nehmen wir die folgende Aussage:

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

Nehmen wir weiter an, dass doSomethingdas folgende Signatur hat:

void doSomething(int a, long b);

Versuchen wir nun, die möglichen Auswirkungen dieser bestimmten Anweisung auf die Leistung zu analysieren.

In C sind die Implikationen klar. fookann nur ein Zeiger auf eine Struktur sein und doSomethingmuss ein Zeiger auf eine Funktion sein. *cdereferences a long, und a + 5ist eine ganzzahlige Addition. Die einzige Unsicherheit ergibt sich aus der Art von a: Wenn es sich nicht um eine Ganzzahl handelt, kommt es zu einer gewissen Konvertierung. Abgesehen davon ist es einfach, die Auswirkungen dieser einzelnen Anweisung auf die Leistung zu quantifizieren.

Wechseln wir nun zu C ++. Dieselbe Aussage kann nun sehr unterschiedliche Leistungsmerkmale haben:

  1. doSomethingkönnte eine nicht-virtuelle Member - Funktion (billig), Funktion virtuelle Element (etwas teurer) std::function, Lambda ... usw. Was noch schlimmer ist, fookönnte ein Klassentyp Überlastung sein operator->mit einigem Betrieb von unkown Komplexität. Um die Anrufkosten zu quantifizieren doSomething, ist es nun erforderlich, die genaue Art von foound zu kennen doSomething.
  2. aDies kann eine Ganzzahl oder ein Verweis auf eine Ganzzahl (zusätzliche Indirektion) oder ein Klassentyp sein, der implementiert wird operator+(int). Der Operator könnte sogar einen anderen Klassentyp zurückgeben, in den implizit konvertiert werden kann int. Wiederum sind die Leistungskosten nicht allein aus der Aussage ersichtlich.
  3. ckönnte ein Klassentyp sein, der implementiert operator*(). Es könnte sich auch um einen Verweis auf ein long*usw. handeln.

Du bekommst das Bild. Aufgrund C ++ 's Sprachfunktionen, ist es viel schwieriger , eine einzige Anweisung Performance Kosten zu quantifizieren , als es in C. Jetzt zusätzlich ist, Abstraktionen wie std::vector, std::stringsind in C häufig verwendete ++, die Leistungsmerkmale ihrer eigenen, und Speicherzuordnungen dynamisch auszublenden ( siehe auch @Ians Antwort).

Das Fazit lautet also: Im Allgemeinen gibt es keinen Unterschied in der möglichen Leistung, die mit C oder C ++ erzielt werden kann. Bei wirklich leistungskritischem Code bevorzugen die Benutzer jedoch häufig die Verwendung von C, da es weitaus weniger mögliche versteckte Leistungseinbußen gibt.

tödliche Gitarre
quelle
1
Hervorragende Antwort. Darauf habe ich in meiner Antwort angespielt, aber Sie haben es weitaus besser erklärt.
Ian Goldby
4
Dies sollte wirklich die akzeptierte Antwort sein. Es erklärt, warum Anweisungen wie "C ist schneller als C ++" existieren. C kann schneller oder langsamer sein als C ++, aber es ist in der Regel viel einfacher herauszufinden, warum ein bestimmtes Stück C-Code schnell / langsam ist, was in der Regel auch die Optimierung erleichtert.
Leo
Von dieser hervorragenden Antwort (für die ich +1 gebe) nichts zu nehmen, aber der Compiler kann die Äpfel und Orangen in diesem Vergleich sein. Es kann identischen Code für C vs. C ++ generieren oder nicht. Natürlich kann das Gleiche für zwei beliebige Compiler oder Compileroptionen gesagt werden, selbst wenn das physikalisch gleiche Programm mit den gleichen quellensprachlichen Annahmen kompiliert wird.
JRobert
4
Ich würde hinzufügen, dass die meisten C ++ - Laufzeiten im Vergleich zur entsprechenden C-Laufzeit massiv sind, was wichtig wäre, wenn Sie über begrenzten Arbeitsspeicher verfügen.
James Anderson
@JamesAnderson Wenn du wirklich so wenig Speicher hast, brauchst du wahrscheinlich überhaupt keine Laufzeit :)
Navin
30

In C ++ geschriebener Code kann für bestimmte Aufgabentypen schneller als in C sein.

Wenn Sie C ++ bevorzugen, verwenden Sie C ++. Leistungsprobleme sind im Vergleich zu algorithmischen Entscheidungen Ihrer Software unbedeutend.

Whatsisname
quelle
6
Es kann schneller sein, aber aus dem gleichen Grund auch nicht.
Rob
Können Sie einige Beispiele für optimierten Code nennen, der in C ++ geschrieben wurde und schneller ist als optimiertes C?
1
@TomDworzanski: Ein Beispiel ist, dass mithilfe von Vorlagen Entscheidungen über Codepfade zur Kompilierungszeit getroffen werden können und in der endgültigen Binärdatei fest codiert werden, anstatt Bedingungen und Verzweigungen, die erforderlich wären, wenn sie in c geschrieben wären, sowie Fähigkeiten um Funktionsaufrufe durch Inlining zu vermeiden.
Whatsisname
23

Eines der Designprinzipien von C ++ ist, dass Sie nicht für Funktionen bezahlen, die Sie nicht nutzen. Wenn Sie also Code in C ++ schreiben und Features vermeiden, die in C nicht vorhanden sind, sollte der resultierende kompilierte Code in der Leistung gleichwertig sein (obwohl Sie dies messen müssten).

Die Verwendung von Klassen ist beispielsweise im Vergleich zu Strukturen und einer Reihe zugehöriger Funktionen mit vernachlässigbaren Kosten verbunden. Virtuelle Funktionen kosten etwas mehr und Sie müssen die Leistung messen, um festzustellen, ob sie für Ihre Anwendung von Bedeutung sind. Gleiches gilt für alle anderen C ++ - Sprachfunktionen.

Greg Hewgill
quelle
3
Der Aufwand für den Versand virtueller Funktionen ist so gut wie vernachlässigbar, es sei denn, Sie haben sich beim Zerlegen und virtuellen Erstellen von Dingen über Bord geworfen. Die vtables sind im Vergleich zum Rest Ihres Codes und Ihrer Daten klein, und der indizierte Zweig durch die vtable fügt jedem Routineaufruf ein paar Takte hinzu. Angesichts der Tatsache, dass alle Routineaufrufe, die zur Rückkehr aufgerufen werden, zwischen einigen hundert und einigen Millionen Uhren liegen, wird der VTable-Zweig im Grundrauschen vergraben.
John R. Strohm
6
Strukturen sind Klassen in C ++.
Rightfold
2
@rightfold: Sicher, aber Sie können immer noch C ++ - Code schreiben, der Zeiger auf Strukturen umgibt, ohne die thisZeigersprachenfunktion zu verwenden. Das ist alles was ich sagte.
Greg Hewgill
4
@John Die realen Kosten sind nicht die Indirektion (obwohl ich mir ziemlich sicher bin, dass dies bei einigen Prozessoren auch ein wenig schadet), sondern die Tatsache, dass Sie keine virtuellen Funktionen inline können (zumindest in C ++), was viele andere Möglichkeiten verbietet Optimierungen. Und ja, das kann einen riesigen Einfluss auf die Leistung haben.
Voo
2
@Voo Um fair zu sein, das Gleiche gilt für äquivalenten C-Code (Code, der Laufzeitpolymorphismus manuell emuliert). Der größte Unterschied besteht darin, dass es meiner Meinung nach für einen Compiler einfacher wäre, festzustellen, ob diese Funktion in C ++ integriert werden kann.
Thomas Eding
14

Ein Grund dafür, dass höhere Sprachen manchmal langsamer sind, ist, dass sie sich hinter den Kulissen viel stärker als niedrigere Sprachen im Speichermanagement verstecken können.

Jede Sprache (oder Bibliothek, API usw.), die Details auf niedriger Ebene abstrahiert, kann möglicherweise kostspielige Vorgänge verbergen. In einigen Sprachen führt beispielsweise das einfache Trimmen von nachgestellten Leerzeichen aus einer Zeichenfolge zu einer Speicherzuweisung und einer Kopie der Zeichenfolge. Insbesondere die Speicherzuweisung und das Kopieren können teuer werden, wenn sie wiederholt in einer engen Schleife stattfinden.

Wenn Sie diese Art von Code in C schreiben würden, wäre dies offensichtlich. In C ++ ist dies möglicherweise weniger der Fall, da die Zuordnungen und das Kopieren irgendwo in eine Klasse abstrahiert werden könnten. Sie könnten sich sogar hinter einem unschuldig aussehenden überladenen Operator oder Kopierkonstruktor verstecken.

Verwenden Sie also C ++, wenn Sie möchten. Aber lassen Sie sich nicht von der scheinbaren Bequemlichkeit von Abstraktionen verführen, wenn Sie nicht wissen, was darunter liegt.

Verwenden Sie natürlich einen Profiler, um herauszufinden, was Ihren Code wirklich verlangsamt.

Ian Goldby
quelle
5

Ich neige dazu, meine Bibliotheken in C ++ 11 für den erweiterten Funktionsumfang zu schreiben. Ich mag es, Dinge wie gemeinsame Zeiger, Ausnahmen, generische Programmierung und andere reine C ++ - Funktionen zu nutzen. Ich mag C ++ 11, weil ich festgestellt habe, dass ein gutes Stück davon auf allen Plattformen unterstützt wird, die mir wichtig sind. Visual Studio 2013 verfügt über viele der wichtigsten Sprachfunktionen und Bibliotheksimplementierungen und arbeitet angeblich daran, den Rest hinzuzufügen. Wie Sie wissen, unterstützen sowohl Clang als auch GCC den gesamten Funktionsumfang.

Vor diesem Hintergrund habe ich kürzlich über eine wirklich gute Strategie in Bezug auf die Bibliotheksentwicklung gelesen, die meiner Meinung nach für Ihre Anfrage direkt relevant ist. Der Artikel trägt den Titel "AC-Fehlerbehandlungsstil, der mit C ++ - Ausnahmen gut funktioniert ". Stefanu Du Toit bezeichnet diese Strategie als "Sanduhr" -Muster. Der erste Absatz des Artikels:

Ich habe viel Bibliothekscode mit einem so genannten "Sanduhr" -Muster geschrieben: Ich implementiere eine Bibliothek (in meinem Fall normalerweise mit C ++) und verpacke sie in eine C-API, die der einzige Einstiegspunkt für die Bibliothek ist. Binden Sie dann diese C-API in C ++ oder eine andere (n) Sprache (n) ein, um eine reichhaltige Abstraktion und bequeme Syntax bereitzustellen. Wenn es um systemeigenen plattformübergreifenden Code geht, bieten C-APIs eine beispiellose ABI-Stabilität und Portabilität für andere Sprachen über FFIs. Ich beschränke die API sogar auf eine Teilmenge von C, von der ich weiß, dass sie auf eine Vielzahl von FFIs übertragbar ist, und isoliere die Bibliothek vor dem Verlust von Änderungen in internen Datenstrukturen. Erwarten Sie in zukünftigen Blog-Posts mehr davon.


Nun zu Ihrem Hauptanliegen: Leistung.

Ich würde vorschlagen, wie viele der anderen Antworten hier, dass das Schreiben von Code in beiden Sprachen unter Performance-Gesichtspunkten genauso gut funktioniert. Vom persönlichen Standpunkt aus finde ich es aufgrund der Sprachfunktionen einfacher, richtigen Code in C ++ zu schreiben, aber ich denke, das ist eine persönliche Präferenz. In beiden Fällen sind Compiler sehr schlau und schreiben normalerweise besseren Code als Sie. Das heißt, dass der Compiler Ihren Code wahrscheinlich besser optimieren wird, als Sie es könnten.

Ich weiß, dass viele Programmierer dies sagen, aber das erste, was Sie tun sollten, ist, Ihren Code zu schreiben, ihn dann zu profilieren und Optimierungen vorzunehmen, wo Ihr Profiler dies vorschlägt. Ihre Zeit wird viel besser damit verbracht, Features zu erstellen und sie dann zu optimieren, sobald Sie sehen, wo Ihre Engpässe sind.


Nun ein paar lustige Lektüren darüber, wie Sprachfunktionen und -optimierungen wirklich zu Ihren Gunsten wirken können:

std :: unique_ptr hat keinen Overhead

constexp ermöglicht die Berechnung zur Kompilierungszeit

Bewegungssemantik verhindert unnötige temporäre Objekte

vmrob
quelle
std::unique_ptr has zero overheadDies kann (technisch gesehen) unmöglich wahr sein, da der Konstruktor aufgerufen werden muss, wenn sich der Stapel aufgrund einer Ausnahme abwickelt. Ein roher Zeiger hat diesen Overhead nicht und ist trotzdem korrekt, wenn Ihr Code wahrscheinlich nicht wirft. Ein Compiler kann dies im allgemeinen Fall nicht nachweisen.
Thomas Eding
2
@ThomasEding Ich bezog mich auf Größe und Laufzeitaufwand in Bezug auf ausnahmefreien Code. Korrigieren Sie mich, wenn ich falsch liege, aber es gibt Ausführungsmodelle, die keinen Laufzeitaufwand verursachen, wenn keine Ausnahmen ausgelöst werden, sodass Ausnahmen bei Bedarf weitergegeben werden können. Trotzdem, wann könnte jemals eine Ausnahme in den Konstruktor von geworfen werden unique_ptr? Es ist deklariert noexceptund behandelt daher zumindest alle Ausnahmen, aber ich kann mir nicht vorstellen, welche Art von Ausnahme überhaupt überhaupt geworfen werden könnte.
vmrob
vmrob: Verzeihung ... ich wollte "destructor" anstelle von "constructor" schreiben. Ich wollte auch "nachweislich nicht werfen" schreiben. Eeek!
Thomas Eding
2
@ThomasEding Weißt du, ich glaube nicht, dass es wichtig wäre, wenn der Destruktor eine Ausnahme auslösen würde. Solange der Destruktor keine neuen Ausnahmen einführt, ist es immer noch keine Overhead-Zerstörung. Darüber hinaus glaube ich, dass der gesamte Destruktor mit Optimierungen in einen einzelnen Lösch- / Freigabeaufruf eingebunden wird.
vmrob
4

Der Leistungsunterschied zwischen C ++ und C ist streng genommen nichts in der Sprache zu suchen, sondern in dem, was Sie dazu verleitet. Es ist wie eine Kreditkarte gegen Bargeld. Es bringt Sie nicht dazu, mehr auszugeben, aber Sie tun es trotzdem, es sei denn, Sie sind sehr diszipliniert.

Hier ist ein Beispiel eines in C ++ geschriebenen Programms, das dann aggressiv an der Leistung angepasst wurde. Sie müssen wissen, wie Sie eine aggressive Leistungsoptimierung durchführen, unabhängig von der Sprache. Die Methode, die ich benutze, ist zufälliges Anhalten, wie in diesem Video gezeigt .

Die kostspieligen Dinge, die C ++ zu tun versucht, sind übermäßiges Speichermanagement, Programmierung im Benachrichtigungsstil, Vertrauen in die mehrschichtigen Abstraktionsbibliotheken (wie @Ian sagte), langsames Verstecken usw.

Mike Dunlavey
quelle
2

C hat keinen Leistungsvorteil gegenüber C ++, wenn Sie in beiden Sprachen die gleichen Aktionen ausführen. Sie können jeden alten C-Code, der von einem anständigen C-Programmierer geschrieben wurde, in gültigen und äquivalenten C ++ - Code umwandeln, der genauso schnell ausgeführt wird (es sei denn, Sie und Ihr Compiler wissen, was das Schlüsselwort "restricted" bewirkt, und Sie verwenden es effektiv. aber die meisten Leute nicht).

C ++ kann eine sehr unterschiedliche Leistung aufweisen, entweder langsamer oder schneller, wenn (1) Sie die Standard-C ++ - Bibliothek verwenden, um Dinge zu erledigen, die ohne die Bibliothek viel schneller und einfacher erledigt werden können, oder (2) wenn Sie die Standard-C ++ - Bibliothek verwenden Es ist viel einfacher und schneller, als die Bibliothek in Bad C neu zu implementieren.

gnasher729
quelle
1
dies scheint nicht zu bieten alles wesentliche über das, was in 6 vor Antworten erklärt wurde
gnat
Ich denke, diese Antwort erwähnt einen wichtigen Punkt, den niemand sonst erwähnt hat. Auf den ersten Blick scheint C ++ eine Obermenge von C zu sein. Wenn Sie also eine schnelle C-Implementierung schreiben können, sollten Sie in der Lage sein, eine C ++ - Implementierung zu schreiben, die äquivalent ist. C99 unterstützt jedoch das Schlüsselwort restricted, mit dem ein unbeabsichtigtes Aliasing von Zeigern vermieden werden kann. C ++ hat keine solche Unterstützung. Die Fähigkeit, Pointer-Aliasing zu vermeiden, ist ein wichtiges Merkmal von Fortran, das es für Hochleistungsanwendungen nützlich macht. Ich gehe davon aus, dass es in ähnlichen Bereichen auch möglich ist, eine bessere Leistung von C99 als von C ++ zu erzielen.
user27539