Optimieren Sie eine "Weile (1)"; in C ++ 0x

153

Aktualisiert, siehe unten!

Ich habe gehört und gelesen, dass C ++ 0x einem Compiler erlaubt, "Hallo" für das folgende Snippet zu drucken

#include <iostream>

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

Es hat anscheinend etwas mit Threads und Optimierungsmöglichkeiten zu tun. Es scheint mir, dass dies viele Menschen überraschen kann.

Hat jemand eine gute Erklärung, warum dies notwendig war, um zuzulassen? Als Referenz heißt es im neuesten C ++ 0x-Entwurf unter6.5/5

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

  • ruft die E / A-Funktionen der Bibliothek nicht auf und
  • greift nicht auf flüchtige Objekte zu oder ändert sie nicht
  • führt keine Synchronisationsoperationen (1.10) oder atomaren Operationen (Abschnitt 29) durch

kann von der Implementierung als beendet angenommen werden. [Hinweis: Dies soll Compiler-Transformationen ermöglichen, z. B. das Entfernen leerer Schleifen, auch wenn die Beendigung nicht nachgewiesen werden kann. - Endnote]

Bearbeiten:

Dieser aufschlussreiche Artikel sagt über diesen Standardtext

Leider werden die Wörter "undefiniertes Verhalten" nicht verwendet. Immer wenn der Standard sagt "der Compiler kann P annehmen", wird impliziert, dass ein Programm mit der Eigenschaft nicht-P eine undefinierte Semantik hat.

Ist das richtig und darf der Compiler "Bye" für das obige Programm drucken?


Es gibt hier einen noch aufschlussreicheren Thread , der sich mit einer analogen Änderung von C befasst, die von dem Kerl gestartet wurde, der den oben verlinkten Artikel geschrieben hat. Neben anderen nützlichen Fakten stellen sie eine Lösung vor, die auch für C ++ 0x zu gelten scheint ( Update : Dies funktioniert mit n3225 nicht mehr - siehe unten!)

endless:
  goto endless;

Ein Compiler darf das anscheinend nicht optimieren, weil es keine Schleife ist, sondern ein Sprung. Ein anderer Mann fasst die vorgeschlagene Änderung in C ++ 0x und C201X zusammen

Durch das Schreiben einer Schleife behauptet der Programmierer entweder, dass die Schleife etwas mit sichtbarem Verhalten tut (E / A ausführt, auf flüchtige Objekte zugreift oder Synchronisations- oder Atomoperationen durchführt) oder dass sie schließlich beendet wird. Wenn ich gegen diese Annahme verstoße, indem ich eine Endlosschleife ohne Nebenwirkungen schreibe, lüge ich den Compiler an und das Verhalten meines Programms ist undefiniert. (Wenn ich Glück habe, warnt mich der Compiler möglicherweise davor.) Die Sprache bietet keine Möglichkeit, eine Endlosschleife ohne sichtbares Verhalten auszudrücken (nicht mehr?).


Update am 3.1.2011 mit n3225: Ausschuss hat den Text auf 1.10 / 24 verschoben und sagen

Die Implementierung kann davon ausgehen, dass ein Thread möglicherweise einen der folgenden Schritte ausführt:

  • beenden,
  • eine Bibliotheks-E / A-Funktion aufrufen,
  • auf ein flüchtiges Objekt zugreifen oder es ändern, oder
  • Führen Sie eine Synchronisationsoperation oder eine atomare Operation durch.

Der gotoTrick wird nicht mehr funktionieren!

Johannes Schaub - litb
quelle
4
while(1) { MyMysteriousFunction(); }muss unabhängig kompilierbar sein, ohne die Definition dieser mysteriösen Funktion zu kennen, oder? Wie können wir also feststellen, ob Bibliotheks-E / A-Funktionen aufgerufen werden? Mit anderen Worten: Sicherlich ruft diese erste Kugel keine Funktion auf .
Daniel Earwicker
19
@ Daniel: Wenn es Zugriff auf die Definition der Funktion hat, kann es viele Dinge beweisen. Es gibt so etwas wie eine interprozedurale Optimierung.
Potatoswatter
3
Gerade jetzt, in C ++ 03, ist ein Compiler Änderung erlaubt int x = 1; for(int i = 0; i < 10; ++i) do_something(&i); x++;in for(int i = 0; i < 10; ++i) do_something(&i); int x = 2;? Oder möglicherweise umgekehrt, xindem es 2vor der Schleife initialisiert wird. Es kann erkennen, dass do_somethinges sich nicht um den Wert von kümmert x, daher ist es eine absolut sichere Optimierung, wenn do_something sich der Wert von nicht iso ändert, dass Sie in eine Endlosschleife geraten.
Dennis Zickefoose
4
Bedeutet dies also, dass das main() { start_daemon_thread(); while(1) { sleep(1000); } }Programm möglicherweise sofort beendet wird, anstatt meinen Daemon in einem Hintergrundthread auszuführen?
Gabe
2
"Dieser aufschlussreiche Artikel" geht davon aus, dass ein bestimmtes Verhalten undefiniertes Verhalten ist, nur weil es kein explizites, definiertes Verhalten gibt. Das ist eine falsche Annahme. Wenn der Standard eine begrenzte Anzahl von Verhaltensweisen offen lässt, muss eine Implementierung im Allgemeinen eines dieser Verhaltensweisen auswählen ( nicht spezifiziert) Verhalten). Dies muss nicht deterministisch sein. Ob eine Nichtstun-Schleife endet, ist wohl eine boolesche Wahl. entweder tut es oder tut es nicht. Etwas anderes zu tun ist nicht erlaubt.
MSalters

Antworten:

33

Hat jemand eine gute Erklärung, warum dies notwendig war, um zuzulassen?

Ja, Hans Boehm liefert in N1528 eine Begründung dafür : Warum undefiniertes Verhalten für Endlosschleifen? Obwohl dies ein WG14-Dokument ist, gilt die Begründung auch für C ++ und das Dokument bezieht sich sowohl auf WG14 als auch auf WG21:

Wie N1509 richtig hervorhebt, gibt der aktuelle Entwurf Endlosschleifen in 6.8.5p6 im Wesentlichen undefiniertes Verhalten. Ein Hauptproblem dabei ist, dass Code sich über eine möglicherweise nicht terminierende Schleife bewegen kann. Angenommen, wir haben die folgenden Schleifen, in denen count und count2 globale Variablen sind (oder deren Adresse übernommen wurde) und p eine lokale Variable ist, deren Adresse nicht übernommen wurde:

for (p = q; p != 0; p = p -> next) {
    ++count;
}
for (p = q; p != 0; p = p -> next) {
    ++count2;
}

Könnten diese beiden Schleifen zusammengeführt und durch die folgende Schleife ersetzt werden?

for (p = q; p != 0; p = p -> next) {
        ++count;
        ++count2;
}

Ohne die spezielle Abgabe in 6.8.5p6 für Endlosschleifen wäre dies nicht zulässig: Wenn die erste Schleife nicht beendet wird, weil q auf eine zirkuläre Liste zeigt, schreibt das Original niemals in count2. Somit könnte es parallel mit einem anderen Thread ausgeführt werden, der auf count2 zugreift oder es aktualisiert. Dies ist mit der transformierten Version, die trotz der Endlosschleife auf count2 zugreift, nicht mehr sicher. Somit führt die Transformation möglicherweise ein Datenrennen ein.

In solchen Fällen ist es sehr unwahrscheinlich, dass ein Compiler die Beendigung einer Schleife nachweisen kann. es müsste verstehen, dass q auf eine azyklische Liste verweist, von der ich glaube, dass sie die Fähigkeiten der meisten Mainstream-Compiler übersteigt und ohne vollständige Programminformationen oft unmöglich ist.

Die durch nicht terminierende Schleifen auferlegten Einschränkungen sind eine Einschränkung für die Optimierung von terminierenden Schleifen, für die der Compiler keine Terminierung nachweisen kann, sowie für die Optimierung von tatsächlich nicht terminierenden Schleifen. Die ersteren sind viel häufiger als die letzteren und oft interessanter zu optimieren.

Es gibt eindeutig auch for-Schleifen mit einer Ganzzahlschleifenvariablen, in denen es für einen Compiler schwierig wäre, die Beendigung zu beweisen, und es für den Compiler daher schwierig wäre, Schleifen ohne 6.8.5p6 umzustrukturieren. Sogar so etwas

for (i = 1; i != 15; i += 2)

oder

for (i = 1; i <= 10; i += j)

scheint nicht trivial zu handhaben. (Im ersteren Fall ist eine grundlegende Zahlentheorie erforderlich, um die Beendigung zu beweisen, im letzteren Fall müssen wir etwas über die möglichen Werte von j wissen, um dies zu tun. Das Umschließen für vorzeichenlose ganze Zahlen kann einige dieser Überlegungen weiter erschweren. )

Dieses Problem scheint für fast alle Schleifenumstrukturierungstransformationen zu gelten, einschließlich Compilerparallelisierung und Cache-Optimierungstransformationen, die wahrscheinlich an Bedeutung gewinnen und für numerischen Code bereits häufig wichtig sind. Dies wird wahrscheinlich zu erheblichen Kosten führen, um Endlosschleifen auf möglichst natürliche Weise schreiben zu können, zumal die meisten von uns selten absichtlich Endlosschleifen schreiben.

Der einzige wesentliche Unterschied zu C besteht darin, dass C11 eine Ausnahme für die Steuerung von Ausdrücken darstellt, bei denen es sich um konstante Ausdrücke handelt, die sich von C ++ unterscheiden und Ihr spezifisches Beispiel in C11 genau definieren.

Shafik Yaghmour
quelle
1
Gibt es sichere und nützliche Optimierungen, die durch die vorliegende Sprache erleichtert werden, die nicht genauso gut erleichtert würden, wenn gesagt wird: "Wenn die Beendigung einer Schleife vom Zustand eines Objekts abhängt, wird die zur Ausführung der Schleife erforderliche Zeit nicht als eine angesehen." beobachtbare Nebenwirkung, auch wenn diese Zeit unendlich ist ". Angesichts der Tatsache, dass do { x = slowFunctionWithNoSideEffects(x);} while(x != 23);Hoisting-Code nach der Schleife nicht davon abhängt, xerscheint dies sicher und vernünftig, lässt jedoch einen Compiler davon ausgehenx==23 erscheint es eher gefährlich als nützlich in einem solchen Code zu .
Supercat
47

Für mich ist die relevante Begründung:

Dies soll Compiler-Transformationen ermöglichen, z. B. das Entfernen leerer Schleifen, auch wenn die Beendigung nicht nachgewiesen werden kann.

Vermutlich liegt dies daran, dass es schwierig ist , die Terminierung mechanisch zu beweisen, und dass die Unfähigkeit, die Terminierung zu beweisen, Compiler behindert, die andernfalls nützliche Transformationen vornehmen könnten, z. B. das Verschieben nicht abhängiger Operationen von vor der Schleife nach nachher oder umgekehrt, wobei Post-Loop-Operationen in einem Thread ausgeführt werden Die Schleife wird in einer anderen ausgeführt und so weiter. Ohne diese Transformationen kann eine Schleife alle anderen Threads blockieren, während sie darauf warten, dass der eine Thread diese Schleife beendet. (Ich verwende "Thread" lose, um jede Form der Parallelverarbeitung zu bezeichnen, einschließlich separater VLIW-Befehlsströme.)

EDIT: Dummes Beispiel:

while (complicated_condition()) {
    x = complicated_but_externally_invisible_operation(x);
}
complex_io_operation();
cout << "Results:" << endl;
cout << x << endl;

Hier wäre es für einen Thread schneller, dies zu tun, complex_io_operationwährend der andere alle komplexen Berechnungen in der Schleife ausführt. Ohne die von Ihnen angegebene Klausel muss der Compiler jedoch zwei Dinge beweisen, bevor er die Optimierung durchführen kann: 1) die complex_io_operation()nicht von den Ergebnissen der Schleife abhängt, und 2) die Schleife wird beendet . 1) zu beweisen ist ziemlich einfach, 2) zu beweisen ist das Halteproblem. Mit der Klausel kann davon ausgegangen werden, dass die Schleife beendet wird und ein Parallelisierungsgewinn erzielt wird.

Ich stelle mir auch vor, dass die Designer der Ansicht waren, dass die Fälle, in denen Endlosschleifen im Produktionscode auftreten, sehr selten sind und normalerweise ereignisgesteuerte Schleifen sind, die auf irgendeine Weise auf E / A zugreifen. Infolgedessen haben sie den seltenen Fall (Endlosschleifen) zugunsten der Optimierung des häufigeren Falls (nicht unendliche, aber schwer mechanisch zu beweisende nicht unendliche Schleifen) pessimiert.

Dies bedeutet jedoch, dass Endlosschleifen, die in Lernbeispielen verwendet werden, darunter leiden und Fallstricke im Anfängercode auslösen. Ich kann nicht sagen, dass das eine ganz gute Sache ist.

BEARBEITEN: In Bezug auf den aufschlussreichen Artikel, den Sie jetzt verlinken, würde ich sagen, dass "der Compiler X über das Programm annehmen kann" logisch äquivalent zu "wenn das Programm X nicht erfüllt, ist das Verhalten undefiniert" ist. Wir können dies wie folgt zeigen: Angenommen, es gibt ein Programm, das die Eigenschaft X nicht erfüllt. Wo würde das Verhalten dieses Programms definiert? Der Standard definiert das Verhalten nur unter der Annahme, dass die Eigenschaft X wahr ist. Obwohl der Standard das Verhalten nicht explizit als undefiniert deklariert, hat er es durch Auslassen als undefiniert deklariert.

Stellen Sie sich ein ähnliches Argument vor: "Der Compiler kann davon ausgehen, dass eine Variable x höchstens einmal zwischen Sequenzpunkten zugewiesen wird" entspricht "mehr als einmal zwischen Sequenzpunkten zuzuweisen ist undefiniert".

Philip Potter
quelle
"Beweisen 1) ist ziemlich einfach" - folgt es nicht unmittelbar aus den drei Bedingungen, unter denen der Compiler die Schleifenbeendigung gemäß der Klausel, nach der Johannes fragt, annehmen darf? Ich denke, sie belaufen sich auf "die Schleife hat keinen beobachtbaren Effekt, außer vielleicht für immer zu drehen", und die Klausel stellt sicher, dass "für immer drehen" für solche Schleifen kein garantiertes Verhalten ist.
Steve Jessop
@Steve: Es ist einfach, wenn die Schleife nicht endet. Wenn die Schleife jedoch beendet wird, kann sie ein nicht triviales Verhalten aufweisen, das sich auf die Verarbeitung der Schleife auswirkt complex_io_operation.
Philip Potter
Hoppla, ja, ich habe verpasst, dass es nichtflüchtige Einheimische / Aliase / was auch immer modifizieren könnte, die in der E / A-Operation verwendet werden. Sie haben also Recht: Obwohl dies nicht unbedingt der Fall ist, gibt es viele Fälle, in denen Compiler beweisen können und müssen, dass keine solche Änderung erfolgt.
Steve Jessop
"Es bedeutet jedoch, dass Endlosschleifen, die in Lernbeispielen verwendet werden, darunter leiden und Fallstricke im Anfängercode auslösen. Ich kann nicht sagen, dass dies eine ganz gute Sache ist." Kompilieren Sie einfach mit
deaktivierten
1
@supercat: Was Sie beschreiben, ist, was in der Praxis passieren wird, aber es ist nicht das, was der Standardentwurf erfordert. Wir können nicht davon ausgehen, dass der Compiler nicht weiß, ob eine Schleife beendet wird. Wenn der Compiler weiß, dass die Schleife nicht beendet wird, kann er tun, was er will. Die DS9K wird nasal Dämonen schaffen jeder ohne I / O usw. Endlosschleife (daher die DS9K löst das Halteproblem.)
Philip Potter
15

Ich denke, die richtige Interpretation ist die aus Ihrer Bearbeitung: Leere Endlosschleifen sind undefiniertes Verhalten.

Ich würde nicht sagen, dass es ein besonders intuitives Verhalten ist, aber diese Interpretation ist sinnvoller als die alternative, dass der Compiler willkürlich Endlosschleifen ignorieren darf, ohne UB aufzurufen.

Wenn Endlosschleifen UB ist, es bedeutet nur , dass nicht abbrechenden Programme sind nicht aussagekräftig betrachtet: nach C ++ 0x, sie haben keine Semantik.

Das macht auch einen gewissen Sinn. Dies ist ein Sonderfall, bei dem eine Reihe von Nebenwirkungen einfach nicht mehr auftreten (z. B. wird nie etwas zurückgegeben main) und eine Reihe von Compiler-Optimierungen dadurch behindert werden, dass Endlosschleifen beibehalten werden müssen. Zum Beispiel ist das Verschieben von Berechnungen über die Schleife vollkommen gültig, wenn die Schleife keine Nebenwirkungen hat, da die Berechnung schließlich in jedem Fall durchgeführt wird. Wenn die Schleife jedoch niemals beendet wird, können wir den Code nicht sicher neu anordnen, da wir möglicherweise nur ändern, welche Operationen tatsächlich ausgeführt werden, bevor das Programm hängt. Es sei denn, wir behandeln ein Hängeprogramm als UB.

jalf
quelle
7
"Leere Endlosschleifen sind undefiniertes Verhalten"? Alan Turing würde sich unterscheiden, aber nur, wenn er es hinter sich hat, sich in seinem Grab zu drehen.
Donal Fellows
11
@Donal: Ich habe nie etwas über die Semantik in einer Turing-Maschine gesagt. Wir diskutieren die Semantik einer Endlosschleife ohne Nebenwirkungen in C ++ . Und während ich es lese, sagt C ++ 0x, dass solche Schleifen undefiniert sind.
Jalf
Leere Endlosschleifen wären dumm, und es gäbe keinen Grund, spezielle Regeln für sie zu haben. Die Regel wurde entwickelt, um nützliche Schleifen von unbegrenzter (hoffentlich nicht unendlicher) Dauer zu behandeln, die etwas berechnen, das in Zukunft benötigt wird, aber nicht sofort.
Supercat
1
Bedeutet dies, dass C ++ 0x nicht für eingebettete Geräte geeignet ist? Fast alle eingebetteten Geräte werden nicht terminiert und erledigen ihre Arbeit in einem großen Fett while(1){...}. Sie werden sogar routinemäßig verwendet while(1);, um einen Watchdog-unterstützten Reset aufzurufen.
vsz
1
@vsz: Die erste Form ist in Ordnung. Endlosschleifen sind perfekt definiert, solange sie ein beobachtbares Verhalten aufweisen. Die zweite Form ist schwieriger, aber ich kann mir zwei sehr einfache Auswege vorstellen: (1) Ein Compiler, der auf eingebettete Geräte abzielt, kann in diesem Fall nur ein strengeres Verhalten definieren, oder (2) Sie erstellen einen Body, der eine Dummy-Bibliotheksfunktion aufruft . Solange der Compiler nicht weiß, was diese Funktion bewirkt, muss er davon ausgehen, dass sie möglicherweise Nebenwirkungen hat und sich daher nicht mit der Schleife herumschlagen kann.
Jalf
8

Ich denke, dies entspricht der Art dieser Frage , die auf einen anderen Thread verweist . Durch die Optimierung können gelegentlich leere Schleifen entfernt werden.

linuxuser27
quelle
3
Gute Frage. Scheint, als hätte dieser Typ genau das Problem, das dieser Compiler durch diesen Absatz verursachen kann. In der verknüpften Diskussion durch eine der Antworten heißt es: "Leider werden die Wörter 'undefiniertes Verhalten' nicht verwendet. Immer wenn der Standard sagt 'der Compiler kann P annehmen', wird jedoch impliziert, dass ein Programm das hat Eigenschaft not-P hat eine undefinierte Semantik. " . Das überrascht mich. Bedeutet dies, dass mein Beispielprogramm oben ein undefiniertes Verhalten aufweist und möglicherweise aus dem Nichts fehlerhaft ist?
Johannes Schaub - litb
@Johannes: Der Text "kann angenommen werden" kommt nirgendwo anders in dem Entwurf vor, den ich zur Hand habe, und "darf annehmen" kommt nur ein paar Mal vor. Obwohl ich dies mit einer Suchfunktion überprüft habe, die nicht über Zeilenumbrüche hinweg übereinstimmt, habe ich möglicherweise einige übersehen. Ich bin mir also nicht sicher, ob die Verallgemeinerung des Autors in Bezug auf die Beweise gerechtfertigt ist, aber als Mathematiker muss ich die Logik des Arguments zugeben, dass der Compiler, wenn er etwas Falsches annimmt, im Allgemeinen alles ableiten kann ...
Steve Jessop
... Permitting einen Widerspruch in der Argumentation der Compiler über das Programm sicherlich sehr stark bei UB Hinweise, insbesondere , da es den Compiler ermöglicht es , für jeden X, abzuleiten , dass das Programm zu X. äquivalent ist sicherlich den Compiler ermöglicht zu folgern , dass ist es erlauben, das zu tun . Ich stimme auch dem Autor zu, dass wenn UB beabsichtigt ist, es explizit angegeben werden sollte, und wenn es nicht beabsichtigt ist, dann ist der Spezifikationstext falsch und sollte korrigiert werden (möglicherweise durch das Äquivalent der Spezifikationssprache von ", kann der Compiler das ersetzen Schleife mit Code, der keine Wirkung hat ", bin ich mir nicht sicher).
Steve Jessop
@SteveJessop: Was würden Sie davon halten, einfach zu sagen, dass die Ausführung eines Codeteils - einschließlich Endlosschleifen - so lange verschoben werden kann, bis etwas, das der Codeteil getan hat, ein beobachtbares Programmverhalten beeinflusst, und das zu diesem Zweck In der Regel ist die Zeit, die benötigt wird, um einen Code auszuführen - auch wenn er unendlich ist - kein "beobachtbarer Nebeneffekt". Wenn ein Compiler nachweisen kann, dass eine Schleife nicht beendet werden kann, ohne dass eine Variable einen bestimmten Wert enthält, kann davon ausgegangen werden, dass die Variable diesen Wert enthält, und es kann auch gezeigt werden, dass die Schleife nicht beendet werden kann, wenn sie diesen Wert enthält.
Supercat
@supercat: Wie Sie diese Schlussfolgerung gesagt haben, denke ich nicht, dass es die Dinge verbessert. Wenn die Schleife nachweislich nie für ein Objekt Xund ein Bitmuster beendet wird x, kann der Compiler nachweisen, dass die Schleife nicht beendet wird, ohne Xdas Bitmuster zu halten x. Es ist vakuum wahr. Man Xkönnte also davon ausgehen, dass es ein beliebiges Bitmuster enthält, und das ist genauso schlecht wie UB in dem Sinne, dass für das Falsche Xund xes schnell einige verursachen wird. Ich glaube, Sie müssen in Ihrem Standard präziser sein. Es ist schwierig, darüber zu sprechen, was "am Ende" einer Endlosschleife passiert, und es einer endlichen Operation gleichzusetzen.
Steve Jessop
8

Das relevante Problem ist, dass der Compiler Code neu anordnen darf, dessen Nebenwirkungen nicht in Konflikt stehen. Die überraschende Ausführungsreihenfolge könnte auch dann auftreten, wenn der Compiler nicht terminierenden Maschinencode für die Endlosschleife erzeugt.

Ich glaube, das ist der richtige Ansatz. Die Sprachspezifikation definiert Möglichkeiten zum Erzwingen der Ausführungsreihenfolge. Wenn Sie eine Endlosschleife wünschen, die nicht neu angeordnet werden kann, schreiben Sie Folgendes:

volatile int dummy_side_effect;

while (1) {
    dummy_side_effect = 0;
}

printf("Never prints.\n");
Daniel Newby
quelle
2
@ JohannesSchaub-litb: Wenn eine Endlosschleife - endlos oder nicht - während der Ausführung keine flüchtigen Variablen liest oder schreibt und keine Funktionen aufruft, die dies tun könnten, kann ein Compiler einen Teil der Schleife verschieben, bis der erste Versuch, auf etwas zuzugreifen, das darin berechnet wurde. Vorausgesetzt unsigned int dummy; while(1){dummy++;} fprintf(stderror,"Hey\r\n"); fprintf(stderror,"Result was %u\r\n",dummy);, der erste fprintfkönnte ausgeführt werden, der zweite jedoch nicht (der Compiler könnte die Berechnung dummyzwischen den beiden verschieben fprintf, jedoch nicht über diejenige hinaus, die seinen Wert druckt).
Supercat
1

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

void testfermat (int n)
{
  int a = 1, b = 1, c = 1;
  während (pow (a, n) + pow (b, n)! = pow (c, n))
  {
    wenn (b> a) a ++; sonst wenn (c> b) {a = 1; b ++}; sonst {a = 1; b = 1; c ++};
  }}
  printf ("Das Ergebnis ist");
  printf ("% d /% d /% d", a, b, c);
}}

wie

void testfermat (int n)
{
  if (fork_is_first_thread ())
  {
    int a = 1, b = 1, c = 1;
    während (pow (a, n) + pow (b, n)! = pow (c, n))
    {
      wenn (b> a) a ++; sonst wenn (c> b) {a = 1; b ++}; sonst {a = 1; b = 1; c ++};
    }}
    signal_other_thread_and_die ();
  }}
  sonst // Zweiter Thread
  {
    printf ("Das Ergebnis ist");
    wait_for_other_thread ();
  }}
  printf ("% d /% d /% d", a, b, c);
}}

Im Allgemeinen nicht unangemessen, obwohl ich mir Sorgen machen könnte, dass:

  int total = 0;
  für (i = 0; num_reps> i; i ++)
  {
    update_progress_bar (i);
    total + = do_something_slow_with_no_side_effects (i);
  }}
  show_result (total);

würde werden

  int total = 0;
  if (fork_is_first_thread ())
  {
    für (i = 0; num_reps> i; i ++)
      total + = do_something_slow_with_no_side_effects (i);
    signal_other_thread_and_die ();
  }}
  sonst
  {
    für (i = 0; num_reps> i; i ++)
      update_progress_bar (i);
    wait_for_other_thread ();
  }}
  show_result (total);

Wenn eine CPU die Berechnungen und eine andere die Fortschrittsbalkenaktualisierungen übernimmt, würde das Umschreiben die Effizienz verbessern. Leider wären die Aktualisierungen des Fortschrittsbalkens weniger nützlich, als sie sein sollten.

Superkatze
quelle
Ich denke, dass Ihr Fall des Fortschrittsbalkens nicht getrennt werden konnte, da das Anzeigen eines Fortschrittsbalkens ein E / A-Aufruf der Bibliothek ist. Optimierungen sollten das sichtbare Verhalten auf diese Weise nicht ändern.
Philip Potter
@Philip Potter: Wenn die langsame Routine Nebenwirkungen hätte, wäre das sicherlich wahr. In meinem vorherigen Beispiel wäre es bedeutungslos, wenn es nicht so wäre, also habe ich es geändert. Meine Interpretation der Spezifikation ist, dass das System die Ausführung des langsamen Codes verzögern darf, bis seine Auswirkungen (außer der Zeit, die für die Ausführung benötigt wird) sichtbar werden, dh der Aufruf show_result (). Wenn der Fortschrittsbalkencode die laufende Summe verwendet oder zumindest so tut, als würde dies dazu führen, dass er mit dem langsamen Code synchronisiert wird.
Supercat
1
Dies erklärt all jene Fortschrittsbalken, die schnell von 0 auf 100 gehen und dann
ewig
0

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

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 nach der Optimierung O (n) oder O (1)).

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


Eine andere Sache: Ich habe noch nie ein gültiges Beispiel gesehen, bei dem Sie eine Endlosschleife benötigen, die nichts bewirkt.

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

Wenn Sie ein gültiges / gutes Beispiel kennen, bei dem Sie eine Endlosschleife benötigen, die nichts bewirkt, sagen Sie es mir bitte.

Albert
quelle
1
Beispiel dafür, wo Sie eine Endlosschleife wünschen: ein eingebettetes System, in dem Sie aus Leistungsgründen nicht schlafen möchten und der gesamte Code an ein oder zwei Interrupts hängt?
JCx
@JCx In Standard C sollten die Interrupts ein Flag setzen, das von der Hauptschleife überprüft wird. Daher würde die Hauptschleife im Fall der gesetzten Flags ein beobachtbares Verhalten aufweisen. Das Ausführen von umfangreichem Code in Interrupts ist nicht portierbar.
MM
-1

Ich denke, es ist erwähnenswert, dass Schleifen, die unendlich wären, außer dass sie über nichtflüchtige, nicht synchronisierte Variablen mit anderen Threads interagieren, jetzt mit einem neuen Compiler zu falschem Verhalten führen können.

Mit anderen Worten, machen Sie Ihre Globalen flüchtig - sowie Argumente, die über einen Zeiger / eine Referenz in eine solche Schleife übergeben werden.

Spraff
quelle
Wenn sie mit anderen Threads interagieren, sollten Sie sie nicht flüchtig machen, atomarisieren oder mit einem Schloss schützen.
BCoates
1
Das ist ein schrecklicher Rat. Es volatileist weder notwendig noch ausreichend, sie herzustellen, und es beeinträchtigt die Leistung dramatisch.
David Schwartz