168 Stimmen

Optimieren ohne "while(1);" in C++0x

Aktualisiert, siehe unten!

Ich habe gehört und gelesen, dass C++0x einem Compiler erlaubt, "Hello" für den folgenden Ausschnitt zu drucken

#include <iostream>

int main() {
  while(1) 
    ;
  std::cout << "Hello" << std::endl;
}

Es hat offenbar etwas mit Threads und Optimierungsmöglichkeiten zu tun. Es sieht so aus, als ob dies viele Leute überraschen kann.

Hat jemand eine gute Erklärung dafür, warum dies notwendig war, um dies zu erlauben? Zum Vergleich: Der jüngste C++0x-Entwurf sagt unter 6.5/5

Eine Schleife, die im Falle einer for-Anweisung außerhalb der for-init-Anweisung liegt,

  • ruft keine Bibliotheks-E/A-Funktionen auf, und
  • nicht auf flüchtige Objekte zugreift oder sie verändert, und
  • keine Synchronisationsoperationen (1.10) oder atomare Operationen (Klausel 29) durchführt

kann von der Implementierung als beendet angenommen werden. [Hinweis: Dies soll Compiler-Transformationen ermöglichen. mationen, wie z.B. das Entfernen von Leerschleifen, zu ermöglichen, auch wenn die Terminierung nicht nachgewiesen werden kann. - end note ]

Edita:

Dieser aufschlussreiche Artikel sagt über diesen Standardtext

Leider werden die Worte "undefiniertes Verhalten" nicht verwendet. Immer wenn es in der Norm heißt: "Der Compiler kann P annehmen", wird damit impliziert, dass ein Programm, das die Eigenschaft not-P hat, eine undefinierte Semantik aufweist.

Ist das korrekt, und darf der Compiler "Bye" für das obige Programm ausgeben?


Es gibt eine noch aufschlussreichere Thread hier , in dem es um eine analoge Änderung zu C geht, die von dem Kerl aus dem oben verlinkten Artikel angestoßen wurde. Neben anderen nützlichen Fakten stellen sie eine Lösung vor, die auch für C++0x zu gelten scheint ( Update : Dies wird mit n3225 nicht mehr funktionieren - siehe unten!)

endless:
  goto endless;

Ein Compiler darf das nicht wegoptimieren, weil es sich nicht um eine Schleife, sondern um einen Sprung handelt. Ein anderer Mann fasst die vorgeschlagene Änderung in C++0x und C201X zusammen

Indem der Programmierer eine Schleife schreibt, behauptet er entweder dass die Schleife etwas mit sichtbarem Verhalten tut (E/A durchführt, auf flüchtige Objekte oder führt Synchronisierungs- oder atomare Operationen durch), oder dass sie schließlich beendet wird. Wenn ich diese Annahme verletze indem ich eine Endlosschleife ohne Seiteneffekte schreibe, belüge ich den Compiler an, und das Verhalten meines Programms ist undefiniert. (Wenn ich Glück habe, warnt mich der Compiler vielleicht davor.) Die Sprache bietet keine (nicht mehr?) eine Möglichkeit, eine Endlosschleife ohne sichtbares Verhalten auszudrücken.


Aktualisierung am 3.1.2011 mit n3225: Der Ausschuss hat den Text nach 1.10/24 verschoben und sagt

Bei der Implementierung kann davon ausgegangen werden, dass jeder Thread irgendwann eine der folgenden Aktionen ausführt:

  • terminieren,
  • einen Aufruf an eine Bibliotheks-E/A-Funktion machen,
  • auf ein flüchtiges Objekt zuzugreifen oder es zu verändern, oder
  • eine Synchronisationsoperation oder eine atomare Operation durchführen.

En goto Trick wird no nicht mehr funktionieren!

1voto

supercat Punkte 72939

Ich denke, das Problem könnte vielleicht am besten wie folgt formuliert werden: "Wenn ein späteres Stück Code nicht von einem früheren Stück Code abhängt und das frühere Stück Code keine Nebeneffekte auf einen anderen Teil des Systems hat, kann die Ausgabe des Compilers das spätere Stück Code vor, nach oder vermischt mit der Ausführung des ersteren ausführen, selbst wenn das erstere Schleifen enthält, ohne Rücksicht darauf, wann oder ob der frühere Code tatsächlich abgeschlossen sein würde . Der Compiler könnte zum Beispiel umschreiben:

void testfermat(int n)
{
  int a=1,b=1,c=1;
  while(pow(a,n)+pow(b,n) != pow(c,n))
  {
    if (b > a) a++; else if (c > b) {a=1; b++}; else {a=1; b=1; c++};
  }
  printf("The result is ");
  printf("%d/%d/%d", a,b,c);
}

als

void testfermat(int n)
{
  if (fork\_is\_first\_thread())
  {
    int a=1,b=1,c=1;
    while(pow(a,n)+pow(b,n) != pow(c,n))
    {
      if (b > a) a++; else if (c > b) {a=1; b++}; else {a=1; b=1; c++};
    }
    signal\_other\_thread\_and\_die();
  }
  else // Second thread
  {
    printf("The result is ");
    wait\_for\_other\_thread();
  }
  printf("%d/%d/%d", a,b,c);
}

Im Allgemeinen nicht unvernünftig, obwohl ich mir darüber Sorgen machen könnte:

  int total=0;
  for (i=0; num\_reps > i; i++)
  {
    update\_progress\_bar(i);
    total+=do\_something\_slow\_with\_no\_side\_effects(i);
  }
  show\_result(total);

werden würde

  int total=0;
  if (fork\_is\_first\_thread())
  {
    for (i=0; num\_reps > i; i++)
      total+=do\_something\_slow\_with\_no\_side\_effects(i);
    signal\_other\_thread\_and\_die();
  }
  else
  {
    for (i=0; num\_reps > i; i++)
      update\_progress\_bar(i);
    wait\_for\_other\_thread();
  }
  show\_result(total);

Indem eine CPU die Berechnungen und eine andere die Aktualisierungen des Fortschrittsbalkens übernimmt, würde die Neuschreibung die Effizienz verbessern. Leider würden die Aktualisierungen des Fortschrittsbalkens dadurch weniger nützlich werden, als sie sein sollten.

0voto

Albert Punkte 61113

Ob es sich überhaupt um eine Endlosschleife handelt, ist für den Compiler in nicht-trivialen Fällen nicht entscheidbar.

In verschiedenen Fällen kann es vorkommen, dass Ihr Optimierer eine bessere Komplexitätsklasse für Ihren Code erreicht (z. B. war es O(n^2) und Sie erhalten O(n) oder O(1) nach der Optimierung).

Die Aufnahme einer solchen Regel, die das Entfernen einer Endlosschleife verbietet, in den C++-Standard würde also viele Optimierungen unmöglich machen. Und das wollen die meisten Leute nicht. Ich denke, das beantwortet Ihre Frage ganz gut.


Und noch etwas: Ich habe noch nie ein gültiges Beispiel gesehen, in dem man eine Endlosschleife braucht, die nichts tut.

Das eine Beispiel, von dem ich gehört habe, war ein hässlicher Hack, der eigentlich anders gelöst werden sollte: Es ging um eingebettete Systeme, bei denen die einzige Möglichkeit, einen Reset auszulösen, darin bestand, das Gerät einzufrieren, damit der Watchdog es automatisch neu startet.

Wenn Sie ein gutes Beispiel kennen, in dem Sie eine Endlosschleife brauchen, die nichts tut, sagen Sie es mir bitte.

-1voto

spraff Punkte 30606

Ich denke, es lohnt sich, darauf hinzuweisen, dass Schleifen, die unendlich wären, wenn sie nicht über nicht-flüchtige, nicht-synchronisierte Variablen mit anderen Threads interagieren würden, jetzt mit einem neuen Compiler ein falsches Verhalten zeigen können.

Mit anderen Worten, machen Sie Ihre Globals flüchtig - ebenso wie Argumente, die per Zeiger/Referenz an eine solche Schleife übergeben werden.

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