Ich habe alle Antworten gelesen (mehr als 30) und habe keinen einfachen Grund gefunden: Assembler ist schneller als C, wenn man die Regeln gelesen und geübt hat. Handbuch zur Optimierung von Intel® 64- und IA-32-Architekturen , Der Grund, warum Assembler langsamer sein kann, ist also, dass die Leute, die solch langsames Assembler schreiben, das Optimierungshandbuch nicht gelesen haben. .
In den guten alten Zeiten des Intel 80286 wurde jeder Befehl mit einer festen Anzahl von CPU-Zyklen ausgeführt. Doch seit dem Pentium Pro, der 1995 auf den Markt kam, sind die Intel-Prozessoren superskalar und nutzen Complex Pipelining: Out-of-Order Execution & Register Renaming. Davor, beim Pentium, der 1993 hergestellt wurde, gab es U- und V-Pipelines. Mit dem Pentium wurden daher duale Pipelines eingeführt, die zwei einfache Befehle in einem Taktzyklus ausführen konnten, sofern sie nicht voneinander abhängig waren. Dies war jedoch nichts im Vergleich zur Out-of-Order Execution & Register Renaming, die mit dem Pentium Pro eingeführt wurde. Dieser mit dem Pentium Pro eingeführte Ansatz ist heute bei den meisten aktuellen Intel-Prozessoren praktisch identisch.
Lassen Sie mich die "Out-of-Order Execution" in wenigen Worten erklären. Der schnellste Code ist der, bei dem die Anweisungen nicht von vorherigen Ergebnissen abhängen, z.B. sollte man immer ganze Register löschen (durch movzx
), um die Abhängigkeit von früheren Werten der Register, mit denen Sie arbeiten, zu beseitigen, so dass sie intern von der CPU umbenannt werden können, um die parallele Ausführung von Befehlen oder eine andere Reihenfolge zu ermöglichen. Auf manchen Prozessoren können auch falsche Abhängigkeiten bestehen, die die Arbeit verlangsamen können, wie z.B. falsche Abhängigkeit von Pentium 4 für inc/dec Sie können also Folgendes verwenden add eax, 1
stattdessen oder inc eax
um die Abhängigkeit vom vorherigen Zustand der Flaggen aufzuheben.
Wenn es Ihre Zeit erlaubt, können Sie mehr über Out-of-Order Execution & Register Renaming lesen. Im Internet sind zahlreiche Informationen verfügbar.
Es gibt noch viele andere wichtige Aspekte wie die Verzweigungsvorhersage, die Anzahl der Lade- und Speichereinheiten, die Anzahl der Gatter, die Mikro-OPs ausführen, Speicher-Cache-Kohärenzprotokolle usw., aber das Entscheidende ist die Out-of-Order-Ausführung. Die meisten Menschen sind sich der Out-of-Order Execution einfach nicht bewusst. Daher schreiben sie ihre Assembler-Programme wie für den 80286 und gehen davon aus, dass ihre Befehle unabhängig vom Kontext eine bestimmte Zeit für die Ausführung benötigen. Gleichzeitig sind sich C-Compiler der Out-of-Order Execution bewusst und generieren den Code korrekt. Deshalb ist der Code solcher uninformierten Leute langsamer, aber wenn Sie sich damit auskennen, wird Ihr Code schneller sein.
Neben der Out-of-Order Execution gibt es noch viele weitere Tipps und Tricks zur Optimierung. Lesen Sie einfach das oben erwähnte Optimierungshandbuch :-)
Allerdings hat die Assemblersprache ihre eigenen Nachteile, wenn es um die Optimierung geht. Laut Peter Cordes (siehe Kommentar unten) wären einige der Optimierungen, die Compiler vornehmen, für große Code-Basen in handgeschriebener Assembler-Sprache nicht mehr wartbar. Nehmen wir zum Beispiel an, Sie schreiben in Assembler. In diesem Fall müssen Sie eine Inline-Funktion (ein Assembler-Makro) beim Inlinen vollständig in eine Funktion ändern, die sie aufruft, wobei einige Argumente Konstanten sind. Gleichzeitig macht ein C-Compiler seine Arbeit sehr viel einfacher, indem er denselben Code auf unterschiedliche Weise in verschiedene Aufrufstellen einbindet. Die Möglichkeiten von Assembly-Makros sind begrenzt. Um also den gleichen Nutzen zu erzielen, müssten Sie dieselbe Logik an jeder Stelle manuell optimieren, um sie an die Konstanten und verfügbaren Register anzupassen, die Ihnen zur Verfügung stehen.
0 Stimmen
Und nun wäre eine weitere Frage angebracht: Wann ist die Tatsache, dass Assembler schneller ist als C, tatsächlich von Bedeutung?
20 Stimmen
Eigentlich ist es recht trivial, kompilierten Code zu verbessern. Jeder, der über solide Kenntnisse in Assembler und C verfügt, kann dies erkennen, indem er den erzeugten Code untersucht. Eine einfache Möglichkeit ist die erste Leistungsklippe, von der man herunterfällt, wenn man in der kompilierten Version keine freien Register mehr hat. Im Durchschnitt wird der Compiler bei einem großen Projekt weitaus besser abschneiden als ein Mensch, aber es ist nicht schwer, bei einem Projekt von angemessener Größe Leistungsprobleme im kompilierten Code zu finden.
19 Stimmen
Die kurze Antwort lautet eigentlich: Assembler ist siempre Der Grund dafür ist, dass man Assembler ohne C haben kann, aber man kann C nicht ohne Assembler haben (in der binären Form, die wir früher "Maschinencode" nannten). Das heißt, die lange Antwort ist: C-Compiler sind ziemlich gut darin, zu optimieren und über Dinge "nachzudenken", an die man normalerweise nicht denkt, also hängt es wirklich von Ihren Fähigkeiten ab, aber normalerweise können Sie den C-Compiler immer schlagen; es ist immer noch nur eine Software, die nicht denken und Ideen bekommen kann. Sie können auch portablen Assembler schreiben, wenn Sie Makros verwenden und geduldig sind.
13 Stimmen
Ich bin ganz und gar nicht der Meinung, dass Antworten auf diese Frage "meinungsbasiert" sein müssen - sie können durchaus objektiv sein - es ist nicht so, als würde man versuchen, die Leistung der Lieblingssprachen zu vergleichen, für die jede von ihnen Stärken und Schwächen hat. Hier geht es darum, zu verstehen, wie weit uns Compiler bringen können und ab welchem Punkt es besser ist, sie zu übernehmen.
0 Stimmen
Es ist nicht einmal immer der Fall, dass Sie etwas in Assembler umschreiben müssen, um die Vorteile der Assembler-Kenntnisse zu nutzen. Wenn Sie Ihren C-Algorithmus einfach in verschiedenen Formen neu kompilieren und die vom Compiler erzeugte Assemblerdatei beobachten, können Sie effizienteren Code in C schreiben.
0 Stimmen
Ein esoterisches Beispiel: Suchen Sie im Internet nach
pclmulqdq crc
. pclmulqdq ist eine spezielle Assembler-Anweisung. Die optimierten Beispiele benötigen etwa 500 Zeilen Assemblercode. Einige X86 haben auch einencrc32c
Anweisung für einen bestimmten Fall von crc32. Benchmark-Ergebnisse für die Erzeugung von crc32 über ein 256MB (256*1024*1024) Byte-Array: c-Code mit Tabelle => 0,516749 sec, Assembler mit pcmuldq => 0,0783919 sec, c-Code mit crc32 intrinsic => 0,0541801 sec.30 Stimmen
Zu Beginn meiner beruflichen Laufbahn habe ich in einer Softwarefirma viel in C und Mainframe-Assembler geschrieben. Einer meiner Kollegen war, wie ich es nennen würde, ein "Assembler-Purist" (alles musste in Assembler sein), also wettete ich mit ihm, dass ich eine bestimmte Routine schreiben könnte, die in C schneller lief als das, was er in Assembler schreiben konnte. Ich habe gewonnen. Nachdem ich gewonnen hatte, sagte ich ihm, ich wolle eine zweite Wette abschließen - dass ich etwas in Assembler schneller schreiben könne als das C-Programm, das ihn bei der ersten Wette geschlagen hatte. Auch diese Wette habe ich gewonnen, was beweist, dass es mehr auf die Fähigkeiten des Programmierers ankommt als auf alles andere.
0 Stimmen
@ValerieR Nun, Sie haben auch bewiesen, dass Ihr Assemblerprogramm schneller war als Ihr C-Programm :-) Vielleicht könnte man sagen, dass Sie unabhängig von Ihrem Kenntnisstand in der C-Programmierung wahrscheinlich ein Assembler-Programm schreiben können, das schneller ist?
1 Stimmen
@RobertF: Wir lassen bei diesen Fragen oft den Teil "zu welchem Preis" weg. Ich kann schnelles C oder Assembler schreiben - manchmal ist das C billiger zu schreiben, und manchmal ist der Assembler billiger zu schreiben. Geschwindigkeit kommt oft von zwei Seiten: bessere Algorithmen oder Ausnutzung der Low-Level-Infrastruktur - Quicksort in C wird typischerweise schneller sein als Bubble Sort in Assembler. Aber wenn Sie identische Logik in beiden implementieren, bietet Ihnen der Assembler normalerweise Möglichkeiten, die Maschinenarchitektur besser auszunutzen als der Compiler - der Compiler ist universell einsetzbar, und Sie erstellen eine spezifische Anpassung für einen einzigen Anwendungsfall.
0 Stimmen
Ich habe mehrere M68000-Ersetzungen für C-Funktionen (memset, snprintf usw.) geschrieben, die erheblich schneller (und immer noch sicherer) als ihre C-Pendants sind. Ich habe auch Testcode geschrieben, um zu überprüfen, dass es a) funktioniert und b) tatsächlich schneller ist.