En avant : Lorsque ce billet parle simplement de "coroutines", je fais référence aux concept d'une coroutine, et non la caractéristique spécifique du C++20. Lorsque je parlerai de cette fonctionnalité, je l'appellerai " co_await
"ou "co_await coroutines".
Sur l'allocation dynamique
Cppreference utilise parfois une terminologie plus souple que celle de la norme. co_await
en tant que fonctionnalité "nécessite" une allocation dynamique ; que cette allocation provienne du tas ou d'un bloc de mémoire statique ou autre est une question pour le fournisseur de l'allocation. De telles allocations peuvent être éludées dans des circonstances arbitraires, mais puisque la norme ne les précise pas, vous devez toujours supposer que toute coroutine co_await peut allouer dynamiquement de la mémoire.
Les coroutines co_await disposent de mécanismes permettant aux utilisateurs de fournir une allocation pour l'état de la coroutine. Ainsi, vous pouvez substituer l'allocation du tas/stock libre pour tout pool de mémoire particulier que vous préférez.
co_await
en tant que fonctionnalité est bien conçu pour supprimer verbosité du point de vue de l'utilisation pour tout co_await
-des objets et des fonctionnalités. Le site co_await
La machinerie est incroyablement compliquée et complexe, avec de nombreuses interactions entre des objets de plusieurs types. Mais au point d'arrêt/de reprise, elle toujours ressemble à co_await <some expression>
. L'ajout de la prise en charge des allocateurs à vos objets en attente et à vos promesses nécessite une certaine verbosité, mais cette verbosité se trouve en dehors de l'endroit où ces choses sont utilisées.
Utilisation de alloca
pour une coroutine serait... très inapproprié pour... le plus les utilisations de co_await
. Bien que la discussion autour de cette fonctionnalité tente de le cacher, le fait est que co_await
en tant que fonctionnalité est conçu pour une utilisation asynchrone. C'est son but : arrêter l'exécution d'une fonction et planifier la reprise de cette fonction sur un autre thread potentiel, puis acheminer toute valeur éventuellement générée vers un code récepteur qui peut être quelque peu éloigné du code qui a invoqué la coroutine.
alloca
n'est pas approprié pour ce cas d'utilisation particulier, puisque l'appelant de la coroutine est autorisé/encouragé à faire n'importe quoi pour que la valeur puisse être générée par un autre thread. L'espace alloué par alloca
n'existerait donc plus, et c'est plutôt mauvais pour la coroutine qui y vit.
Notez également que les performances de l'allocation dans un tel scénario seront généralement éclipsées par d'autres considérations : l'ordonnancement des threads, les mutex, et d'autres choses seront souvent nécessaires pour planifier correctement la reprise de la coroutine, sans parler du temps nécessaire pour obtenir la valeur de n'importe quel processus asynchrone qui la fournit. Ainsi, le fait qu'une allocation dynamique soit nécessaire n'est pas vraiment une considération importante dans ce cas.
Maintenant, il y a sont les circonstances dans lesquelles l'allocation in situ serait appropriée. Les cas d'utilisation des générateurs sont ceux où vous souhaitez essentiellement mettre une fonction en pause et renvoyer une valeur, puis reprendre là où la fonction s'est arrêtée et éventuellement renvoyer une nouvelle valeur. Dans ces scénarios, la pile de la fonction qui invoque la coroutine sera certainement toujours là.
co_await
prend en charge de tels scénarios (bien que co_yield
), mais il le fait d'une manière moins qu'optimale, du moins en ce qui concerne la norme. Parce que la fonction est conçue pour la suspension up-and-out, la transformer en coroutine suspend-down a pour effet d'avoir cette allocation dynamique qui n'a pas besoin d'être dynamique.
C'est pourquoi la norme n'exige pas d'allocation dynamique ; si un compilateur est suffisamment intelligent pour détecter un modèle d'utilisation de générateur, il peut supprimer l'allocation dynamique et allouer simplement l'espace sur la pile locale. Mais encore une fois, c'est ce qu'un compilateur peut faire, pas devoir faire.
Dans ce cas, alloca
-L'allocation basée sur les résultats serait appropriée.
Comment il est entré dans la norme
La version courte est qu'elle a été intégrée à la norme parce que les personnes à l'origine de son élaboration ont fourni le travail nécessaire, alors que les personnes à l'origine des autres solutions ne l'ont pas fait.
Toute idée de coroutine est compliquée, et il y aura toujours des questions sur l'implémentabilité en ce qui les concerne. Par exemple, le " fonctions pouvant être reprises "Les propositions étaient superbes, et j'aurais aimé les voir dans le standard. Mais personne n'a réellement mis en œuvre dans un compilateur. Donc personne ne pouvait prouver que c'était réellement une chose que vous pouviez faire. Oh bien sûr, il sons réalisable, mais cela ne veut pas dire que cela est réalisable.
Souvenez-vous de ce qui s'est passé la dernière fois "semble pouvoir être mis en œuvre" a été utilisé comme base pour l'adoption d'une fonctionnalité.
Vous ne voulez pas normaliser quelque chose si vous ne savez pas si cela peut être mis en œuvre. Et vous ne voulez pas normaliser quelque chose si vous ne savez pas si cela résout réellement le problème prévu.
Gor Nishanov et son équipe chez Microsoft ont travaillé pour mettre en place co_await
. Ils ont fait cela pour années en affinant leur mise en œuvre, etc. D'autres personnes ont utilisé leur implémentation dans du code de production réel et semblaient tout à fait satisfaites de sa fonctionnalité. Clang l'a même implémenté. Même si personnellement je n'aime pas ça, il est indéniable que co_await
est un mature fonction.
En revanche, les alternatives de "core coroutines" qui ont été évoquées il y a un an comme des idées concurrentes avec les co_await
n'a pas réussi à s'imposer en partie parce qu'ils étaient difficiles à mettre en œuvre . C'est pourquoi co_await
a été adopté : parce qu'il s'agissait d'un outil éprouvé, mature et solide, que les gens voulaient et dont la capacité à améliorer leur code avait été démontrée.
co_await
n'est pas pour tout le monde. Personnellement, je ne l'utiliserai probablement pas beaucoup, car les fibres fonctionnent bien mieux pour mes cas d'utilisation. Mais il est très bon pour son utilisation spécifique : la suspension de haut en bas.