Une implémentation C ++ pourrait-elle, en théorie, paralléliser l’évaluation de deux arguments de fonction?

Compte tenu de l’appel de fonction suivant:

f(g(), h()) 

puisque l’ordre d’évaluation des arguments de fonction n’est pas spécifié (c’est toujours le cas en C ++ 11 à ma connaissance), une implémentation pourrait-elle théoriquement exécuter g() et h() en parallèle?

Une telle parallélisation ne pouvait que s’appuyer sur g et h connus pour être assez sortingviaux (dans le cas le plus évident, n’accédant qu’aux données locales à leurs corps) afin de ne pas introduire de problèmes de concurrence, mais au-delà de cette ressortingction l’interdire

Alors, la norme le permet-il? Même si ce n’est que par la règle si?

(Dans cette réponse , Mankarse affirme le contraire, mais il ne cite pas la norme, et ma lecture [expr.call] de [expr.call] n’a révélé aucune formulation évidente.)

L’exigence provient de [intro.execution]/15 :

… Lors de l’appel d’une fonction … Toute évaluation de la fonction appelante (y compris les autres appels de fonction) qui n’est pas autrement séquencée spécifiquement avant ou après l’exécution du corps de la fonction appelée est séquencée de manière indéterminée en ce qui concerne l’exécution du Fonction appelée [Note en bas de page: En d’autres termes, les exécutions de fonctions ne s’entrelacent pas les unes avec les autres. ].

Ainsi, toute exécution du corps de g() doit être indéfiniment séquencée avec (c’est-à-dire sans chevauchement) avec l’évaluation de h() (car h() est une expression dans la fonction appelante).

Le point critique ici est que g() et h() sont tous deux des appels de fonction.

(Bien sûr, la règle as-if signifie que la possibilité ne peut pas être entièrement exclue, mais elle ne devrait jamais se produire d’une manière susceptible d’affecter le comportement observable d’un programme. Une telle implémentation ne ferait que modifier les caractéristiques de performance de le code.)

Tant que vous ne pouvez pas le dire, tout ce que le compilateur fait pour évaluer ces fonctions dépend entièrement du compilateur. De toute évidence, l’évaluation des fonctions ne peut impliquer aucun access à des données partagées et mutables, car cela introduirait des courses de données. Le principe de base est la règle “comme si” et les opérations observables fondamentales, c’est-à-dire l’access aux données volatile , les opérations d’E / S, l’access aux données atomiques, etc.

Pas à moins que le compilateur ne sache exactement ce que g() , h() et tout ce qu’ils appellent fait.

Les deux expressions sont des appels de fonction qui peuvent avoir des effets secondaires inconnus. Par conséquent, leur parallélisation pourrait entraîner une course aux données sur ces effets secondaires. Comme le standard C ++ ne permet pas l’évaluation des arguments pour provoquer une course de données sur les effets secondaires des expressions, le compilateur ne peut les paralléliser que s’il sait qu’aucune course de données n’est possible.

Cela signifie marcher dans chaque fonction et regarder exactement ce qu’ils font et / ou appeler, puis suivre ces fonctions, etc. Dans le cas général, ce n’est pas possible.

Réponse facile: lorsque les fonctions sont séquencées , même si elles sont indéterminées, il n’y a pas de possibilité de condition de concurrence entre les deux, ce qui n’est pas le cas si elles sont parallélisées. Même une paire de fonctions “sortingviales” d’une ligne pourrait le faire.

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

Si p est un nom partagé par g et h , alors un appel séquentiel de g et h dans n’importe quel ordre entraînera la valeur pointée par p ne changeant pas. Si elles sont parallélisées, la lecture de *p et son affectation pourraient être entrelacées arbitrairement entre les deux:

  1. g lit *p et trouve la valeur 1.
  2. f lit *p et trouve également la valeur 1.
  3. g écrit 2 à *p .
  4. f , en utilisant toujours la valeur 1 lue avant, écrira 0 à *p .

Ainsi, le comportement est différent quand ils sont parallélisés.