Que signifient les termes "CPU lié" et "I/O lié"?
C'est assez intuitif:
Un programme est lié au CPU s'il irait plus vite si le CPU était plus rapide, c'est-à-dire qu'il passe la majorité de son temps simplement à utiliser le CPU (à effectuer des calculs). Un programme qui calcule de nouveaux chiffres de π sera généralement lié au processeur, il ne fait que tordre les chiffres.
Un programme est lié E/S s'il irait plus vite si le sous-système E/S était plus rapide. Le système d’E/S exact visé peut varier; Je l'associe généralement à un disque, mais bien entendu, la mise en réseau ou la communication est également courante. Un programme qui recherche dans un fichier volumineux certaines données peut devenir lié aux E/S, car le goulot d'étranglement est alors la lecture des données à partir du disque (en fait, cet exemple est peut-être un peu démodé de nos jours avec des centaines de Mo/s venant de SSD).
CPU Bound signifie que la vitesse à laquelle le processus progresse est limitée par la vitesse de la CPU. Une tâche qui effectue des calculs sur un petit ensemble de nombres, par exemple en multipliant de petites matrices, sera probablement liée au processeur.
Liaison E/S signifie que la vitesse à laquelle un processus progresse est limitée par la vitesse du sous-système E/S. Une tâche qui traite les données du disque, par exemple, en comptant le nombre de lignes dans un fichier, est susceptible d'être liée à l'entrée/sortie.
Lié à la mémoire signifie que la vitesse à laquelle un processus progresse est limitée par la quantité de mémoire disponible et la vitesse de cet accès à la mémoire. Une tâche qui traite de grandes quantités de données en mémoire, par exemple en multipliant des matrices volumineuses, sera probablement liée à la mémoire.
Cache lié désigne le taux auquel la progression d'un processus est limitée par la quantité et la vitesse du cache disponible. Une tâche qui traite simplement plus de données que ce qui est contenu dans le cache sera liée au cache.
I/O Bound serait plus lent que Memory Bound, plus lent que Cache Bound, plus lent que CPU Bound.
La solution pour être lié aux E/S n'est pas nécessairement d'obtenir plus de mémoire. Dans certaines situations, l’algorithme d’accès pourrait être conçu en fonction des limitations d’E/S, de mémoire ou de cache. Voir Cache Oblivious Algorithms .
Multi-threading
Dans cette réponse, je vais étudier un cas d'utilisation important qui consiste à distinguer le CPU du travail borné IO: lors de l'écriture de code multithread.
Exemple lié à la RAM I/O: somme de vecteur
Considérons un programme qui additionne toutes les valeurs d'un seul vecteur:
#define SIZE 1000000000
unsigned int is[SIZE];
unsigned int sum = 0;
size_t i = 0;
for (i = 0; i < SIZE; i++)
/* Each one of those requires a RAM access! */
sum += is[i]
Mettre en parallèle le fait de diviser le tableau de manière égale pour chacun de vos cœurs n’est d’une utilité limitée que sur les ordinateurs de bureau modernes.
Par exemple, sur mon Ubuntu 19.04, mon ordinateur portable Lenovo ThinkPad P51 avec processeur: Processeur Intel Core i7-7820HQ (4 cœurs/8 threads), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16 Go), j'obtiens des résultats comme celui-ci:
Notez qu'il y a cependant beaucoup de variance entre les exécutions. Mais je ne peux pas augmenter la taille du tableau beaucoup plus loin puisque je suis déjà à 8GiB, et je ne suis pas d'humeur pour les statistiques sur plusieurs exécutions aujourd'hui. Cela semblait cependant être une course typique après de nombreuses courses manuelles.
Code de référence:
code source POSIX C pthread
utilisé dans le graphique.
Et voici un version C++ qui produit des résultats analogues.
Je ne connais pas assez d'architecture informatique pour expliquer complètement la forme de la courbe, mais une chose est claire: le calcul ne devient pas 8 fois plus rapide que prévu naïvement, car j'utilise tous mes 8 fils! Pour une raison quelconque, 2/3 threads était l’optimum, et ajouter plus rend les choses beaucoup plus lentes.
Comparez cela au travail lié au processeur, qui est en réalité 8 fois plus rapide: Que signifient les expressions "réel", "utilisateur" et "sys" dans la sortie du temps (1)?
La raison pour laquelle tous les processeurs partagent un seul bus de mémoire en liaison avec la RAM:
CPU 1 --\ Bus +-----+
CPU 2 ---\__________| RAM |
... ---/ +-----+
CPU N --/
de sorte que le bus de mémoire devient rapidement le goulot d'étranglement, pas le processeur.
Cela est dû au fait que l’ajout de deux nombres prend un seul cycle de processeur et que les lectures en mémoire prennent environ 100 cycles de processeur en 2016.
Le travail de la CPU par octet de données d'entrée est donc trop petit et nous appelons cela un processus lié à l'IO.
Le seul moyen d’accélérer davantage ce calcul serait d’accélérer les accès individuels à la mémoire avec un nouveau matériel de mémoire, par ex. mémoire multicanal .
Passer par exemple à une horloge plus rapide ne serait pas très utile.
Autres exemples
la multiplication de matrice est liée au processeur sur RAM et aux GPU. L'entrée contient:
2 * N**2
chiffres, mais:
N ** 3
des multiplications sont effectuées, et cela suffit pour que la parallélisation en vaille la peine pour un grand N. pratique.
C'est pourquoi il existe des bibliothèques de multiplication de matrices de processeurs parallèles telles que:
L'utilisation du cache fait une grande différence en termes de rapidité d'implémentation. Voir par exemple this exemple de comparaison de GPU didactique .
Les GPU présentent un goulot d'étranglement IO lors du transfert de données vers la CPU.
Ils sont conçus pour que la sortie de rendu (un rectangle de pixels) puisse être directement émise vers la mémoire vidéo, afin d’éviter les allers-retours du processeur.
La mise en réseau est l'exemple type lié aux entrées-sorties.
Même lorsque nous envoyons un seul octet de données, il faut encore beaucoup de temps pour atteindre sa destination.
La mise en parallèle de petites requêtes réseau telles que les requêtes HTTP peut offrir des gains de performances énormes.
Si le réseau est déjà à pleine capacité (par exemple, en téléchargeant un torrent), la parallélisation peut encore augmenter et améliorer la latence (par exemple, vous pouvez charger une page Web "en même temps").
Une opération factice liée au processeur C++ qui prend un nombre et le crunch beaucoup:
Comment savoir si vous êtes CPU ou IO lié
Non-RAM IO lié comme un disque, réseau: ps aux
, puis vérifie si CPU% / 100 < n threads
. Si oui, vous êtes IO lié, par exemple. blocage read
s n'attendent que des données et le planificateur ignore ce processus. Ensuite, utilisez des outils supplémentaires tels que Sudo iotop
pour décider quel IO est exactement le problème.
Ou, si l'exécution est rapide et que vous paramétrez le nombre de threads, vous pouvez facilement constater que time
améliore les performances à mesure que le nombre de threads augmente pour le travail lié au processeur: Qu'est-ce que 'real', 'utilisateur' et 'sys' signifient dans la sortie du temps (1)?
RAM-IO lié: difficile à dire, car RAM temps d'attente est inclus dans les mesures CPU%
. Le mieux que vous puissiez faire est peut-être d'estimer les erreurs de cache.
Voir également:
Verrouillage global d'intepreter de CPython (GIL)
En guise d’étude de cas rapide, je voudrais signaler le Python Global Interpreter Lock (GIL): Qu'est-ce que le verrouillage d'interprète global (GIL) dans CPython?
Les détails de cette implémentation CPython empêchent plusieurs threads Python d'utiliser efficacement le travail lié au processeur. Les documents CPython disent:
Détail de l'implémentation de CPython: sous CPython, en raison du verrou d'interprète global, un seul thread peut exécuter le code Python à la fois (même si certaines bibliothèques orientées sur les performances peuvent dépasser cette limitation). Si vous souhaitez que votre application optimise l'utilisation des ressources de calcul des machines multicœurs, il est conseillé d'utiliser
multiprocessing
ouconcurrent.futures.ProcessPoolExecutor
. Toutefois, le threading reste un modèle approprié si vous souhaitez exécuter plusieurs tâches liées aux E/S simultanément.
Par conséquent, nous avons ici un exemple où le contenu lié à la CPU ne convient pas et le contenu lié aux E/S l'est.
CPU lié signifie que le programme est goulot d’étranglement par la CPU ou l’unité centrale de traitement, alors que I/O lié signifie que le programme est goulot d’étranglement par entrée/sortie, tel que la lecture ou l’écriture sur le disque. , réseau, etc.
En général, lors de l'optimisation des programmes informatiques, on essaie de trouver le goulot d'étranglement et de l'éliminer. Le fait de savoir que votre programme est lié à la CPU aide à ne pas optimiser inutilement autre chose.
[Et par "goulot d'étranglement", je veux dire la chose qui rend votre programme plus lent qu'il ne l'aurait fait autrement.]
Une autre façon de formuler la même idée:
Si l'accélération du processeur n'accélère pas votre programme, il est possible que I/O .
Si l’accélération des E/S (par exemple, l’utilisation d’un disque plus rapide) n’aide pas, votre programme est peut-être lié au processeur.
(J'ai utilisé "peut-être" parce que vous devez prendre en compte d'autres ressources. La mémoire en est un exemple.)
Lorsque votre programme attend I/O (c'est-à-dire une lecture/écriture sur disque ou une lecture/écriture sur réseau, etc.), la CPU est libre d'effectuer d'autres tâches, même si votre programme est arrêté. La vitesse de votre programme dépendra principalement de la vitesse à laquelle IO peut se produire, et si vous souhaitez l'accélérer, vous devez accélérer les E/S.
Si votre programme exécute beaucoup d'instructions de programme et n'attend pas d'entrées/sorties, il est dit qu'il est lié à la CPU. L'accélération du processeur accélérera l'exécution du programme.
Dans les deux cas, la clé pour accélérer le programme n'est peut-être pas d'accélérer le matériel, mais d'optimiser le programme afin de réduire la quantité de IO ou de la CPU dont il a besoin, ou de le faire faire d'E/S alors qu'il fait aussi des trucs gourmands en ressources CPU.
I/O lié fait référence à une condition dans laquelle le temps nécessaire pour effectuer un calcul est déterminé principalement par la période d'attente pour que les opérations d'entrée/sortie soient terminées.
C'est le contraire d'une tâche liée au processeur. Cette situation survient lorsque le débit auquel les données sont demandées est plus lent que le débit auquel elles sont consommées ou, en d'autres termes, que l'on passe plus de temps à demander des données qu'à les traiter.
Processus liés à l'IO: passez plus de temps à effectuer la tâche IO que les calculs, utilisez de nombreuses rafales de temps CPU. Processus liés au processeur: passez plus de temps à faire des calculs, quelques très longues rafales de processeur
Processus lié aux entrées/sorties: - Si la majeure partie de la durée de vie d'un processus est dépensée dans un état d'entrée/sortie, il s'agit alors d'un processus lié aux entrées/sorties. Exemple: -calculator, Internet Explorer
Processus lié au CPU: - Si la majeure partie de la vie du processus est dépensée en CPU, il s'agit d'un processus lié au CPU.