it-swarm.com.de

Wann ist Assembly schneller als C?

Einer der angegebenen Gründe für das Kennenlernen von Assembler ist, dass es gelegentlich verwendet werden kann, um Code zu schreiben, der leistungsfähiger ist als das Schreiben dieses Codes in einer höheren Sprache, insbesondere C. Ich habe jedoch auch schon oft gehört, dass Assembler tatsächlich verwendet werden kann, um leistungsfähigeren Code zu generieren, obwohl dies nicht ganz falsch ist Beides ist äußerst selten und erfordert Fachwissen und Erfahrung in der Montage.

Diese Frage geht nicht einmal auf die Tatsache ein, dass Assembler-Anweisungen maschinenspezifisch und nicht portierbar sind, oder auf einen der anderen Aspekte von Assembler. Es gibt natürlich viele gute Gründe, Assembly neben diesem zu kennen, aber dies soll eine spezifische Frage sein, die Beispiele und Daten anfordert, und nicht einen erweiterten Diskurs über Assembler im Vergleich zu übergeordneten Sprachen.

Kann jemand einige spezifische Beispiele für Fälle nennen, in denen Assembly mithilfe eines modernen Compilers schneller ist als gut geschriebener C-Code, und können Sie diese Behauptung durch Profilerstellung unterstützen? Beweise? 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 Streitpunkt zu sein scheint.

451
Adam Bellaire

Hier ist ein Beispiel aus der Praxis: Fixpunktmultiplikatoren auf alten Compilern.

Diese sind nicht nur für Geräte ohne Gleitkommazahl nützlich, sondern überzeugen auch durch Präzision, da sie eine Genauigkeit von 32 Bit mit einem vorhersagbaren Fehler bieten (Float hat nur 23 Bit und es ist schwieriger, einen Präzisionsverlust vorherzusagen). d.h. gleichmäßige absolut Präzision über den gesamten Bereich, anstatt nahezu gleichmäßige relativ Präzision (float).


Moderne Compiler optimieren dieses Festkomma-Beispiel sehr gut. Moderne Beispiele, die noch compilerspezifischen Code benötigen, finden Sie hier


C hat keinen Vollmultiplikationsoperator (2N-Bit-Ergebnis von N-Bit-Eingaben). Die übliche Art, es in C auszudrücken, besteht darin, die Eingaben in den breiteren Typ umzuwandeln und zu hoffen, dass der Compiler erkennt, dass die oberen Bits der Eingaben nicht interessant sind:

// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
  long long a_long = a; // cast to 64 bit.

  long long product = a_long * b; // perform multiplication

  return (int) (product >> 16);  // shift by the fixed point bias
}

Das Problem mit diesem Code ist, dass wir etwas tun, das nicht direkt in der C-Sprache ausgedrückt werden kann. Wir wollen zwei 32-Bit-Zahlen multiplizieren und ein 64-Bit-Ergebnis erhalten, von dem wir das mittlere 32-Bit zurückgeben. In C existiert diese Multiplikation jedoch nicht. Alles, was Sie tun können, ist, die Ganzzahlen auf 64-Bit hochzustufen und eine 64 * 64 = 64-Multiplikation durchzuführen.

x86 (und ARM, MIPS und andere) können die Multiplikation jedoch in einem einzigen Befehl ausführen. Einige Compiler haben diese Tatsache ignoriert und Code generiert, der eine Laufzeitbibliotheksfunktion aufruft, um die Multiplikation durchzuführen. Die Verschiebung um 16 erfolgt häufig auch durch eine Bibliotheksroutine (auch der x86 kann solche Verschiebungen ausführen).

Wir haben also nur noch ein oder zwei Bibliotheksaufrufe für eine Multiplikation. Dies hat schwerwiegende Folgen. Die Verschiebung ist nicht nur langsamer, die Register müssen über die Funktionsaufrufe hinweg beibehalten werden, und es hilft auch nicht beim Inlinen und Auflösen des Codes.

Wenn Sie denselben Code in (Inline-) Assembler umschreiben, können Sie eine erhebliche Geschwindigkeitssteigerung erzielen.

Darüber hinaus ist die Verwendung von ASM nicht der beste Weg, um das Problem zu lösen. Bei den meisten Compilern können Sie einige Assembler-Anweisungen in intrinsischer Form verwenden, wenn Sie sie nicht in C ausdrücken können. Der VS.NET2008-Compiler macht beispielsweise die 32 * 32 = 64-Bit-Mul als Emul und die 64-Bit-Verschiebung als All_Rshift verfügbar.

Mit intrinsics können Sie die Funktion so umschreiben, dass der C-Compiler die Möglichkeit hat, zu verstehen, was vor sich geht. Auf diese Weise kann der Code inline geschrieben, das Register zugewiesen, gemeinsame Unterausdrücke entfernt und eine konstante Weitergabe durchgeführt werden. Auf diese Weise erhalten Sie eine riesige Leistungsverbesserung gegenüber dem handgeschriebenen Assembler-Code.

Als Referenz: Das Endergebnis für den Festkomma-Mul für den VS.NET-Compiler lautet:

int inline FixedPointMul (int a, int b)
{
    return (int) __ll_rshift(__emul(a,b),16);
}

Der Leistungsunterschied von Festkommadivisionen ist sogar noch größer. Ich hatte Verbesserungen bis zum Faktor 10 für teilungslastigen Festkomma-Code, indem ich ein paar asm-Zeilen schrieb.


Wenn Sie Visual C++ 2013 verwenden, erhalten Sie für beide Arten denselben Assemblycode.

gcc4.1 von 2007 optimiert auch die pure C-Version sehr gut. (Auf dem Godbolt-Compiler-Explorer sind keine früheren Versionen von gcc installiert. Vermutlich können jedoch auch ältere GCC-Versionen dies ohne Eigenheiten tun.)

Siehe source + asm für x86 (32-Bit) und ARM on Godbolt-Compiler-Explorer . (Leider sind keine Compiler alt genug, um schlechten Code zu erzeugen aus der einfachen reinen C-Version.)


Moderne CPUs können Dinge tun, für die C keine Operatoren hat überhaupt, wie popcnt oder Bit-Scan, um das Erste oder Letzte zu finden Bit setzen . (POSIX hat eine ffs() -Funktion, aber die Semantik stimmt nicht mit x86 bsf/bsr überein. Siehe https://en.wikipedia.org/ wiki/Find_first_set ).

Einige Compiler können manchmal eine Schleife erkennen, die die Anzahl der gesetzten Bits in einer Ganzzahl zählt, und diese zu einer popcnt -Anweisung kompilieren (falls zur Kompilierungszeit aktiviert). Es ist jedoch viel zuverlässiger, __builtin_popcnt Zu verwenden. in GNU C oder auf x86, wenn Sie nur auf Hardware mit SSE4.2 abzielen: _mm_popcnt_u32 von <immintrin.h> .

Oder weisen Sie in C++ einen std::bitset<32> Zu und verwenden Sie .count(). (Dies ist ein Fall, in dem die Sprache eine Möglichkeit gefunden hat, eine optimierte Implementierung von popcount portabel über die Standardbibliothek verfügbar zu machen, sodass immer eine korrekte Kompilierung möglich ist und alle vom Ziel unterstützten Funktionen verwendet werden können.) Siehe auch - https://en.wikipedia.org/wiki/Hamming_weight#Language_support .

In ähnlicher Weise kann ntohl bei einigen C-Implementierungen, bei denen dies der Fall ist, zu bswap kompiliert werden (x86-32-Bit-Byte-Austausch für die Endian-Konvertierung).


Ein weiteres Hauptgebiet für intrinsische oder handgeschriebene asm ist die manuelle Vektorisierung mit SIMD-Anweisungen. Compiler sind nicht schlecht mit einfachen Schleifen wie dst[i] += src[i] * 10.0;, Aber oft schlecht oder gar nicht automatisch vektorisieren, wenn die Dinge komplizierter werden. Zum Beispiel ist es unwahrscheinlich, dass Sie etwas wie Wie implementiert man atoi mit SIMD? erhalten, das vom Compiler automatisch aus skalarem Code generiert wird.

259

Vor vielen Jahren brachte ich jemandem bei, C zu programmieren. Die Übung bestand darin, eine Grafik um 90 Grad zu drehen. Er kam mit einer Lösung zurück, deren Fertigstellung einige Minuten in Anspruch nahm, hauptsächlich, weil er Multiplikationen und Divisionen usw. verwendete.

Ich zeigte ihm, wie er das Problem mithilfe von Bitverschiebungen neu formulieren kann, und auf dem nicht optimierten Compiler, den er hatte, betrug die Verarbeitungszeit etwa 30 Sekunden.

Ich hatte gerade einen optimierenden Compiler und derselbe Code drehte die Grafik in <5 Sekunden. Ich schaute auf den Assembler-Code, den der Compiler generierte, und nach dem, was ich dort sah, entschied ich, dass meine Tage, als ich Assembler schrieb, vorbei waren.

134
lilburne

Fast immer, wenn der Compiler Gleitkommacode sieht, wird eine handgeschriebene Version schneller sein. Der Hauptgrund ist, dass der Compiler keine robusten Optimierungen durchführen kann. Siehe diesen Artikel von MSDN für eine Diskussion zu diesem Thema. Hier ist ein Beispiel, in dem die Assembly-Version doppelt so schnell ist wie die C-Version (kompiliert mit VS2K5):

#include "stdafx.h"
#include <windows.h>

float KahanSum
(
  const float *data,
  int n
)
{
   float
     sum = 0.0f,
     C = 0.0f,
     Y,
     T;

   for (int i = 0 ; i < n ; ++i)
   {
      Y = *data++ - C;
      T = sum + Y;
      C = T - sum - Y;
      sum = T;
   }

   return sum;
}

float AsmSum
(
  const float *data,
  int n
)
{
  float
    result = 0.0f;

  _asm
  {
    mov esi,data
    mov ecx,n
    fldz
    fldz
l1:
    fsubr [esi]
    add esi,4
    fld st(0)
    fadd st(0),st(2)
    fld st(0)
    fsub st(0),st(3)
    fsub st(0),st(2)
    fstp st(2)
    fstp st(2)
    loop l1
    fstp result
    fstp result
  }

  return result;
}

int main (int, char **)
{
  int
    count = 1000000;

  float
    *source = new float [count];

  for (int i = 0 ; i < count ; ++i)
  {
    source [i] = static_cast <float> (Rand ()) / static_cast <float> (Rand_MAX);
  }

  LARGE_INTEGER
    start,
    mid,
    end;

  float
    sum1 = 0.0f,
    sum2 = 0.0f;

  QueryPerformanceCounter (&start);

  sum1 = KahanSum (source, count);

  QueryPerformanceCounter (&mid);

  sum2 = AsmSum (source, count);

  QueryPerformanceCounter (&end);

  cout << "  C code: " << sum1 << " in " << (mid.QuadPart - start.QuadPart) << endl;
  cout << "asm code: " << sum2 << " in " << (end.QuadPart - mid.QuadPart) << endl;

  return 0;
}

Und einige Nummern von meinem PC, auf denen ein Standard-Release-Build ausgeführt wird*:

  C code: 500137 in 103884668
asm code: 500137 in 52129147

Aus Interesse habe ich den Loop mit einem Dez/Jnz vertauscht und es machte keinen Unterschied zu den Timings - manchmal schneller, manchmal langsamer. Ich denke, der speicherbegrenzte Aspekt zwingt andere Optimierungen auf.

Hoppla, ich habe eine etwas andere Version des Codes ausgeführt und die Zahlen falsch herum ausgegeben (d. H. C war schneller!). Die Ergebnisse wurden korrigiert und aktualisiert.

62
Skizz

Ohne spezielle Beispiele oder Profiler-Beweise können Sie einen besseren Assembler als den Compiler schreiben, wenn Sie mehr wissen als der Compiler.

Im Allgemeinen weiß ein moderner C-Compiler viel mehr darüber, wie man den fraglichen Code optimiert: Er weiß, wie die Prozessor-Pipeline funktioniert, er kann versuchen, Anweisungen schneller als ein Mensch neu zu ordnen, und so weiter - es ist im Grunde dasselbe wie Ein Computer ist so gut wie oder besser als der beste menschliche Spieler für Brettspiele usw., einfach weil er die Suche im Problemraum schneller als die meisten Menschen machen kann. Obwohl Sie theoretisch in einem bestimmten Fall die Leistung des Computers übertreffen können, können Sie dies mit Sicherheit nicht mit der gleichen Geschwindigkeit tun, was es für mehr als einige Fälle unmöglich macht (dh der Compiler wird Sie mit Sicherheit übertreffen, wenn Sie versuchen zu schreiben mehr als ein paar Routinen in Assembler).

Auf der anderen Seite gibt es Fälle, in denen der Compiler nicht über so viele Informationen verfügt - ich würde sagen, vor allem, wenn er mit verschiedenen Arten externer Hardware arbeitet, von denen der Compiler keine Kenntnis hat. Das Hauptbeispiel sind wahrscheinlich Gerätetreiber, bei denen Assembler in Kombination mit dem genauen Wissen eines Menschen über die betreffende Hardware bessere Ergebnisse erzielen können, als dies ein C-Compiler tun könnte.

Andere haben spezielle Anweisungen erwähnt, von denen der Compiler möglicherweise nur begrenzte oder gar keine Kenntnisse hat, sodass ein Mensch schneller Code schreiben kann.

56
Liedman

In meinem Beruf gibt es drei Gründe, warum ich Assembly kenne und verwende. Der Wichtigkeit nach geordnet:

  1. Debugging - Ich bekomme oft Bibliothekscode mit Fehlern oder unvollständiger Dokumentation. Ich finde heraus, was es tut, indem ich auf der Ebene der Versammlung eintrete. Ich muss das ungefähr einmal in der Woche machen. Ich benutze es auch als Tool zum Debuggen von Problemen, bei denen meine Augen den idiomatischen Fehler in C/C++/C # nicht erkennen. Ein Blick auf die Versammlung führt daran vorbei.

  2. Optimieren - Der Compiler kann ziemlich gut optimieren, aber ich spiele in einem anderen Umfeld als die meisten anderen. Ich schreibe Bildverarbeitungscode, der normalerweise mit folgendem Code beginnt:

    for (int y=0; y < imageHeight; y++) {
        for (int x=0; x < imageWidth; x++) {
           // do something
        }
    }
    

    das "Tun Sie etwas Teil" geschieht typischerweise in der Größenordnung von mehreren Millionen Mal (dh zwischen 3 und 30). Durch das Abschaben von Zyklen in dieser Phase werden die Leistungssteigerungen enorm vergrößert. Normalerweise fange ich dort nicht an - normalerweise schreibe ich zuerst den Code, um zu funktionieren, und dann bemühe ich mich, das C so umzugestalten, dass es natürlich besser ist (besserer Algorithmus, weniger Last in der Schleife usw.). Normalerweise muss ich Assembly lesen, um zu sehen, was los ist, und selten muss ich es schreiben. Ich mache das vielleicht alle zwei oder drei Monate.

  3. etwas zu tun, das die Sprache nicht zulässt. Dazu gehören - die Prozessorarchitektur und bestimmte Prozessorfunktionen abrufen, auf Flags zugreifen, die sich nicht in der CPU befinden (Mann, ich wünschte wirklich, C hätte Ihnen Zugriff auf das Carry-Flag) usw. Ich mache das möglicherweise einmal im Jahr oder zwei Jahre.

45
plinth

Nur wenn Sie spezielle Befehlssätze verwenden, unterstützt der Compiler diese nicht.

Um die Rechenleistung einer modernen CPU mit mehreren Pipelines und prädiktiver Verzweigung zu maximieren, müssen Sie das Assembly-Programm so strukturieren, dass es a) für einen Menschen fast unmöglich ist zu schreiben, b) noch schwieriger zu warten.

Bessere Algorithmen, Datenstrukturen und Speicherverwaltung bieten Ihnen mindestens eine Größenordnung mehr Leistung als die in Assembly möglichen Mikrooptimierungen.

41
Nir

Obwohl C der Bearbeitung von 8-Bit-, 16-Bit-, 32-Bit- und 64-Bit-Daten auf niedriger Ebene "nahe" ist, gibt es einige mathematische Operationen, die von C nicht unterstützt werden und die in bestimmten Assembly-Anweisungen häufig elegant ausgeführt werden können setzt:

  1. Festkommamultiplikation: Das Produkt zweier 16-Bit-Zahlen ist eine 32-Bit-Zahl. Die Regeln in C besagen jedoch, dass das Produkt aus zwei 16-Bit-Zahlen eine 16-Bit-Zahl und das Produkt aus zwei 32-Bit-Zahlen eine 32-Bit-Zahl ist - in beiden Fällen die untere Hälfte. Wenn Sie die top Hälfte eines 16x16-Multiplikators oder eines 32x32-Multiplikators wollen, müssen Sie Spiele mit dem Compiler spielen. Die allgemeine Methode besteht darin, auf eine Bitbreite zu konvertieren, zu multiplizieren, nach unten zu verschieben und zurück zu konvertieren:

    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 eines 16x16-Multiplikators zu erhalten und mit dem systemeigenen 16x16-Multiplikator das Richtige zu tun. Oder es kann dumm sein und einen Bibliotheksaufruf erfordern, um die 32x32-Multiplikation durchzuführen, die viel zu viel ist, weil Sie nur 16 Bit des Produkts benötigen - aber der C-Standard gibt Ihnen keine Möglichkeit, sich auszudrücken.

  2. Bestimmte Bitverschiebungsoperationen (Drehen/Übertragen):

    // 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 zu unelegant, aber wenn der Compiler nicht klug genug ist, um zu erkennen, was Sie tun, wird er eine Menge "unnötiger" Arbeit erledigen. Bei vielen Assembler-Anweisungssätzen können Sie das Ergebnis im Carry-Register nach links oder rechts drehen oder verschieben. So können Sie die obigen Anweisungen in 34 Befehlen ausführen: Laden Sie einen Zeiger auf den Anfang des Arrays, löschen Sie den Carry, und führen Sie die folgenden Schritte aus. Bitverschiebung nach rechts mit automatischer Inkrementierung des Zeigers.

    Für ein anderes Beispiel gibt es Schieberegister mit linearer Rückkopplung (LFSR), die elegant in Assembly ausgeführt werden: Nehmen Sie einen Teil von N Bits (8, 16, 32, 64, 128 usw.), verschieben Sie das Ganze Sache direkt um 1 (siehe oben Algorithmus), dann, wenn der resultierende Übertrag 1 ist, dann XOR in einem Bitmuster, das das Polynom darstellt.

Allerdings würde ich nicht auf diese Techniken zurückgreifen, es sei denn, ich hätte ernsthafte Leistungseinschränkungen. Wie bereits erwähnt, ist Assembly viel schwieriger zu dokumentieren, zu debuggen, zu testen und zu warten als C-Code: Der Leistungsgewinn ist mit erheblichen Kosten verbunden.

edit: 3. Überlauferkennung ist in Assembly möglich (kann es in C nicht wirklich tun), dies macht einige Algorithmen viel einfacher.

38
Jason S

Kurze Antwort? Manchmal.

Technisch ist jede Abstraktion mit Kosten verbunden, und eine Programmiersprache ist eine Abstraktion für die Funktionsweise der CPU. C ist jedoch sehr nah. Ich erinnere mich, dass ich vor Jahren laut gelacht habe, als ich mich in mein UNIX-Konto eingeloggt habe und die folgende Glücksmeldung erhalten habe (als solche Dinge populär waren):

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

Es ist lustig, weil es stimmt: C ist wie eine portable Assemblersprache.

Es ist erwähnenswert, dass die Assemblersprache nur so läuft, wie Sie sie schreiben. Es gibt jedoch einen Compiler zwischen C und der von ihm generierten Assemblersprache, und das ist äußerst wichtig, weil wie schnell Ihr C-Code ist, hat sehr viel damit zu tun, wie gut Ihr Compiler ist.

Als gcc auf die Bühne kam, war eines der Dinge, die es so beliebt machten, dass es oft so viel besser war als die C-Compiler, die mit vielen kommerziellen UNIX-Varianten ausgeliefert wurden. Es war nicht nur ANSI C (kein Müll von K & R C), sondern auch robuster und produzierte in der Regel besseren (schnelleren) Code. Nicht immer aber oft.

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

Ebenso variiert Assembler stark, je nachdem, welchen Prozessor Sie ausführen, welche Systemspezifikation Sie verwenden, welche Anweisungsgruppe Sie verwenden usw. In der Vergangenheit gab es zwei CPU-Architekturfamilien: CISC und RISC. Der größte Player in CISC war und ist die Intel x86-Architektur (und der Befehlssatz). RISC dominierte die UNIX-Welt (MIPS6000, Alpha, Sparc usw.). CISC hat den Kampf um Herz und Verstand gewonnen.

Als ich ein jüngerer Entwickler war, war die gängige Meinung, dass handgeschriebenes x86 oft viel schneller als C sein kann, da die Architektur so funktionierte, dass die Komplexität davon profitierte, dass ein Mensch sie ausführte. Auf der anderen Seite schien RISC für Compiler gedacht zu sein, so dass niemand (ich wusste) etwas über Sparc Assembler schrieb. Ich bin mir sicher, dass es solche Leute gab, aber zweifellos sind sie beide verrückt geworden und inzwischen institutionalisiert.

Befehlssätze sind auch in derselben Prozessorfamilie ein wichtiger Punkt. Bestimmte Intel-Prozessoren haben Erweiterungen wie SSE bis SSE4. AMD hatte ihre eigenen SIMD-Anweisungen. Der Vorteil einer Programmiersprache wie C war, dass jemand seine Bibliothek schreiben konnte, sodass sie für den jeweiligen Prozessor optimiert wurde Das war harte Arbeit in Assembler.

Es gibt noch Optimierungen in Assembler, die kein Compiler vornehmen kann, und ein gut geschriebener Assembler-Algorithmus ist genauso schnell oder schneller als sein C-Äquivalent. Die größere Frage ist: Lohnt es sich?

Letztendlich war Assembler ein Produkt seiner Zeit und zu einer Zeit, in der CPU-Zyklen teuer waren, populärer. Heutzutage kann eine CPU, deren Herstellung 5 bis 10 US-Dollar kostet (Intel Atom) so ziemlich alles, was man sich nur wünschen kann. Der einzige wirkliche Grund, um Assembler zu schreiben, ist heutzutage, dass 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 dort tendenziell dominiert) sind auch) und so weiter. Oder einfach nur zum Kicken (was etwas masochistisch ist).

23
cletus

Punkt eins, der nicht die Antwort ist.
Auch wenn Sie nie programmieren, finde ich es nützlich, mindestens einen Assembler-Befehlssatz zu kennen. Dies ist Teil der unendlichen Suche der Programmierer, mehr zu wissen und deshalb besser zu werden. Auch nützlich beim Betreten von Frameworks, wenn Sie nicht über den Quellcode verfügen und zumindest eine ungefähre Vorstellung davon haben, was gerade vor sich geht. Es hilft Ihnen auch, JavaByteCode und .Net IL zu verstehen, da beide Assembler ähnlich sind.

Beantwortung der Frage, wenn Sie wenig Code oder viel Zeit haben. Am nützlichsten für den Einsatz in eingebetteten Chips, bei denen eine geringe Komplexität der Chips und eine schlechte Konkurrenz bei Compilern, die diese Chips einsetzen, die Waage zugunsten des Menschen halten können. Auch bei eingeschränkten Geräten wird häufig die Codegröße/Speichergröße/Leistung in einer Weise abgewogen, die für einen Compiler schwer zu bewerkstelligen ist. z.B. Ich weiß, dass diese Benutzeraktion nicht oft aufgerufen wird, daher habe ich eine kleine Codegröße und eine schlechte Leistung, aber diese andere Funktion, die ähnlich aussieht, wird jede Sekunde verwendet, damit ich eine größere Codegröße und eine schnellere Leistung habe. Dies ist der Kompromiss, den ein erfahrener Assembly-Programmierer eingehen kann.

Ich möchte auch hinzufügen, dass es eine Menge Mittelwege gibt, in denen Sie in C kompilieren und die produzierte Assembly untersuchen können, dann entweder Ihren C-Code ändern oder optimieren und als Assembly pflegen.

Mein Freund arbeitet an Mikrocontrollern, derzeit Chips zur Steuerung kleiner Elektromotoren. Er arbeitet in einer Kombination von 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 hatte. Er steht auch vor der Wahl, dass der Code den 256-KB-Chip ausfüllt und das Unternehmen eine neue Funktion wünscht, oder?

  1. Entfernen Sie ein vorhandenes Feature
  2. Reduzieren Sie die Größe einiger oder aller vorhandenen Features, möglicherweise auf Kosten der Leistung.
  3. Befürworten Sie die Umstellung auf einen größeren Chip mit höheren Kosten, höherem Stromverbrauch und größerem Formfaktor.

Ich möchte als kommerzieller Entwickler eine Reihe von Sprachen, Plattformen und Anwendungstypen hinzufügen, für die ich noch nie das Bedürfnis hatte, Assembly zu schreiben. Ich habe wie immer das Wissen geschätzt, das ich darüber gewonnen habe. Und manchmal darin debuggt.

Ich weiß, dass ich die Frage "Warum sollte ich Assembler lernen?" Weitaus häufiger beantwortet habe, aber ich denke, dass es eine wichtigere Frage ist, als wenn es schneller ist.

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

  • arbeiten mit Betriebssystemfunktionen auf niedriger Ebene
  • Auf einem Compiler arbeiten.
  • Arbeiten an einem extrem limitierten Chip, Embedded System etc

Denken Sie daran, Ihre Assembly mit dem generierten Compiler zu vergleichen, um festzustellen, welche schneller/kleiner/besser ist.

David.

15
David Waters

Ein Anwendungsfall, der nicht mehr auf Sie zutrifft: Auf dem Amiga würden die CPU und die Grafik-/Audiochips um den Zugriff auf einen bestimmten Bereich von RAM (die ersten 2 MB von =) kämpfen RAM um genau zu sein). Wenn Sie also nur 2 MB RAM (oder weniger) hatten, würde das Anzeigen komplexer Grafiken und das Abspielen von Sound die Leistung der CPU beeinträchtigen.

In Assembler können Sie Ihren Code so geschickt verschachteln, dass die CPU nur dann versucht, auf RAM) zuzugreifen, wenn die Grafik-/Audiochips intern ausgelastet waren (dh wenn der Bus frei war). Wenn Sie also Ihre Anweisungen neu ordnen, den CPU-Cache clever nutzen, das Bus-Timing festlegen und einige Effekte erzielen, die mit einer höheren Sprache einfach nicht möglich waren, weil Sie jeden Befehl zeitlich festlegen und sogar hier und da NOPs einfügen mussten, um die verschiedenen zu behalten Chips aus dem jeweils anderen Radar.

Dies ist ein weiterer Grund, warum die NOP-Anweisung (No Operation - do nothing) der CPU dazu führen kann, dass Ihre gesamte Anwendung schneller ausgeführt wird.

[EDIT] Natürlich hängt die Technik von einem bestimmten Hardware-Setup ab. Was der Hauptgrund war, warum viele Amiga-Spiele mit schnelleren CPUs nicht zurechtkommen: Das Timing der Anweisungen war falsch.

15
Aaron Digulla

Matrixoperationen unter Verwendung von SIMD-Anweisungen sind wahrscheinlich schneller als vom Compiler generierter Code.

14
Mehrdad Afshari

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

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

in der Montage können Sie es erheblich beschleunigen:

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. Dies vergleicht 4 Zeichen gleichzeitig und ist somit 4-mal schneller. Und denken Sie mit dem höherwertigen Wort von eax und ebx, es wird 8-mal schneller dass die vorherige C-Routine!

14
BlackBear

Ich kann die konkreten Beispiele nicht nennen, weil es zu viele Jahre her ist, aber es gab viele Fälle, in denen handgeschriebene Assembler jeden Compiler übertreffen konnten. Gründe warum:

  • Sie können von Aufrufkonventionen abweichen und Argumente in Registern übergeben.

  • Sie könnten sorgfältig überlegen, wie Sie Register verwenden und vermeiden, Variablen im Speicher zu speichern.

  • Bei Dingen wie Sprungtabellen könnten Sie vermeiden, den Index anhand von Grenzen überprüfen zu müssen.

Grundsätzlich optimieren Compiler ziemlich gut, und das ist fast immer "gut genug", aber in einigen Situationen (wie dem Rendern von Grafiken), in denen Sie für jeden einzelnen Zyklus teuer bezahlen, können Sie Verknüpfungen verwenden, weil Sie den Code kennen , wo ein Compiler nicht konnte, weil er auf der sicheren Seite sein muss.

Tatsächlich habe ich von einigen Grafik-Rendering-Codes gehört, bei denen eine Routine wie eine Linien- oder Polygonfüllroutine tatsächlich einen kleinen Block Maschinencode auf dem Stapel generiert und dort ausgeführt hat, um eine kontinuierliche Entscheidungsfindung zu vermeiden über Linienstil, Breite, Muster usw.

Trotzdem möchte ich, dass ein Compiler guten Assembly-Code für mich generiert, aber nicht zu clever ist, und das tun sie meistens. Tatsächlich ist eines der Dinge, die ich an Fortran hasse, das Verwürfeln des Codes, um ihn zu "optimieren", normalerweise zu keinem nennenswerten Zweck.

Wenn Apps Leistungsprobleme haben, liegt dies normalerweise an verschwenderischem Design. Heutzutage würde ich Assembler niemals für die Leistung empfehlen, es sei denn, die gesamte App war bereits in einem Zentimeter ihres Lebens optimiert, war noch nicht schnell genug und verbrachte ihre Zeit in engen inneren Schleifen.

Hinzugefügt: Ich habe viele Apps gesehen, die in Assembler geschrieben sind, und der Hauptvorteil in Bezug auf die Geschwindigkeit gegenüber einer Sprache wie C, Pascal, Fortran usw. war, dass der Programmierer beim Codieren in Assembler weitaus vorsichtiger war. Er oder sie schreibt ungefähr 100 Codezeilen pro Tag, unabhängig von der Sprache, und in einer Compilersprache, die 3 oder 400 Anweisungen entspricht.

13
Mike Dunlavey

Einige Beispiele aus meiner Erfahrung:

  • Zugriff auf Anweisungen, auf die von C aus nicht zugegriffen werden kann. Beispielsweise unterstützen viele Architekturen (wie x86-64, IA-64, DEC Alpha und 64-Bit-MIPS oder PowerPC) eine 64-Bit-zu-64-Bit-Multiplikation, die ein 128-Bit-Ergebnis ergibt. GCC hat kürzlich eine Erweiterung hinzugefügt, die den Zugriff auf solche Anweisungen ermöglicht, aber bevor diese Versammlung erforderlich war. Und der Zugriff auf diese Anweisung kann bei 64-Bit-CPUs einen großen Unterschied bei der Implementierung von RSA bewirken - manchmal sogar die Verbesserung der Leistung um den Faktor 4.

  • Zugriff auf CPU-spezifische Flags. Derjenige, der mich sehr gebissen hat, ist die Tragefahne; Wenn Sie bei einer Addition mit Mehrfachgenauigkeit keinen Zugriff auf das CPU-Übertragsbit haben, müssen Sie stattdessen das Ergebnis vergleichen, um festzustellen, ob es übergelaufen ist, was 3 bis 5 weitere Anweisungen pro Glied erfordert. und noch schlimmer, da es sich um serielle Datenzugriffe handelt, die die Leistung moderner superskalarer Prozessoren beeinträchtigen. Wenn Sie Tausende solcher Ganzzahlen hintereinander verarbeiten, ist die Verwendung von addc ein riesiger Gewinn (es gibt superskalare Probleme mit Konflikten auch auf dem Übertragsbit, aber moderne CPUs kommen ziemlich gut damit zurecht).

  • SIMD. Selbst Autovector-Compiler können nur relativ einfache Fälle ausführen. Wenn Sie also eine gute SIMD-Leistung wünschen, ist es leider häufig erforderlich, den Code direkt zu schreiben. Natürlich können Sie Intrinsics anstelle von Assembly verwenden, aber sobald Sie die Intrinsics-Ebene erreicht haben, schreiben Sie im Grunde genommen Assembly, indem Sie den Compiler lediglich als Registerzuweiser und (nominal) Anweisungsplaner verwenden. (Ich neige dazu, Intrinsics für SIMD zu verwenden, nur weil der Compiler die Funktionsprologe generieren kann und so weiter, damit ich unter Linux, OS X und Windows denselben Code verwenden kann, ohne mich mit ABI-Problemen wie Funktionsaufrufkonventionen befassen zu müssen, aber mit anderen als das sind die SSE intrinsics wirklich nicht sehr nett - die Altivec scheinen besser zu sein, obwohl ich nicht viel Erfahrung mit ihnen habe.) Als Beispiele für Dinge, die ein (aktueller Tag) vektorisierender Compiler kann nicht herausfinden, lesen Sie über Bitslicing AES oder SIMD-Fehlerkorrektur - Man könnte sich einen Compiler vorstellen, der Algorithmen analysieren und solchen Code generieren könnte, aber für mich ist ein solcher intelligenter Compiler (bestenfalls) mindestens 30 Jahre von seiner Existenz entfernt.

Auf der anderen Seite haben Multicore-Maschinen und verteilte Systeme viele der größten Leistungsgewinne in die andere Richtung verschoben - Sie erzielen eine zusätzliche Beschleunigung von 20% beim Schreiben Ihrer inneren Schleifen in Assembly oder von 300% beim Ausführen über mehrere Kerne hinweg oder von 10000% durch Laufen sie über einen Cluster von Maschinen. Und natürlich sind Optimierungen auf hoher Ebene (Dinge wie Futures, Memoization usw.) in einer höheren Sprache wie ML oder Scala als C oder asm) oft viel einfacher und bieten oft viel Es gibt also, wie immer, Kompromisse zu schließen.

11
Jack Lloyd

Enge Schleifen, wie beim Spielen mit Bildern, da ein Bild möglicherweise mehrere Millionen Pixel enthält. Sich hinzusetzen und herauszufinden, wie die begrenzte Anzahl von Prozessorregistern optimal genutzt werden kann, kann einen Unterschied bewirken. Hier ist ein Beispiel aus dem wirklichen Leben:

http://danbystrom.se/2008/12/22/optimizing-away-ii/

Dann haben Prozessoren oft einige esoterische Anweisungen, die für einen Compiler zu spezialisiert sind, um sich damit zu beschäftigen, aber gelegentlich kann ein Assembler-Programmierer sie gut gebrauchen. Nehmen Sie zum Beispiel die XLAT-Anweisung. Wirklich großartig, wenn Sie Tabellensuchen in einer Schleife durchführen müssen und die Tabelle auf 256 Byte begrenzt ist!

Aktualisiert: Oh, denken Sie mal darüber nach, was im Allgemeinen am wichtigsten ist, wenn wir von Schleifen sprechen: Der Compiler hat oft keine Ahnung, wie viele Iterationen der häufigste Fall sein werden! Nur der Programmierer weiß, dass eine Schleife VIELE Male iteriert wird und dass es daher von Vorteil ist, die Schleife mit etwas zusätzlicher Arbeit vorzubereiten, oder wenn sie so wenige Male iteriert wird, dass die Einrichtung tatsächlich länger dauert als die Iterationen erwartet.

10
Dan Byström

Häufiger als Sie denken, muss C Dinge tun, die aus der Sicht eines Assembly-Codierers unnötig erscheinen, nur weil dies in den C-Standards festgelegt ist.

Zum Beispiel ganzzahlige Promotion. Wenn Sie eine char-Variable in C verschieben möchten, würde man normalerweise erwarten, dass der Code genau das tut, eine einzelne Bitverschiebung.

Die Standards erzwingen jedoch, dass der Compiler vor der Verschiebung ein Vorzeichen für int ausgibt und das Ergebnis anschließend abschneidet, was abhängig von der Architektur des Zielprozessors den Code komplizieren kann.

10
mfro

Sie wissen nicht wirklich, ob Ihr gut geschriebener C-Code wirklich schnell ist, wenn Sie sich nicht die Zerlegung der Compiler-Produkte angesehen haben. Oft sieht man es sich an und sieht, dass "gut geschrieben" subjektiv war.

Es ist also nicht notwendig, in Assembler zu schreiben, um den schnellsten Code aller Zeiten zu erhalten, aber es lohnt sich auf jeden Fall, Assembler aus demselben Grund zu kennen.

9
sharptooth

Ich denke, der allgemeine Fall, wenn Assembler schneller ist, ist, wenn ein intelligenter Assembly-Programmierer die Ausgabe des Compilers betrachtet und sagt, dass dies ein kritischer Pfad für die Leistung ist und ich dies schreiben kann, um effizienter zu sein von Grund auf neu.

8
Doug T.

Es hängt alles von Ihrer Arbeitsbelastung ab.

Für alltägliche Operationen sind C und C++ in Ordnung, aber es gibt bestimmte Workloads (alle Transformationen, die Video beinhalten (Komprimierung, Dekomprimierung, Bildeffekte usw.)), für die die Leistung von Assemblys ziemlich hoch sein muss.

Sie beinhalten normalerweise auch die Verwendung von CPU-spezifischen Chipsatz-Erweiterungen (MME/MMX/SSE/was auch immer), die für diese Art von Betrieb optimiert sind.

7
Larry Osterman

Ich habe eine Operation der Transposition von Bits, die auf 192 oder 256 Bits jeder Unterbrechung durchgeführt werden muss, die alle 50 Mikrosekunden geschieht.

Dies geschieht durch eine feste Zuordnung (Hardwareeinschränkungen). Bei Verwendung von C dauerte die Herstellung etwa 10 Mikrosekunden. Als ich dies in Assembler übersetzte, berücksichtigte ich die spezifischen Merkmale dieser Map, bestimmte Register-Caching-Vorgänge und benutzte bitorientierte Operationen. Die Leistung dauerte weniger als 3,5 Mikrosekunden.

6
SurDin

Ich habe alle Antworten gelesen (mehr als 30) und keinen einfachen Grund gefunden: Assembler ist schneller als C, wenn Sie das Intel® 64- und IA-32-Referenzhandbuch zur Architekturoptimierung gelesen und geübt haben , der Grund, warum Assembly möglicherweise langsamer ist, besteht darin, dass Personen, die eine solche langsamere Assembly schreiben, das Optimierungshandbuch nicht gelesen haben.

In den guten alten Zeiten von Intel 80286 wurde jeder Befehl mit einer festgelegten Anzahl von CPU-Zyklen ausgeführt. Seit dem 1995 veröffentlichten Pentium Pro wurden Intel-Prozessoren jedoch superskalar und verwendeten Complex Pipelining: Out-of-Order Execution & Register Renaming. Davor gab es auf Pentium, das 1993 produziert wurde, U- und V-Pipelines: Dual-Pipelines, die zwei einfache Befehle in einem Taktzyklus ausführen könnten, wenn sie nicht voneinander abhängig wären; Dies war jedoch nichts Vergleichbares zu dem, was in Pentium Pro unter "Out-of-Order Execution & Register Renaming" erschien und heutzutage fast unverändert blieb.

Kurz gesagt, der schnellste Code ist der, bei dem Anweisungen nicht von vorherigen Ergebnissen abhängen, z. Sie sollten immer ganze Register löschen (von movzx) oder add rax, 1 statt inc rax, um die Abhängigkeit vom vorherigen Status von Flags usw. zu entfernen.

Wenn es die Zeit erlaubt, finden Sie im Internet zahlreiche Informationen zu den Themen "Out-of-Order Execution & Register Renaming".

Es gibt auch andere wichtige Punkte wie die Verzweigungsvorhersage, die Anzahl der Lade- und Speichereinheiten, die Anzahl der Gates, die Mikrooperationen ausführen, usw., aber das Wichtigste, das berücksichtigt werden muss, ist die Ausführung außerhalb der Reihenfolge.

Die meisten Benutzer sind sich der Ausführung von Fehlern einfach nicht bewusst. Daher schreiben sie ihre Assembly-Programme wie für 80286, da sie davon ausgehen, dass die Ausführung ihrer Anweisungen unabhängig vom Kontext eine feste Zeit in Anspruch nimmt. Während C-Compiler die Out-of-Order-Ausführung kennen und den Code korrekt generieren. Das ist der Grund, warum der Code solcher ahnungsloser Personen langsamer ist, aber wenn Sie sich dessen bewusst werden, wird Ihr Code schneller sein.

6
Maxim Masiutin

Ein Blick auf Optimizing Immutable and Purity von Walter Bright ist kein profilierter Test, sondern zeigt ein gutes Beispiel für den Unterschied zwischen handschriftlichem und compilergeneriertem ASM. Walter Bright schreibt, wie er Compiler optimiert, damit es sich lohnt, sich seine anderen Blog-Beiträge anzuschauen.

6
James Brooks

LInux Assembly howto , stellt diese Frage und gibt die Vor- und Nachteile der Verwendung von Assembly.

5
pseudosaint

Die einfache Antwort ... Einer, der weiß Assembly gut (aka hat die Referenz neben sich und nutzt jeden kleinen Prozessor-Cache und jede Pipeline-Funktion usw.) garantiert dazu in der Lage sein, viel schnelleren Code zu produzieren als any Compiler.

In der typischen Anwendung spielt der Unterschied heutzutage jedoch keine Rolle.

gcc ist zu einem weit verbreiteten Compiler geworden. Die Optimierungen sind im Allgemeinen nicht so gut. Weitaus besser als der durchschnittliche Programmierer, der Assembler schreibt, aber für echte Leistung nicht so gut. Es gibt Compiler, deren Code einfach unglaublich ist. Als allgemeine Antwort wird es also viele Stellen geben, an denen Sie die Ausgabe des Compilers aufrufen und den Assembler für die Leistung optimieren und/oder die Routine einfach von Grund auf neu schreiben können.

4
old_timer

Longpoke, es gibt nur eine Einschränkung: die Zeit. Wenn Sie nicht über die Ressourcen verfügen, um jede einzelne Änderung des Codes zu optimieren und Ihre Zeit mit der Zuweisung von Registern zu verbringen, einige Verschüttungen zu optimieren und was nicht, gewinnt der Compiler jedes Mal. Sie ändern den Code, kompilieren und messen ihn neu. Bei Bedarf wiederholen.

Sie können auch viel auf hohem Niveau tun. Wenn Sie die resultierende Assembly untersuchen, kann dies den Eindruck erwecken, dass der Code Mist ist. In der Praxis wird er jedoch schneller ausgeführt, als Sie denken, dass er schneller ist. Beispiel:

int y = Daten [i]; // hier ein paar Sachen machen .. call_function (y, ...);

Der Compiler liest die Daten, schiebt sie in den Stack (Spill) und liest sie später aus dem Stack und übergibt sie als Argument. Klingt scheiße? Dies kann eine sehr effektive Latenzkompensation sein und zu einer schnelleren Laufzeit führen.

// optimierte version call_function (data [i], ...); // doch nicht so optimiert ..

Die Idee mit der optimierten Version war, dass wir den Registerdruck reduziert und das Verschütten vermieden haben. Aber in Wahrheit war die "beschissene" Version schneller!

Ein Blick auf den Assembly-Code, nur auf die Anweisungen und die Schlussfolgerung: Mehr Anweisungen, langsamer, wären eine Fehleinschätzung.

Die Sache, die hier beachtet werden muss, ist: Viele Versammlungsexperten denken sie wissen viel, wissen aber sehr wenig. Die Regeln ändern sich auch von Architektur zu Architektur. Es gibt zum Beispiel keinen Silver-Bullet-x86-Code, der immer der schnellste ist. In diesen Tagen ist es besser, sich an die Faustregeln zu halten:

  • gedächtnis ist langsam
  • cache ist schnell
  • versuchen Sie, zwischengespeicherte besser zu verwenden
  • wie oft wirst du vermissen? Haben Sie eine Strategie zur Latenzkompensation?
  • sie können 10-100 ALU/FPU/SSE-Anweisungen für einen einzelnen Cache-Fehler ausführen
  • anwendungsarchitektur ist wichtig ..
  • .. aber es hilft nicht, wenn das Problem nicht in der Architektur liegt

Zu viel Vertrauen in den Compiler zu haben, um schlecht durchdachten C/C++ - Code auf magische Weise in "theoretisch optimalen" Code umzuwandeln, ist ein Wunschdenken. Sie müssen den Compiler und die Toolkette kennen, die Sie verwenden, wenn Sie Wert auf "Leistung" auf dieser niedrigen Ebene legen.

Compiler in C/C++ sind im Allgemeinen nicht sehr gut darin, Unterausdrücke neu anzuordnen, da die Funktionen für den Anfang Nebenwirkungen haben. Funktionale Sprachen leiden nicht unter dieser Einschränkung, passen aber nicht so gut in das aktuelle Ökosystem. Es gibt Compiler-Optionen, die entspannte Präzisionsregeln ermöglichen, mit denen die Reihenfolge der Operationen vom Compiler/Linker/Codegenerator geändert werden kann.

Dieses Thema ist eine Sackgasse. für die meisten ist es nicht relevant, und die anderen wissen sowieso schon, was sie tun.

Alles läuft darauf hinaus, "zu verstehen, was Sie tun", es ist ein bisschen anders als zu wissen, was Sie tun.

4
tiredcoder

Wie wäre es mit Erstellen von Maschinencode zur Laufzeit?

Mein Bruder realisierte einmal (um 2000) einen extrem schnellen Ray-Tracer in Echtzeit, indem er zur Laufzeit Code generierte. Ich kann mich nicht an die Details erinnern, aber es gab eine Art Hauptmodul, das Objekte durchlief, dann wurde ein für jedes Objekt spezifischer Maschinencode vorbereitet und ausgeführt.

Mit der Zeit wurde diese Methode jedoch von neuer Grafikhardware außer Kraft gesetzt und nutzlos.

Heute denke ich, dass möglicherweise einige Operationen mit Big Data (Millionen von Datensätzen) wie Pivot-Tabellen, Bohren, Berechnungen im laufenden Betrieb usw. mit dieser Methode optimiert werden könnten. Die Frage ist: lohnt sich der Aufwand?

4
user872744

Einer der bekanntesten Ausschnitte von Assembly stammt aus der Textur-Mapping-Schleife von Michael Abrash ( hier im Detail beschrieben ):

add edx,[DeltaVFrac] ; add in dVFrac
sbb ebp,ebp ; store carry
mov [edi],al ; write pixel n
mov al,[esi] ; fetch pixel n+1
add ecx,ebx ; add in dUFrac
adc esi,[4*ebp + UVStepVCarry]; add in steps

Heutzutage drücken die meisten Compiler erweiterte CPU-spezifische Anweisungen als intrinsische Anweisungen aus, d. H. Funktionen, die bis zur tatsächlichen Anweisung kompiliert werden. MS Visual C++ unterstützt systeminterne Funktionen für MMX, SSE, SSE2, SSE3 und SSE4, sodass Sie sich weniger Gedanken darüber machen müssen, ob Sie zu Assembly wechseln müssen, um plattformspezifische Anweisungen nutzen zu können. Visual C++ kann auch die tatsächliche Architektur nutzen, auf die Sie mit der entsprechenden/Arch-Einstellung abzielen.

4
MSN

Mit dem richtigen Programmierer können Assembler-Programme immer schneller als ihre C-Gegenstücke erstellt werden (zumindest geringfügig). Es ist schwierig, ein C-Programm zu erstellen, in dem Sie nicht mindestens eine Anweisung des Assemblers ausführen können.

4
Beep beep

Eine der Möglichkeiten der CP/M-86-Version von PolyPascal (gleichrangig mit Turbo Pascal) bestand darin, die Funktion "BIOS für die Ausgabe von Zeichen auf dem Bildschirm" durch eine Maschinensprachenroutine zu ersetzen, die im Wesentlichen funktioniert wurde die x und y und die Zeichenfolge gegeben, um dort zu setzen.

Dies ermöglichte es, den Bildschirm viel, viel schneller als zuvor zu aktualisieren!

In der Binärdatei war Platz zum Einbetten von Maschinencode (ein paar Hundert Bytes) und es gab auch andere Dinge, daher war es wichtig, so viel wie möglich zu komprimieren.

Es stellte sich heraus, dass beide Koordinaten, da der Bildschirm 80 x 25 groß war, jeweils in ein Byte passen konnten, sodass beide in ein Zwei-Byte-Wort passen konnten. Dies ermöglichte es, die erforderlichen Berechnungen in weniger Bytes durchzuführen, da eine einzige Addition beide Werte gleichzeitig manipulieren konnte.

Meines Wissens gibt es keine C-Compiler, die mehrere Werte in einem Register zusammenführen, SIMD-Anweisungen auf sie anwenden und sie später wieder aufteilen können (und ich denke, die Maschinenanweisungen werden sowieso nicht kürzer sein).

http://cr.yp.to/qhasm.html hat viele Beispiele.

4
Vincent

Die Frage ist etwas irreführend. Die Antwort finden Sie in Ihrem Beitrag. Es ist immer möglich, eine Assembly-Lösung für ein bestimmtes Problem zu schreiben, die schneller ausgeführt wird als alle von einem Compiler generierten. Sie müssen ein Assembler-Experte sein, um die Einschränkungen eines Compilers zu überwinden. Ein erfahrener Assembly-Programmierer kann Programme in jeder HLL schreiben, die schneller abläuft als ein von einem Unerfahrenen geschriebenes. Die Wahrheit ist, dass Sie immer Assembly-Programme schreiben können, die schneller ausgeführt werden als die von einem Compiler generierten.

4
Arun Aravind

Dies ist besonders schwer zu beantworten, da die Frage sehr unspezifisch ist: Was genau ist ein "moderner Compiler"?

Praktisch jede manuelle Assembler-Optimierung könnte theoretisch auch von einem Compiler durchgeführt werden. Ob tatsächlich durchgeführt wird, kann nicht allgemein gesagt werden, sondern nur über ein bestimmtes Version eines bestimmten Compilers. Viele erfordern wahrscheinlich so viel Aufwand, um festzustellen, ob sie in einem bestimmten Kontext ohne Nebenwirkungen angewendet werden können, dass Compiler-Autoren sich nicht mit ihnen beschäftigen.

1

In Tagen, in denen die Prozessorgeschwindigkeit in MHz gemessen wurde und die Bildschirmgröße unter 1 Megapixel lag, bestand ein bekannter Trick für eine schnellere Anzeige darin, Schleifen zu entfernen: Schreibvorgang für jede Abtastzeile des Bildschirms. Es wurde der Aufwand für die Verwaltung eines Schleifenindex vermieden! In Verbindung mit der Erkennung von Bildschirmaktualisierungen war dies sehr effektiv.
Das ist etwas, was ein C-Compiler nicht tun würde ... (obwohl Sie oft zwischen einer Optimierung der Geschwindigkeit oder der Größe wählen können, verwendet der erstere vermutlich einige ähnliche Tricks.)

Ich weiß, dass einige Leute gerne Windows-Anwendungen in Assembler schreiben. Sie behaupten, sie seien schneller (schwer zu beweisen) und kleiner (in der Tat!).
Obwohl es Spaß macht, es zu tun, ist es offensichtlich Zeitverschwendung (außer natürlich zu Lernzwecken!), Insbesondere für GUI-Operationen ... Nun, vielleicht einige Operationen, wie das Durchsuchen einer Zeichenfolge in einer Datei , kann durch sorgfältig geschriebenen Assembler-Code optimiert werden.

1
PhiLho

Tatsächlich können Sie große Programme in einem großen Modellmodus erstellen. Segmente können auf 64-KB-Code beschränkt sein, aber Sie können viele Segmente schreiben. Die Leute sprechen sich gegen ASM aus, da es eine alte Sprache ist und wir keinen Speicher mehr benötigen In diesem Fall würden wir unsere PCs mit Speicher ausstatten. Der einzige Fehler, den ich bei ASM feststellen kann, ist, dass es mehr oder weniger prozessorbasiert ist, sodass die meisten Programme, die für die Intel-Architektur geschrieben wurden, wahrscheinlich nicht auf einer AMD-Architektur ausgeführt werden. Da C schneller ist als ASM, gibt es keine Sprache, die schneller ist als ASM, und ASM kann vieles, was C und andere HLLs auf Prozessorebene nicht können. ASM ist eine schwer zu erlernende Sprache, aber wenn Sie sie erst einmal gelernt haben, kann kein HLL sie besser übersetzen als Sie. Wenn Sie nur einige der Dinge sehen könnten, die HLL mit Ihrem Code macht, und verstehen würden, was HLL tut, würden Sie sich fragen, warum immer mehr Leute ASM nicht verwenden und warum Assembers nicht mehr aktualisiert werden (für die allgemeine öffentliche Verwendung sowieso). Kein C ist also nicht schneller als ASM. Selbst erfahrene C++ - Programmierer verwenden und schreiben immer noch Code-Chunks in ASM, die aus Gründen der Geschwindigkeit dem dortigen C++ - Code hinzugefügt wurden. Andere Sprachen Auch, dass manche Leute denken, obsolet oder möglicherweise nicht gut, ist manchmal ein Mythos. Photoshop ist in Pascal/ASM geschrieben. Die erste Version von souce wurde an das Museum für technische Geschichte übergeben, und Paintshop Pro ist immer noch in Python geschrieben. TCL und ASM ... ein gemeinsamer Nenner für "Schnelle und großartige Bildprozessoren" ist ASM, obwohl Photoshop möglicherweise ein Upgrade auf Delphi durchgeführt hat, ist es immer noch Pascal. Alle Geschwindigkeitsprobleme kommen von Pascal, aber das liegt daran, dass wir den Weg mögen Programme sehen aus und nicht mehr so, wie sie es heutzutage tun. Ich möchte einen Photoshop-Klon in reinem ASM erstellen, an dem ich gearbeitet habe und der recht gut funktioniert. Nicht Code, Interpretieren, Anordnen, Umschreiben usw. ... Nur Code und gehen Sie Prozess abgeschlossen.

1
Dewayne Gunter

Das würde ich sagen, wenn Sie für einen bestimmten Befehlssatz besser als der Compiler sind. Also keine generische Antwort, denke ich

0
webclimber

Angesichts von Compilern wie Intel C++, die den C-Code extrem optimieren, ist es heutzutage sehr schwierig, mit der Compilerausgabe zu konkurrieren.

0
Dennis Yurichev