502 Stimmen

Wann ist Assembler schneller als C?

Einer der angegebenen Gründe für Assembler-Kenntnisse ist, dass man damit gelegentlich Code schreiben kann, der leistungsfähiger ist als der Code in einer höheren Sprache, insbesondere C. Ich habe aber auch schon oft gehört, dass, obwohl das nicht ganz falsch ist, die Fälle, in denen Assembler helfen kann eigentlich zur Generierung von leistungsfähigerem Code verwendet werden können, sind extrem selten und erfordern Expertenwissen und Erfahrung mit Assembler.

Diese Frage geht noch nicht einmal auf die Tatsache ein, dass Assembler-Anweisungen maschinenspezifisch und nicht portierbar sind, oder auf andere Aspekte von Assembler. Es gibt viele gute Gründe, Assembler zu kennen, abgesehen von diesem, natürlich, aber dies soll eine spezifische Frage sein, die nach Beispielen und Daten fragt, nicht ein ausgedehnter Diskurs über Assembler gegenüber höheren Sprachen.

Kann jemand etwas über spezifische Beispiele in welchen Fällen Assembler schneller ist als gut geschriebener C-Code mit einem modernen Compiler, und können Sie diese Behauptung mit Profilergebnissen belegen? Ich bin mir ziemlich sicher, dass es diese Fälle gibt, aber ich möchte wirklich genau wissen, wie esoterisch diese Fälle sind, da dies ein Punkt zu sein scheint, über den man streiten kann.

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.

41voto

Nir Punkte 28685

Nur bei der Verwendung einiger spezieller Befehlssätze, die der Compiler nicht unterstützt.

Um die Rechenleistung einer modernen CPU mit mehreren Pipelines und vorausschauender Verzweigung zu maximieren, müssen Sie das Assemblerprogramm so strukturieren, dass es a) für einen Menschen fast unmöglich ist, es zu schreiben, und b) noch unmöglicher zu warten.

Bessere Algorithmen, Datenstrukturen und Speicherverwaltung bringen mindestens eine Größenordnung mehr Leistung als die Mikrooptimierungen, die Sie in Assembler vornehmen können.

4 Stimmen

+1, auch wenn der letzte Satz nicht wirklich in diese Diskussion gehört - man sollte annehmen, dass Assembler erst dann ins Spiel kommt, wenn alle möglichen Verbesserungen des Algorithmus usw. realisiert wurden.

0 Stimmen

Das ist wirklich wahr. Ich kann gar nicht zählen, wie oft ich mit EEs oder CEs gestritten habe, die behaupteten, handgeschriebene Assembler seien besser.

18 Stimmen

@Matt: Handgeschriebenes ASM ist oft ein ロット besser auf einigen der winzigen CPUs, mit denen EEs arbeiten und die eine miserable Compilerunterstützung vom Hersteller haben.

39voto

Jason S Punkte 178087

Obwohl C der Low-Level-Manipulation von 8-Bit-, 16-Bit-, 32-Bit- und 64-Bit-Daten "nahe steht", gibt es einige mathematische Operationen, die von C nicht unterstützt werden und die in bestimmten Assembler-Befehlssätzen oft elegant ausgeführt werden können:

  1. Festkommamultiplikation: Das Produkt von zwei 16-Bit-Zahlen ist eine 32-Bit-Zahl. Die Regeln in C besagen jedoch, dass das Produkt von zwei 16-Bit-Zahlen eine 16-Bit-Zahl ist und das Produkt von zwei 32-Bit-Zahlen eine 32-Bit-Zahl ist - in beiden Fällen die untere Hälfte. Wenn Sie die top die Hälfte einer 16x16-Multiplikation oder einer 32x32-Multiplikation, müssen Sie mit dem Compiler spielen. Die allgemeine Methode besteht darin, auf eine größere als die notwendige Bitbreite zu casten, zu multiplizieren, nach unten zu verschieben und zurück zu casten:

    int16_t x, y;
    // int16_t is a typedef for "short"
    // set x and y to something
    int16_t prod = (int16_t)(((int32_t)x*y)>>16);`

    In diesem Fall kann der Compiler klug genug sein, um zu wissen, dass Sie wirklich nur versuchen, die obere Hälfte einer 16x16-Multiplikation zu erhalten und das Richtige mit der maschineneigenen 16x16-Multiplikation zu tun. Oder er ist dumm und verlangt einen Bibliotheksaufruf, um die 32x32-Multiplikation durchzuführen, was ein Overkill ist, weil man nur 16 Bits des Produkts braucht - aber der C-Standard gibt einem keine Möglichkeit, sich auszudrücken.

  2. Bestimmte Bitverschiebungsoperationen (Rotation/Carries):

    // 256-bit array shifted right in its entirety:
    uint8_t x[32];
    for (int i = 32; --i > 0; )
    {
       x[i] = (x[i] >> 1) | (x[i-1] << 7);
    }
    x[0] >>= 1;

    Dies ist in C nicht allzu unelegant, aber auch hier gilt: Wenn der Compiler nicht intelligent genug ist, um zu erkennen, was Sie tun, wird er eine Menge "unnötiger" Arbeit leisten. Viele Assembler-Befehlssätze erlauben es, mit dem Ergebnis im Übertragsregister nach links/rechts zu rotieren oder zu schieben, so dass man die oben genannten Aufgaben mit 34 Befehlen erledigen könnte: Laden eines Zeigers auf den Anfang des Arrays, Löschen des Übertrags und Ausführen von 32 8-Bit-Rechtsverschiebungen unter Verwendung der automatischen Inkrementierung des Zeigers.

    Ein weiteres Beispiel: Es gibt Schieberegister mit linearer Rückkopplung (LFSR), die elegant im Assembler ausgeführt werden: Man nehme ein Stück von N Bits (8, 16, 32, 64, 128 usw.), verschiebe das Ganze um 1 nach rechts (siehe obigen Algorithmus), und wenn der resultierende Übertrag 1 ist, wird ein Bitmuster, das das Polynom darstellt, XOR-verknüpft.

Allerdings würde ich auf diese Techniken nur zurückgreifen, wenn ich ernsthafte Leistungseinschränkungen hätte. Wie andere bereits gesagt haben, ist Assembler viel schwieriger zu dokumentieren, zu debuggen, zu testen und zu warten als C-Code: Der Leistungsgewinn ist mit einigen ernsthaften Kosten verbunden.

bearbeiten: 3. Die Erkennung von Überläufen ist in Assembler möglich (in C ist das nicht möglich), was einige Algorithmen wesentlich einfacher macht.

23voto

cletus Punkte 596503

Kurze Antwort? Manchmal.

Technisch gesehen hat jede Abstraktion ihren Preis, und eine Programmiersprache ist eine Abstraktion davon, wie die CPU funktioniert. C ist jedoch sehr nahe dran. Ich erinnere mich, dass ich vor Jahren laut lachen musste, als ich mich bei meinem UNIX-Konto anmeldete und die folgende Glücksnachricht erhielt (als solche Dinge noch populär waren):

Die Programmiersprache C -- A Sprache, die die Flexibilität der Assemblersprache mit mit der Leistungsfähigkeit der Assemblersprache.

Es ist lustig, weil es wahr ist: C ist wie ein portabler Assembler.

Es ist erwähnenswert, dass Assemblersprache einfach läuft, egal wie man sie schreibt. Es gibt jedoch einen Compiler zwischen C und der Assemblersprache, die er erzeugt, und das ist extrem wichtig, weil Wie schnell Ihr C-Code ist, hat sehr viel damit zu tun, wie gut Ihr Compiler ist.

Als der gcc auf den Markt kam, war er unter anderem deshalb so beliebt, weil er oft so viel besser war als die C-Compiler, die mit vielen kommerziellen UNIX-Varianten ausgeliefert wurden. Er war nicht nur ANSI C (nichts von diesem K&R C-Müll), sondern auch robuster und produzierte in der Regel besseren (schnelleren) Code. Nicht immer, aber oft.

Ich sage Ihnen das alles, weil es keine pauschale Regel über die Geschwindigkeit von C und Assembler gibt, weil es keinen objektiven Standard für C gibt.

Auch bei Assembler gibt es große Unterschiede, je nachdem, welchen Prozessor Sie verwenden, wie Ihr System beschaffen ist, welchen Befehlssatz Sie benutzen und so weiter. Historisch gesehen gab es zwei CPU-Architekturfamilien: CISC und RISC. Der größte Akteur im Bereich CISC war und ist die x86-Architektur (und der Befehlssatz) von Intel. RISC dominierte die UNIX-Welt (MIPS6000, Alpha, Sparc und so weiter). CISC hat den Kampf um die Herzen und Köpfe der Menschen gewonnen.

Wie auch immer, als ich ein jüngerer Entwickler war, lautete die gängige Meinung, dass handgeschriebene x86-Programme oft viel schneller sind als C, weil die Architektur so komplex ist, dass es von Vorteil ist, wenn ein Mensch sie ausführt. RISC hingegen schien für Compiler konzipiert zu sein, so dass niemand (den ich kannte) einen Sparc-Assembler schrieb. Ich bin mir sicher, dass es solche Leute gab, aber zweifelsohne sind sie inzwischen beide verrückt geworden und institutionalisiert worden.

Die Befehlssätze sind ein wichtiger Punkt, selbst in ein und derselben Prozessorfamilie. Bestimmte Intel-Prozessoren haben Erweiterungen wie SSE bis SSE4. AMD hatte seine eigenen SIMD-Befehle. Der Vorteil einer Programmiersprache wie C bestand darin, dass jemand seine Bibliothek so schreiben konnte, dass sie für den jeweiligen Prozessor optimiert war, auf dem sie lief. In Assembler war das harte Arbeit.

Es gibt immer noch Optimierungen, die man in Assembler machen kann, die kein Compiler machen könnte, und ein gut geschriebener Assembler-Algorithmus wird genauso schnell oder schneller sein als sein C-Äquivalent. Die größere Frage ist: ist es das wert?

Letztendlich war Assembler jedoch ein Produkt seiner Zeit und wurde zu einer Zeit populärer, als CPU-Zyklen teuer waren. Heutzutage kann eine CPU, die in der Herstellung 5-10 $ kostet (Intel Atom), so ziemlich alles tun, was man sich wünschen kann. Der einzige wirkliche Grund, heutzutage Assembler zu schreiben, ist für Low-Level-Sachen wie einige Teile eines Betriebssystems (auch wenn der Großteil des Linux-Kernels in C geschrieben ist), Gerätetreiber, möglicherweise eingebettete Geräte (obwohl C auch dort dominiert) und so weiter. Oder einfach nur so zum Spaß (was etwas masochistisch ist).

0 Stimmen

Es gab viele Leute, die ARM-Assembler als Sprache der Wahl auf Acorn-Maschinen (Anfang der 90er Jahre) verwendeten. Ich erinnere mich, dass sie sagten, dass der kleine Risc-Befehlssatz es einfacher machte und mehr Spaß. Aber ich vermute, das liegt daran, dass der C-Compiler für Acorn zu spät kam und der C++-Compiler nie fertig wurde.

0 Stimmen

@AndrewM: Ja, ich habe etwa 10 Jahre lang gemischtsprachige Anwendungen in BASIC und ARM-Assembler geschrieben. In dieser Zeit habe ich C gelernt, aber es war nicht sehr nützlich, weil es genauso umständlich wie Assembler und langsamer ist. Norcroft hat einige großartige Optimierungen vorgenommen, aber ich glaube, der bedingte Befehlssatz war ein Problem für die damaligen Compiler.

1 Stimmen

@AndrewM: Nun, eigentlich ist ARM eine Art RISC rückwärts. Andere RISC ISAs wurden ausgehend davon entwickelt, was ein Compiler verwenden würde. Der ARM ISA scheint von dem auszugehen, was die CPU zur Verfügung stellt (Barrel Shifter, Condition Flags, die in jeder Anweisung enthalten sein sollten).

17voto

BlackBear Punkte 21476

Ich bin überrascht, dass das noch niemand gesagt hat. Die strlen() Funktion ist viel schneller, wenn sie in Assembler geschrieben ist! In C ist das Beste, was Sie tun können

int c;
for(c = 0; str[c] != '\0'; c++) {}

während Sie bei der Montage den Vorgang erheblich beschleunigen können:

mov esi, offset string
mov edi, esi
xor ecx, ecx

lp:
mov ax, byte ptr [esi]
cmp al, cl
je  end_1
cmp ah, cl
je end_2
mov bx, byte ptr [esi + 2]
cmp bl, cl
je end_3
cmp bh, cl
je end_4
add esi, 4
jmp lp

end_4:
inc esi

end_3:
inc esi

end_2:
inc esi

end_1:
inc esi

mov ecx, esi
sub ecx, edi

die Länge ist in ecx angegeben. Es werden 4 Zeichen auf einmal verglichen, also ist es 4 mal schneller. Und wenn man das Wort hoher Ordnung von eax und ebx verwendet, wird es zu 8-mal schneller dass die vorherige C-Routine!

3 Stimmen

Wie verhält sich dies im Vergleich zu den in strchr.nfshost.com/optimierte_strlen_funktion ?

0 Stimmen

@ninjalj: sie sind dasselbe :) ich dachte nicht, dass es so in C gemacht werden kann. Es kann leicht verbessert werden, denke ich

0 Stimmen

Im C-Code ist vor jedem Vergleich noch eine bitweise UND-Verknüpfung vorgesehen. Es ist möglich, dass der Compiler intelligent genug ist, um dies auf High und Low Byte Vergleiche zu reduzieren, aber darauf würde ich nicht wetten. Es gibt tatsächlich einen schnelleren Schleifenalgorithmus, der auf der Eigenschaft basiert, dass (word & 0xFEFEFEFF) & (~word + 0x80808080) ist Null, wenn alle Bytes im Wort ungleich Null sind.

16voto

David Waters Punkte 11759

Punkt eins, der nicht die Antwort ist.
Auch wenn man nie in Assembler programmiert, finde ich es nützlich, zumindest einen Assembler-Befehlssatz zu kennen. Dies ist Teil des nie endenden Strebens der Programmierer, mehr zu wissen und deshalb besser zu sein. Es ist auch nützlich, wenn man in Frameworks einsteigt, für die man den Quellcode nicht hat, und zumindest eine ungefähre Vorstellung davon hat, was da vor sich geht. Es hilft Ihnen auch, JavaByteCode und .Net IL zu verstehen, da beide dem Assembler ähnlich sind.

Um die Frage zu beantworten, wann Sie eine kleine Menge an Code oder eine große Menge an Zeit haben. Dies ist vor allem bei eingebetteten Chips nützlich, bei denen die geringe Komplexität des Chips und der geringe Wettbewerb bei Compilern, die auf diese Chips abzielen, das Gleichgewicht zu Gunsten des Menschen verschieben können. Auch bei Geräten mit eingeschränktem Funktionsumfang müssen Sie oft einen Kompromiss zwischen Codegröße/Speichergröße/Leistung eingehen, der einem Compiler nur schwer zu vermitteln ist. z. B. weiß ich, dass diese Benutzeraktion nicht oft aufgerufen wird, so dass ich eine kleine Codegröße und schlechte Leistung habe, aber diese andere Funktion, die ähnlich aussieht, wird jede Sekunde verwendet, so dass ich eine größere Codegröße und schnellere Leistung habe. Das ist die Art von Kompromiss, den ein erfahrener Assembler-Programmierer eingehen kann.

Ich möchte auch hinzufügen, dass es eine Menge von Mittelweg, wo Sie Code in C kompilieren und prüfen die Assembly produziert, dann entweder ändern Sie C-Code oder optimieren und pflegen als Assembly.

Mein Freund arbeitet an Mikrocontrollern, derzeit Chips zur Steuerung kleiner Elektromotoren. Er arbeitet in einer Kombination aus Low Level C und Assembly. Er erzählte mir einmal von einem guten Arbeitstag, an dem er die Hauptschleife von 48 Anweisungen auf 43 reduziert hat. Er steht auch vor Entscheidungen wie der Code ist so groß geworden, dass er den 256k-Chip ausfüllt, und das Unternehmen möchte eine neue Funktion.

  1. Ein bestehendes Merkmal entfernen
  2. Verringern Sie den Umfang einiger oder aller vorhandenen Funktionen, möglicherweise auf Kosten der Leistung.
  3. Befürworten Sie den Wechsel zu einem größeren Chip mit höheren Kosten, höherem Stromverbrauch und größerem Formfaktor.

Ich möchte hinzufügen, dass ich als kommerzieller Entwickler mit einem breiten Portfolio an Sprachen, Plattformen und Anwendungstypen noch nie das Bedürfnis hatte, Assembler zu schreiben. Ich habe jedoch immer das Wissen, das ich darüber gewonnen habe, geschätzt. Und manchmal habe ich damit debuggt.

Ich weiß, dass ich die Frage "Warum sollte ich Assembler lernen?" viel mehr beantwortet habe, aber ich denke, es ist eine wichtigere Frage als die, wann es schneller geht.

also versuchen wir es noch einmal Sie sollten über die Montage nachdenken

  • Arbeit an Funktionen des Betriebssystems auf niedriger Ebene
  • Arbeit an einem Compiler.
  • Arbeiten an einem extrem begrenzten Chip, eingebetteten System usw.

Vergessen Sie nicht, Ihre Assemblierung mit der vom Compiler erzeugten zu vergleichen, um festzustellen, welche schneller/kleiner/besser ist.

David.

4 Stimmen

+1 für die Berücksichtigung eingebetteter Anwendungen auf winzigen Chips. Zu viele Software-Ingenieure hier denken entweder nicht an eingebettete Anwendungen oder meinen, dass damit ein Smartphone gemeint ist (32 Bit, MB RAM, MB Flash).

1 Stimmen

Zeitlich eingebettete Anwendungen sind ein gutes Beispiel! Es gibt oft seltsame Befehle (sogar sehr einfache wie die von avr sbi y cbi ), die die Compiler aufgrund ihrer begrenzten Kenntnis der Hardware früher (und manchmal auch heute noch) nicht voll ausnutzen.

0 Stimmen

Sie schreiben: "Das ist Teil des nie endenden Strebens der Programmierer, mehr zu wissen und deshalb besser zu sein", aber ich bin anderer Meinung. Ich würde es so ausdrücken: "Dies ist Teil des nie endenden Strebens einiger Programmierer, mehr zu wissen und deshalb besser zu sein". Den meisten ist das völlig egal.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X