Könnte eine C ++ - Implementierung theoretisch die Auswertung von zwei Funktionsargumenten parallelisieren?

68

Bei folgendem Funktionsaufruf:

f(g(), h())

Könnte eine Implementierung theoretisch g()und h()parallel ausgeführt werden, da die Reihenfolge der Auswertung von Funktionsargumenten nicht angegeben ist (soweit mir bekannt ist dies in C ++ 11 immer noch der Fall) ?

Eine solche Parallelisierung konnte nur eingesetzt werden, gund es war hbekannt, dass sie ziemlich trivial war (im offensichtlichsten Fall nur auf lokale Daten zuzugreifen), um keine Parallelitätsprobleme zu verursachen, aber über diese Einschränkung hinaus kann ich nichts sehen, was dies verbietet .

Erlaubt der Standard dies? Auch wenn nur nach der Als-ob-Regel?

(In dieser Antwort behauptet Mankarse etwas anderes; er zitiert jedoch nicht den Standard, und mein Durchlesen von [expr.call]hat keinen offensichtlichen Wortlaut ergeben.)

Leichtigkeitsrennen im Orbit
quelle
1
Ich würde denken, dass dies in die gleiche Richtung fällt wie jede andere Optimierung. Solange es die Als-ob-Regel respektiert.
Mysticial
meiner Meinung nach , ist es theoretisch kann die Ausführung von g parallelisieren () und h (). Ich bin nicht sicher, ob der Standard verbietet oder erlaubt, aber meine Idee ist, dass es sollte
Aniket Inge
14
Es könnte eine Rakete starten, um die Mondmänner zu bitten, das Ergebnis des Funktionsaufrufs mitzuteilen und zurückzufahren.
Johannes Schaub - Litb
1
Wie Sie sagen, würde dies nur für ziemlich triviale Zwecke eingesetzt werden ( gund hin diesem Fall könnte der Compiler wahrscheinlich beweisen, dass es sowieso einer sequenziellen Bewertung entspricht), und dann hätte es nicht wirklich viel Vorteil: Das Erstellen eines Threads wäre wahrscheinlich teurer als eine der Funktionen zu bewerten. - Bei größeren Funktionen, bei denen die Leistung im Prinzip erheblich gesteigert werden könnte, ist es schwierig, die Kontrolle über die mögliche Explosion rekursiver Thread-Spawns zu behalten. Sogar Haskell tut dies nicht, da es überall freie Bewertungsreihenfolge und leichte Gewinde gibt.
links um den
1
Wenn wir einen Bezeichner purefür Funktionen hätten, der der Implementierung mitteilt, dass diese Funktion nicht auf den (möglicherweise) gemeinsam genutzten Status zugreift (auch bekannt als hat sie keine Nebenwirkungen), könnte dies in Zukunft möglich werden.
Xeo

Antworten:

42

Die Anforderung kommt von [intro.execution]/15:

... beim Aufrufen einer Funktion ... Jede Auswertung in der aufrufenden Funktion (einschließlich anderer Funktionsaufrufe), die vor oder nach der Ausführung des Hauptteils der aufgerufenen Funktion nicht spezifisch sequenziert wird, wird in Bezug auf die Ausführung der Funktion unbestimmt sequenziert aufgerufene Funktion [Fußnote: Mit anderen Worten, Funktionsausführungen verschachteln sich nicht miteinander. ].

Daher muss jede Ausführung des Körpers von g()unbestimmt mit der Auswertung von h()(weil dies h()ein Ausdruck in der aufrufenden Funktion ist) sequenziert werden (dh nicht überlappen ).

Der kritische Punkt hier ist, dass g()und h()beide Funktionsaufrufe sind.

(Natürlich bedeutet die Als-ob-Regel, dass die Möglichkeit nicht vollständig ausgeschlossen werden kann, aber sie sollte niemals auf eine Weise erfolgen, die das beobachtbare Verhalten eines Programms beeinflussen könnte. Eine solche Implementierung würde höchstens die Leistungsmerkmale von ändern der Code.)

Mankarse
quelle
Das ist der eine. Es ist leicht zu glauben, dass dies nur für g()vs f()und h()vs gilt f(), aber in der Tat ist der Nettoeffekt, dass das g()und das h()nicht verschachtelt werden können.
Leichtigkeitsrennen im Orbit
Ich denke , dass meine ursprüngliche Verwirrung war die „unbestimmbar“ mehr beim Lesen stark als die „sequenziert“, in indeterminately sequenziert . In der Tat ist "sequenziert" der Schlüsselbegriff, da ich mir vorstelle, dass verschachtelte Anrufe keine "Sequenz" zueinander haben würden.
Leichtigkeitsrennen im Orbit
Diese Antwort ist richtig, aber es ist erwähnenswert, dass dies nur so ist, weil keiner der Aufrufe g()und h()Argumente nimmt. Wenn dies der Fall gewesen wäre, würde die Bewertung dieser Argumente (in Vorbereitung auf die Ausführung von foder h) nicht unbestimmt miteinander sequenziert (so dass sie verschachtelt werden könnten), obwohl sie in Bezug auf den Körper des anderen unbestimmt sequenziert würden von fund h.
Marc van Leeuwen
16

Solange Sie nicht sagen können, liegt es ganz beim Compiler, was der Compiler tut, um diese Funktionen zu bewerten. Es ist klar, dass die Bewertung der Funktionen keinen Zugriff auf gemeinsam genutzte, veränderbare Daten beinhalten kann, da dies zu Datenrennen führen würde. Das grundlegende Leitprinzip ist die "als ob" -Regel und die grundlegenden beobachtbaren Operationen, dh Zugriff auf volatileDaten, E / A-Operationen, Zugriff auf Atomdaten usw. Der relevante Abschnitt ist 1.9 [Einführung].

Dietmar Kühl
quelle
Ich glaube ich war unklar. Ich erkenne an, dass es im allgemeinen Fall nicht angewendet werden konnte. Aber in den Fällen, in denen das Verhalten der Funktionen selbst diese Möglichkeit nicht blockierte ...
Leichtigkeitsrennen im Orbit
4
Ich dachte, ich wäre klar: Wenn der Compiler erkennen kann, dass er seine Einschränkungen nicht verletzt, kann er alles tun, was er möchte. Anders ausgedrückt: Solange Sie nicht sagen können (außer möglicherweise die Zeit zu messen), dass es die Funktionen parallel ausgeführt hat, ist es kostenlos, dies zu tun.
Dietmar Kühl
Ich habe Ihre Antwort beim ersten Mal etwas anders gelesen. Hat sich das im fünfminütigen Gnadenfenster überhaupt geändert? Oder werde ich etwas verrückt?
Leichtigkeitsrennen im Orbit
1
@ LightnessRacesinOrbit: Nun, es hat sich kurz nach meinem ersten Posting geändert, aber nicht wesentlich: Ich habe "relecant" als "relevant" festgelegt, aber ich glaube nicht, dass dies die Semantik geändert hat;)
Dietmar Kühl
Guter Punkt. Die Spezifikation definiert die Semantik, nicht die Implementierung. Wenn es also keinen semantischen Unterschied bei der Parallelisierung gibt, kann der Compiler dies tun.
Dhasenan
3

Nicht , wenn der Compiler wußte genau , was g(), h()und alles , was sie nennen tut.

Die beiden Ausdrücke sind Funktionsaufrufe, die unbekannte Nebenwirkungen haben können. Eine Parallelisierung kann daher zu einem Datenwettlauf bei diesen Nebenwirkungen führen. Da der C ++ - Standard nicht zulässt, dass die Argumentauswertung einen Datenwettlauf mit Nebenwirkungen der Ausdrücke verursacht, kann der Compiler diese nur parallelisieren, wenn er weiß, dass ein solcher Datenwettlauf nicht möglich ist.

Das bedeutet, dass Sie durch jede Funktion gehen und genau sehen, was sie tun und / oder aufrufen, und dann diese Funktionen usw. nachverfolgen. Im allgemeinen Fall ist dies nicht möglich.

Nicol Bolas
quelle
Ich gebe zu, dass es nicht in allen Fällen möglich war. Ich nehme s/were g and h fairly trivial/were g and h known to be fairly trivial/in m Frage an.
Leichtigkeitsrennen im Orbit
1

Einfache Antwort: Wenn die Funktionen sequenziert werden , auch wenn sie unbestimmt sind, gibt es keine Möglichkeit für eine Race-Bedingung zwischen den beiden, was nicht zutrifft, wenn sie parallelisiert sind. Sogar ein Paar einzeiliger "trivialer" Funktionen könnte dies tun.

void g()
{
    *p = *p + 1;
}


void h()
{
    *p = *p - 1;
}

Wenn pein Name von gund geteilt wird h, führt ein sequentieller Aufruf von gund hin beliebiger Reihenfolge dazu, dass der Wert angezeigt wird, auf den pnicht geändert wird . Wenn sie parallelisiert sind, kann das Lesen *pund Zuweisen willkürlich zwischen den beiden verschachtelt werden:

  1. gliest *pund findet den Wert 1.
  2. fliest *pund findet auch den Wert 1.
  3. gschreibt 2 an *p.
  4. fWenn Sie immer noch den zuvor gelesenen Wert 1 verwenden, wird 0 in geschrieben *p.

Daher ist das Verhalten anders, wenn sie parallelisiert werden.

Nevsan
quelle
Sicher, aber es war bisher nicht für mich klar , dass die beiden Anrufe waren indeterminately sequenziert werden (im Gegensatz zu unsequenced Gegensatz), daher die Frage. :)
Leichtigkeitsrennen im Orbit