Kann ich std :: transform mit einer Richtlinie für parallele Ausführung verwenden?

11

Wenn ich mich nicht täuscht, kann ich std::transformausführen anstelle unter Verwendung des gleichen Bereichs als Ein- und Ausgang Iterator. Angenommen, ich habe ein std::vectorObjekt vec, dann würde ich schreiben

std::transform(vec.cbegin(),vec.cend(),vec.begin(),unary_op)

unter Verwendung einer geeigneten unären Operation unary_op.

Unter Verwendung des C ++ 17-Standards möchte ich die Transformation parallel ausführen, indem ich std::execution::pardort als erstes Argument ein In stecke. Dies würde dazu führen, dass die Funktion von Überladung (1) nach (2) im Referenzartikel weiter wechseltstd::transform . Die Kommentare zu dieser Überladung lauten jedoch:

unary_op[...] dürfen keine Iteratoren, einschließlich der Enditeratoren, ungültig machen oder Elemente der beteiligten Bereiche ändern. (seit C ++ 11)

Bedeutet "Elemente ändern" wirklich, dass ich den Algorithmus nicht verwenden kann, oder handelt es sich um ein anderes Detail, das ich falsch interpretiert habe?

geo
quelle

Antworten:

4

Um den Standard hier zu zitieren

[alg.transform.1]

op [...] darf Iteratoren oder Unterordnungen nicht ungültig machen oder Elemente in den Bereichen ändern

Dies verbietet es Ihnen unary_op, entweder den als Argument angegebenen Wert oder den Container selbst zu ändern.

auto unary_op = [](auto& value) 
{ 
    value = 10;    // this is bad
    return value;
}

auto unary_op = [&vec](auto const& value) 
{ 
    vec[0] = value;   // also bad
    return value;
}

auto unary_op = [&vec](auto& value) 
{ 
    vec.erase(vec.begin());   // nope 
    return value;
}

Das Folgende ist jedoch in Ordnung.

auto unary_op = [](auto& value)  // const/ref not strictly needed
{         
    return value + 10;   // totally fine
}

auto unary_op = [&vec](auto& value)
{         
    return value + vec[0];   // ok in sequential but not in parallel execution
}

Unabhängig von dem, was UnaryOperationwir haben

[alg.transform.5]

Das Ergebnis kann bei unärer Transformation gleich [...] sein.

Dies bedeutet, dass Vor-Ort-Operationen ausdrücklich zulässig sind.

Jetzt

[algorithm.parallel.overloads.2]

Sofern nicht anders angegeben, ist die Semantik von ExecutionPolicy-Algorithmusüberladungen identisch mit ihren Überladungen ohne.

bedeutet, dass die Ausführungsrichtlinie keinen vom Benutzer sichtbaren Unterschied zum Algorithmus aufweist. Sie können davon ausgehen, dass der Algorithmus genau das gleiche Ergebnis liefert, als würden Sie keine Ausführungsrichtlinie angeben.

Timo
quelle
6

Ich glaube, dass es sich um ein anderes Detail handelt. Das unary_opnimmt ein Element der Sequenz und gibt einen Wert zurück. Dieser Wert wird (von transform) in der Zielsequenz gespeichert .

Das unary_opwäre also in Ordnung:

int times2(int v) { return 2*v; }

aber dieser würde nicht:

int times2(int &v) { return v*=2; }

Aber das ist nicht wirklich das, worüber du fragst. Sie möchten wissen, ob Sie die unary_opVersion von transformals parallelen Algorithmus mit demselben Quell- und Zielbereich verwenden können. Ich verstehe nicht warum nicht. transformordnet ein einzelnes Element der Quellsequenz einem einzelnen Element der Zielsequenz zu. Wenn Sie unary_opjedoch nicht wirklich unär sind (dh auf andere Elemente in der Sequenz verweisen - selbst wenn sie nur gelesen werden, haben Sie ein Datenrennen).

Marshall Clow
quelle
1

Wie Sie im Beispiel des von Ihnen zitierten Links sehen können, bedeutet das Ändern von Elementen nicht alle Arten von Änderungen an den Elementen:

Die Signatur der Funktion sollte der folgenden entsprechen:

Ret fun(const Type &a);

Dies schließt Änderungen an den Elementen ein. Im schlimmsten Fall, wenn Sie denselben Iterator als Ziel verwenden, sollte die Änderung keine Ungültigmachung von Iteratoren verursachen, z. B. ein push_backto-Vektor oder ein erasIng, von vectordem wahrscheinlich die Ungültigmachung von Iteratoren verursacht wird.

Sehen Sie sich ein Beispiel für einen Fehler an, den Sie NICHT live ausführen sollten .

Vergessenheit
quelle