J'ai cherché sur le Web des détails techniques sur le blocage des E / S et les E / S non bloquantes et j'ai trouvé plusieurs personnes affirmant que les E / S non bloquantes seraient plus rapides que les E / S bloquantes. Par exemple dans ce document .
Si j'utilise le blocage des E / S, alors bien sûr, le thread actuellement bloqué ne peut rien faire d'autre ... Parce qu'il est bloqué. Mais dès qu'un thread commence à être bloqué, le système d'exploitation peut basculer vers un autre thread et ne pas revenir en arrière jusqu'à ce qu'il y ait quelque chose à faire pour le thread bloqué. Donc, tant qu'il y a un autre thread sur le système qui a besoin de CPU et qui n'est pas bloqué, il ne devrait plus y avoir de temps d'inactivité du CPU par rapport à une approche non bloquante basée sur les événements, n'est-ce pas?
En plus de réduire le temps d'inactivité du processeur, je vois une autre option pour augmenter le nombre de tâches qu'un ordinateur peut effectuer dans un laps de temps donné: réduire la surcharge introduite par le changement de thread. Mais comment cela peut-il se faire? Et les frais généraux sont-ils suffisamment importants pour montrer des effets mesurables? Voici une idée sur la façon dont je peux l'imaginer fonctionner:
- Pour charger le contenu d'un fichier, une application délègue cette tâche à une infrastructure d'E / S basée sur des événements, en passant une fonction de rappel avec un nom de fichier
- L'infrastructure d'événements délègue au système d'exploitation, qui programme un contrôleur DMA du disque dur pour écrire le fichier directement dans la mémoire
- Le cadre d'événements permet l'exécution de code supplémentaire.
- Une fois la copie disque vers mémoire terminée, le contrôleur DMA provoque une interruption.
- Le gestionnaire d'interruptions du système d'exploitation informe la structure d'E / S basée sur les événements que le fichier est complètement chargé en mémoire. Comment fait-il cela? Utilisation d'un signal ??
- Le code qui est actuellement exécuté dans le cadre d'E / S d'événements se termine.
- L'infrastructure d'E / S basée sur les événements vérifie sa file d'attente et voit le message du système d'exploitation à partir de l'étape 5 et exécute le rappel obtenu à l'étape 1.
Est-ce ainsi que cela fonctionne? Si ce n'est pas le cas, comment ça marche? Cela signifie que le système d'événements peut fonctionner sans jamais avoir besoin de toucher explicitement la pile (comme un vrai planificateur qui aurait besoin de sauvegarder la pile et de copier la pile d'un autre thread en mémoire tout en changeant de thread)? Combien de temps cela fait-il réellement gagner? Y a-t-il autre chose?
la source
Réponses:
Le plus grand avantage des E / S non bloquantes ou asynchrones est que votre thread peut continuer son travail en parallèle. Bien sûr, vous pouvez également y parvenir en utilisant un fil supplémentaire. Comme vous l'avez déclaré pour obtenir les meilleures performances globales (système), je suppose qu'il serait préférable d'utiliser des E / S asynchrones et non plusieurs threads (réduisant ainsi le changement de thread).
Regardons les implémentations possibles d'un programme de serveur réseau qui doit gérer 1000 clients connectés en parallèle:
Chaque thread nécessite des ressources de mémoire (également de la mémoire du noyau!), C'est un inconvénient. Et chaque thread supplémentaire signifie plus de travail pour le planificateur.
Cela prend la charge du système car nous avons moins de threads. Mais cela vous empêche également d'utiliser toutes les performances de votre machine, car vous pourriez finir par conduire un processeur à 100% et laisser tous les autres processeurs inactifs.
Cela prend la charge du système car il y a moins de threads. Et il peut utiliser tous les processeurs disponibles. Sous Windows, cette approche est prise en charge par l' API Thread Pool .
Bien sûr, avoir plus de threads n'est pas un problème en soi. Comme vous l'avez peut-être reconnu, j'ai choisi un nombre assez élevé de connexions / threads. Je doute que vous voyiez une différence entre les trois implémentations possibles si nous ne parlons que d'une dizaine de threads (c'est aussi ce que suggère Raymond Chen sur le billet de blog MSDN. Windows a-t-il une limite de 2000 threads par processus? ).
Sous Windows, l'utilisation d' E / S de fichier sans tampon signifie que les écritures doivent être d'une taille qui est un multiple de la taille de la page. Je ne l'ai pas testé, mais il semble que cela pourrait également affecter positivement les performances d'écriture pour les écritures synchrones et asynchrones tamponnées.
Les étapes 1 à 7 que vous décrivez donnent une bonne idée de son fonctionnement. Sous Windows, le système d'exploitation vous informera de l'achèvement d'une E / S asynchrone (
WriteFile
avecOVERLAPPED
structure) à l'aide d'un événement ou d'un rappel. Les fonctions de rappel ne seront appelées par exemple que lorsque votre code appelleWaitForMultipleObjectsEx
avecbAlertable
réglé surtrue
.Quelques lectures supplémentaires sur le Web:
la source
Les E / S comprennent plusieurs types d'opérations telles que la lecture et l'écriture de données à partir de disques durs, l'accès aux ressources réseau, l'appel de services Web ou la récupération de données à partir de bases de données. En fonction de la plate-forme et du type d'opération, les E / S asynchrones tireront généralement parti de tout support matériel ou système de bas niveau pour effectuer l'opération. Cela signifie qu'il sera exécuté avec le moins d'impact possible sur le processeur.
Au niveau de l'application, les E / S asynchrones évitent aux threads d'attendre la fin des opérations d'E / S. Dès qu'une opération d'E / S asynchrone est lancée, elle libère le thread sur lequel elle a été lancée et un rappel est enregistré. Une fois l'opération terminée, le rappel est mis en file d'attente pour exécution sur le premier thread disponible.
Si l'opération d'E / S est exécutée de manière synchrone, elle maintient son thread en cours d'exécution sans rien faire tant que l'opération n'est pas terminée. Le runtime ne sait pas quand l'opération d'E / S se termine, donc il fournira périodiquement du temps CPU au thread en attente, temps CPU qui aurait pu autrement être utilisé par d'autres threads qui ont des opérations liées au CPU à effectuer.
Ainsi, comme @ user1629468 l'a mentionné, les E / S asynchrones n'offrent pas de meilleures performances mais plutôt une meilleure évolutivité. Cela est évident lors de l'exécution dans des contextes qui ont un nombre limité de threads disponibles, comme c'est le cas avec les applications Web. Les applications Web utilisent généralement un pool de threads à partir duquel elles attribuent des threads à chaque requête. Si les demandes sont bloquées lors d'opérations d'E / S de longue durée, il existe un risque d'épuisement du pool Web et de blocage de l'application Web ou de ralentissement de la réponse.
Une chose que j'ai remarquée est que les E / S asynchrones ne sont pas la meilleure option lorsqu'il s'agit d'opérations d'E / S très rapides. Dans ce cas, l'avantage de ne pas garder un thread occupé pendant l'attente de la fin de l'opération d'E / S n'est pas très important et le fait que l'opération soit lancée sur un thread et terminée sur un autre ajoute une surcharge à l'exécution globale.
Vous pouvez lire une recherche plus détaillée que j'ai récemment effectuée sur le sujet des E / S asynchrones et du multithreading ici .
la source
La principale raison d'utiliser AIO est l'évolutivité. Considérés dans le contexte de quelques fils, les avantages ne sont pas évidents. Mais lorsque le système passe à des milliers de threads, AIO offrira de bien meilleures performances. La mise en garde est que la bibliothèque AIO ne doit pas introduire d'autres goulots d'étranglement.
la source
Pour présumer une amélioration de la vitesse due à toute forme de multi-calcul, vous devez présumer soit que plusieurs tâches basées sur le processeur sont exécutées simultanément sur plusieurs ressources informatiques (généralement des cœurs de processeur), soit que toutes les tâches ne reposent pas sur l'utilisation simultanée de la même ressource - c'est-à-dire que certaines tâches peuvent dépendre d'un sous-composant système (stockage sur disque, par exemple) tandis que certaines tâches dépendent d'un autre (réception de la communication d'un périphérique) et d'autres encore peuvent nécessiter l'utilisation de cœurs de processeur.
Le premier scénario est souvent appelé programmation «parallèle». Le second scénario est souvent appelé programmation «simultanée» ou «asynchrone», bien que «simultanée» soit parfois également utilisée pour désigner le cas de la simple autorisation d'un système d'exploitation à entrelacer l'exécution de plusieurs tâches, que cette exécution doive ou non prendre place en série ou si plusieurs ressources peuvent être utilisées pour réaliser une exécution parallèle. Dans ce dernier cas, «concurrent» fait généralement référence à la manière dont l'exécution est écrite dans le programme, plutôt que du point de vue de la simultanéité réelle de l'exécution de la tâche.
Il est très facile de parler de tout cela avec des hypothèses tacites. Par exemple, certains sont prompts à faire une réclamation telle que «Les E / S asynchrones seront plus rapides que les E / S multi-thread». Cette affirmation est douteuse pour plusieurs raisons. Premièrement, il se peut que certains frameworks d'E / S asynchrones soient implémentés précisément avec le multi-threading, auquel cas ils sont un dans le même et cela n'a pas de sens de dire qu'un concept "est plus rapide que" l'autre .
Deuxièmement, même dans le cas où il existe une implémentation à un seul thread d'un framework asynchrone (comme une boucle d'événements à un seul thread), vous devez toujours faire une hypothèse sur ce que fait cette boucle. Par exemple, une chose stupide que vous pouvez faire avec une boucle d'événement à thread unique est de lui demander d'effectuer de manière asynchrone deux tâches différentes purement liées au processeur. Si vous avez fait cela sur une machine avec seulement un cœur de processeur unique idéalisé (en ignorant les optimisations matérielles modernes), l'exécution de cette tâche "asynchrone" ne serait pas vraiment différente de celle avec deux threads gérés indépendamment, ou avec un seul processus - - la différence peut résulter du changement de contexte de thread ou des optimisations de planification du système d'exploitation, mais si les deux tâches vont au CPU, ce serait similaire dans les deux cas.
Il est utile d'imaginer un grand nombre de cas inhabituels ou stupides que vous pourriez rencontrer.
"Asynchrone" ne doit pas nécessairement être simultané, par exemple comme ci-dessus: vous exécutez "de manière asynchrone" deux tâches liées au processeur sur une machine avec exactement un cœur de processeur.
L'exécution multithread n'a pas besoin d'être simultanée: vous créez deux threads sur une machine avec un seul cœur de processeur, ou demandez à deux threads d'acquérir tout autre type de ressource rare (imaginez, par exemple, une base de données réseau qui ne peut en établir qu'une connexion à la fois). L'exécution des threads peut être entrelacée mais le planificateur du système d'exploitation le juge opportun, mais leur durée d'exécution totale ne peut pas être réduite (et sera augmentée à partir du changement de contexte de thread) sur un seul cœur (ou plus généralement, si vous créez plus de threads qu'il n'y en a cœurs pour les exécuter, ou avoir plus de threads demandant une ressource que ce que la ressource peut supporter). Il en va de même pour le multi-traitement.
Ainsi, ni les E / S asynchrones ni le multi-threading ne doivent offrir de gain de performances en termes de temps d'exécution. Ils peuvent même ralentir les choses.
Cependant, si vous définissez un cas d'utilisation spécifique, comme un programme spécifique qui effectue à la fois un appel réseau pour récupérer des données à partir d'une ressource connectée au réseau comme une base de données distante et effectue également des calculs locaux liés au processeur, vous pouvez commencer à raisonner les différences de performances entre les deux méthodes étant donné une hypothèse particulière sur le matériel.
Les questions à se poser: Combien d'étapes de calcul dois-je effectuer et combien de systèmes de ressources indépendants existe-t-il pour les exécuter? Existe-t-il des sous-ensembles d'étapes de calcul qui nécessitent l'utilisation de sous-composants système indépendants et qui peuvent en bénéficier simultanément? Combien de cœurs de processeur ai-je et quelle est la surcharge liée à l'utilisation de plusieurs processeurs ou threads pour effectuer des tâches sur des cœurs séparés?
Si vos tâches reposent largement sur des sous-systèmes indépendants, une solution asynchrone peut être bonne. Si le nombre de threads nécessaires pour le gérer était important, de sorte que le changement de contexte devenait non trivial pour le système d'exploitation, alors une solution asynchrone à thread unique pourrait être meilleure.
Chaque fois que les tâches sont liées par la même ressource (par exemple, plusieurs besoins pour accéder simultanément au même réseau ou à la même ressource locale), le multi-threading introduira probablement une surcharge insatisfaisante, et tandis que l'asynchronie monothread peut introduire moins de surcharge, dans une telle ressource- situation limitée, il ne peut pas non plus produire une accélération. Dans un tel cas, la seule option (si vous voulez une accélération) est de rendre plusieurs copies de cette ressource disponibles (par exemple, plusieurs cœurs de processeur si la ressource rare est le processeur; une meilleure base de données qui prend en charge plus de connexions simultanées si la ressource rare est une base de données à connexion limitée, etc.).
Une autre façon de le dire est: permettre au système d'exploitation d'entrelacer l'utilisation d'une seule ressource pour deux tâches ne peut pas être plus rapide que de simplement laisser une tâche utiliser la ressource pendant que l'autre attend, puis de laisser la deuxième tâche se terminer en série. En outre, le coût de l'entrelacement par l'ordonnanceur signifie que dans toute situation réelle, il crée en fait un ralentissement. Peu importe si l'utilisation entrelacée se produit du processeur, d'une ressource réseau, d'une ressource mémoire, d'un périphérique ou de toute autre ressource système.
la source
Une implémentation possible d'E / S non bloquantes est exactement ce que vous avez dit, avec un pool de threads d'arrière-plan qui bloquent les E / S et notifient le thread de l'expéditeur de l'E / S via un mécanisme de rappel. En fait, c'est ainsi que fonctionne le module AIO de la glibc. Voici quelques détails vagues sur la mise en œuvre.
Bien que ce soit une bonne solution qui soit assez portable (tant que vous avez des threads), le système d'exploitation est généralement capable de desservir plus efficacement les E / S non bloquantes. Cet article de Wikipedia répertorie les implémentations possibles en plus du pool de threads.
la source
Je suis actuellement en train d'implémenter async io sur une plateforme embarquée utilisant des protothreads. Non bloquant io fait la différence entre fonctionner à 16000fps et 160fps. Le plus grand avantage de non bloquant io est que vous pouvez structurer votre code pour faire d'autres choses pendant que le matériel fait son travail. Même l'initialisation des appareils peut être effectuée en parallèle.
Martin
la source
Dans Node, plusieurs threads sont en cours de lancement, mais il s'agit d'une couche inférieure à l'exécution C ++.
https://codeburst.io/how-node-js-single-thread-mechanism-work-understanding-event-loop-in-nodejs-230f7440b0ea
https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98
L'explication "Node est plus rapide car non bloquant ..." est un peu marketing et c'est une excellente question. C'est efficace et évolutif, mais pas exactement avec un seul thread.
la source
L'amélioration pour autant que je sais est que les utilisations entrées / sorties asynchrones (je parle de MS système, juste pour préciser) que l'on appelle E / S ports d'achèvement . En utilisant l'appel asynchrone, le framework exploite automatiquement cette architecture, ce qui est censé être beaucoup plus efficace que le mécanisme de threading standard. En tant qu'expérience personnelle, je peux dire que vous sentirez sensiblement votre application plus réactive si vous préférez AsyncCalls au lieu de bloquer les threads.
la source
Permettez-moi de vous donner un contre-exemple selon lequel les E / S asynchrones ne fonctionnent pas. J'écris un proxy similaire à ci-dessous en utilisant boost :: asio. https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp
Cependant, le scénario de mon cas est que les messages entrants (du côté des clients) sont rapides tandis que les messages sortants (du côté du serveur) sont lents pendant une session, pour suivre la vitesse entrante ou pour maximiser le débit total du proxy, nous devons utiliser plusieurs sessions sous une seule connexion.
Ainsi, ce cadre d'E / S asynchrone ne fonctionne plus. Nous avons besoin d'un pool de threads à envoyer au serveur en attribuant à chaque thread une session.
la source