tag:linuxfr.org,2005:/tags/haute_performance/public
LinuxFr.org : les contenus étiquetés avec « haute_performance »
2021-09-30T20:48:52+02:00
/favicon.png
tag:linuxfr.org,2005:Bookmark/3649
2021-09-29T10:10:26+02:00
2021-09-29T10:10:26+02:00
Formation Fortran & calcul scientifique
<a href="https://www.futurelearn.com/courses/fortran-for-scientific-computing">https://www.futurelearn.com/courses/fortran-for-scientific-computing</a> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/125565/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/e3ms6vyx/liens/formation-fortran-calcul-scientifique#comments">ouvrir dans le navigateur</a>
</p>
E3Ms6vyX
https://linuxfr.org/nodes/125565/comments.atom
tag:linuxfr.org,2005:News/38265
2017-10-28T09:25:37+02:00
2017-10-28T17:29:26+02:00
Sortie de gfast-copy et de fast-copy sur www.open-source-projects.net
Licence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr
<div><p><img src="//img.linuxfr.org/img/687474703a2f2f7777772e6f70656e2d736f757263652d70726f6a656374732e6e65742f67666173742d636f70792f49636f6e2f67666173742d636f70795f69636f6e2e706e67/gfast-copy_icon.png" alt="Icône de gfast-copy" title="Source : http://www.open-source-projects.net/gfast-copy/Icon/gfast-copy_icon.png"></p>
<p>Copier un unique (gros) fichier d’une source vers une destination donnée n’est pas une tâche pour laquelle les systèmes sont toujours optimisés.</p>
<p>Selon la philosophie UNIX, un programme doit accomplir une unique tâche précise et l’accomplir du mieux possible. C’est ce qui a donné naissance à <em>gfast-copy</em> et à <a href="http://www.open-source-projects.net/gfast-copy/gfast-copy_presentation.html"><em>fast-copy</em></a>. Ces programmes font une chose banale mais le font bien.</p></div><ul><li>lien nᵒ 1 : <a title="http://www.open-source-projects.net/gfast-copy/Download/py/download_gfast-copy_deb.cgi" hreflang="en" href="https://linuxfr.org/redirect/100860">Télécharger le paquetage Debian de gfast-copy</a></li><li>lien nᵒ 2 : <a title="http://www.open-source-projects.net/gfast-copy/Download/py/download_gfast-copy_rpm.cgi" hreflang="en" href="https://linuxfr.org/redirect/100861">Télécharger le paquetage RPM de gfast-copy</a></li><li>lien nᵒ 3 : <a title="http://www.open-source-projects.net/gfast-copy/Download/py/download_gfast-copy_exe.cgi" hreflang="en" href="https://linuxfr.org/redirect/100862">Télécharger la version Windows de gfast-copy</a></li><li>lien nᵒ 4 : <a title="http://www.open-source-projects.net/gfast-copy/Download/py/download_gfast-copy_tarball.cgi" hreflang="en" href="https://linuxfr.org/redirect/100863">Télécharger l’archive tar de gfast-copy</a></li><li>lien nᵒ 5 : <a title="http://www.open-source-projects.net/gfast-copy/Notice/Online/index.html" hreflang="en" href="https://linuxfr.org/redirect/100864">README de gfast-copy.</a></li><li>lien nᵒ 6 : <a title="https://github.com/mrcyberfighter/gfast-copy" hreflang="fr" href="https://linuxfr.org/redirect/100865">GitHub de gfast-copy</a></li></ul><div><p>J’ai développé ces deux programmes car je copie souvent de gros fichiers (vidéos, images ISO, grosses archives, etc.) de « droite à gauche » et de « gauche à droite ». Et je trouvais que les systèmes d’exploitation copiaient trop lentement, parce qu’ils faisaient parallèlement autre chose, alors j’ai décidé de déléguer cette tâche à un binaire.</p>
<p>J’ai lu dans les <a href="https://www.gnu.org/manual/manual.fr.html">manuels GNU</a> qu’un programme qui dispose d’une interface graphique doit pouvoir accomplir la même tâche dans un (pseudo) terminal, alors j’ai écrit deux petits programmes en langage <em>C</em> : <strong>gfast-copy</strong> (<strong>G</strong>raphical <strong>F</strong>ast <strong>C</strong>opy) et <strong>fast-copy</strong> (<strong>F</strong>ast <strong>C</strong>opy).</p>
<h2 id="gfast-copy-graphical-fast-copy">gfast-copy (Graphical Fast Copy).</h2>
<p><strong>gfast-copy</strong> dispose d’une interface graphique <strong>simple</strong> :<br><img src="//img.linuxfr.org/img/687474703a2f2f7777772e6f70656e2d736f757263652d70726f6a656374732e6e65742f67666173742d636f70792f53637265656e73686f74732f67666173742d636f70795f474e555f4c696e75785f6d61696e5f696e746572666163655f6461726b5f7468656d655f65726173655f7372635f66696c655f6f6e2e706e67/gfast-copy_GNU_Linux_main_interface_dark_theme_erase_src_file_on.png" alt="Capture d’écran de gfast-copy" title="Source : http://www.open-source-projects.net/gfast-copy/Screenshots/gfast-copy_GNU_Linux_main_interface_dark_theme_erase_src_file_on.png"></p>
<p>Tout en haut, se trouve une barre de menu. Puis, alignés verticalement, viennent :</p>
<ul>
<li>un bouton étiqueté <em>Source</em> avec une icône qui vous permet de choisir le fichier à copier ;</li>
<li>à côté, se trouve un bouton interrupteur affichant une simple icône permettant d’effacer le fichier source ou pas après la copie ;</li>
<li>un bouton étiqueté <em>Destination</em> avec une icône vous permettant de choisir le l’emplacement et le nom du fichier de sortie (vous pouvez écraser un fichier).</li>
<li>à côté se trouve un bouton permettant de lancer la copie ;</li>
<li>et, en bas, une barre de progression affichant la progression rapide de la copie.</li>
</ul><h2 id="fast-copy-fastcopy">fast-copy (Fast Copy)</h2>
<p><strong>fast-copy</strong> est un outil en ligne de commande permettant de faire la même chose que <strong>gfast-copy</strong> mais depuis un terminal et, du coup, plus rapidement.<br><img src="//img.linuxfr.org/img/687474703a2f2f7777772e6f70656e2d736f757263652d70726f6a656374732e6e65742f67666173742d636f70792f53637265656e73686f74732f666173742d636f70795f474e555f4c696e75785f70726f63657373696e675f636f7079696e675f6f7065726174696f6e2e706e67/fast-copy_GNU_Linux_processing_copying_operation.png" alt="Capture d’écran de fast-copy" title="Source : http://www.open-source-projects.net/gfast-copy/Screenshots/fast-copy_GNU_Linux_processing_copying_operation.png"></p>
<p>Ce programme permet aussi:</p>
<ul>
<li>d’écraser la destination ;</li>
<li>d’effacer le fichier source après la copie ;</li>
<li>d’utiliser les appels système au lieu des flux (comportement par défaut).</li>
</ul><pre><code class="bash">$ fast-copy -h
fast-copy - a fast chunk file copy program.
Usage : fast-copy -r input-file -w output-file <span class="o">[</span>-o<span class="o">]</span> <span class="o">[</span>-s<span class="o">]</span> <span class="o">[</span>-e<span class="o">]</span> <span class="o">[</span>-h<span class="o">]</span>
-r Read from file <span class="o">(</span>required<span class="o">)</span>.
-w Write to file <span class="o">(</span>required<span class="o">)</span>.
-o Overwrite destination file <span class="o">(</span>optional<span class="o">)</span>.
-e Erase <span class="nb">source</span> file <span class="o">(</span>optional<span class="o">)</span>.
-s Use syscalls instead of streams <span class="o">(</span>optional only UNIX<span class="o">)</span>.
-h Print this <span class="nb">help</span> message.
- Copyright <span class="o">(</span>©<span class="o">)</span> <span class="m">2017</span> Brüggemann Eddie <mrcyberfighter@gmail.com> GPLv3.</code></pre>
<h3 id="algorithme-de-copie">Algorithme de copie.</h3>
<ol>
<li>Le programme cherche d’abord la taille optimale de tampon :
<ul>
<li>soit en regardant si la constante <code>BUFSIZ</code> est définie et si elle est assez grande,</li>
<li>sinon, la taille du tampon est mise à <code>8192</code> octets,</li>
<li>si le programme utilise les appels système, il va regarder la taille optimale du tampon dans le système de fichiers ;</li>
</ul>
</li>
<li>le programme définit un tampon de la taille optimale ;</li>
<li>le programme va copier dans une boucle très rapide la source vers la destination affichant une barre de progression ;</li>
<li>une fois la copie effectuée avec succès le programme va mettre à jour le système de fichiers ;</li>
<li>si vous le désirez le programme va supprimer le fichier source.</li>
</ol><h2 id="compatibilité-et-portages">Compatibilité et portages</h2>
<p>Les programmes ne requièrent que GTK+ en version supérieure ou égale à 3.14 (<strong>fast-copy</strong> et ne font qu’un petit usage de <a href="https://fr.wikipedia.org/wiki/GLib">GLib</a> et de <a href="https://developer.gnome.org/gio/stable/">Gio</a>).</p>
<p>Les programmes existent pour plusieurs systèmes d’exploitation :</p>
<h3 id="gnulinux">GNU/Linux</h3>
<ul>
<li>un paquetage <code>*.deb</code> ;</li>
<li>un paquetage <code>*.rpm</code> ;</li>
<li>un <code>tarball</code> basé sur les <em>autotools</em>.</li>
</ul><h3 id="windows">Windows</h3>
<ul>
<li>un fichier <code>*.exe</code> auto‐extractible ne comprenant que <strong>gfast-copy</strong> ;</li>
<li>le <code>tarball</code> permet de compiler avec <strong>MSYS2</strong> et <strong>Cygwin</strong> (les dernières versions en date d’aujourd’hui).</li>
</ul><h3 id="macos-sierra1012">macOS (Sierra 10.12)</h3>
<p>Le <strong>tarball</strong> permet de compiler les programmes et vous crée une icône dans le dossier <code>/Applications</code>.</p>
<p><em><strong>Note :</strong> Vous pourrez télécharger GTK 3 grâce à <strong>brew</strong> et suivre les instructions d’installation.</em></p>
<h2 id="notes-de-lauteur">Notes de l’auteur</h2>
<p>N’utilisez ces deux programmes que pour copier de gros fichiers. Pour les autres, votre système d’exploitation s’en occupe très bien.</p>
<p>Si vous préférez la rapidité de votre système d’exploitation alors n’utilisez pas ces programmes.</p>
<blockquote>
<p><strong><em>Pourquoi ces programmes ne permettent pas de copier plusieurs fichiers à la fois <br>
(surtout fast-copy) ?</em></strong></p>
<p><em>Car il existe des outils pour cela et le récursivité des chemins de fichiers de destination n’est pas une chose facile.</em></p>
</blockquote>
<p>Car, comme je l’ai dit, je les ai développés à des fins personnelles mais je désire les partager.</p>
<p><em><strong>Note :</strong> J’ai sûrement péché d’avoir mis une option pour chaque chemin de fichier, mais regardez la notice, vous trouverez un exemple de « wrapper » de <strong>fast-copy</strong> très pratique.</em></p>
<h2 id="performances">Performances</h2>
<p>Cela dépend de plusieurs facteurs : le système d’exploitation sur lequel est utilisé le programme, la charge de la machine et, bien sûr, la nature des sources et cibles de l’opération de copie :</p>
<ul>
<li>disque dur interne vers un disque dur interne ;</li>
<li>disque dur interne vers un disque dur externe ;</li>
<li>disque dur externe vers un disque dur interne.</li>
</ul></div><div><a href="https://linuxfr.org/news/sortie-de-gfast-copy-et-de-fast-copy-sur-www-open-source-projects-net.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/112949/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/sortie-de-gfast-copy-et-de-fast-copy-sur-www-open-source-projects-net#comments">ouvrir dans le navigateur</a>
</p>
Linuxator
Davy Defaud
Pierre Jarillon
Xavier Teyssier
palm123
claudex
https://linuxfr.org/nodes/112949/comments.atom
tag:linuxfr.org,2005:Post/36992
2016-06-22T15:08:00+02:00
2016-06-22T15:08:00+02:00
Optimisation d'un serveur de base de données (PG)
<p>Bonjour tout le monde,</p>
<p>Je gère un cluster de calcul monté comme suit :<br>
-2 pfSense inter-connectés en entrée, filtrant les utilisateurs sur leurs adresses IP,<br>
-3 serveurs faisant tourner Proxmox: 2 grosses babasses (1 HP DL360G7 et 1 HP DL360G8, 24 coeurs chacun, 192Go RAM chacun) + 1 ch'ti qui sert juste pour faire le quorum de Proxmox,<br>
-1 SAN de 12To pour poser les machines virtuelles<br>
-1 NAS pour les backups des VMs</p>
<p>Pour l'instant les utilisateurs (une soixantaine) sont répartis en groupes, chaque groupe ayant une base de donnée PostgreSQL où sont stockés des tables (…) et des fonctions qui lorsqu'elles sont exécutées, brassent de grosses quantités de données (plusieurs dizaine de millions d'enregistrements). Toutes ces bases de données sont gérées par un seul serveur PostgreSQL (9.4) qui tourne sur une seule VM de 2To (configurée en haute dispo dans Proxmox). De plus chaque utilisateur peut se connecter en SSH sur son répertoire personnel, et à un répertoire partagé entre tous les utilisateurs. L'authentification se fait via LDAP. Tous les utilisateurs se connectent depuis l'extérieur.<br>
D'autres Vms tournent aussi sur le cluster, mais elles sont insignifiantes ici.</p>
<p>Les problèmes :<br>
1- Lorsque un utilisateur, membre d'un groupe, se connecte au serveur de base de données, il peut avoir un listing de toutes les bases existantes sur le serveur. Il ne peut bien sûr pas y accéder à cause des règles d'accès, mais il peut voir qui est connecté. Niveau confidentialité c'est pas ça.<br>
2- Lorsqu'un utilisateur lance une fonction, il y a un impact sur les temps de réponse des autres bases puisque les CPU et la RAM sont partagés.<br>
3- La VM où tourne le serveur de base de données n'utilise la puissance que d'une seule des 2 babasses, l'autre étant au repos pour récupèrer la VM au cas où le premier serveur plante (HA).</p>
<p>Du coup, je me demande (et c'est le but de mon post ici) qu'elle est la meilleure solution pour palier à tous ça. Pour l'instant j'imagine passer tout ce petit monde dans des VM par groupe d'utilisateurs, et de répartir ces VMs équitablement sur les 2 serveurs.<br>
Est-ce que ça serait plus pertinent d'un point de vue performance/facilité d'utiliser des conteneurs (OpenVZ car Proxmox 3.4, sinon upgrade en 4.0 pour du LXC)? Sinon Docker? Ou monter une plate-forme OpenStack, mais jamais fait et la doc parle de plusieurs serveurs physiques?<br>
De plus, nous avons besoin de propager des patchs correctifs sur les bases de données, je me vois mal faire ça à la main sur chaque VM. J'imagine que des softs comme Puppet ou Chef pourraient aider, mais est-ce bien fait pour?<br>
Pareil pour la gestion des utilisateurs. Pour l'instant c'est relativement simple avec LDAP et Webmin qui propage les modifs sur l'unique machine, mais comment faire sur X machines?<br>
Après, il restera la question du routage, avec une unique adresse IP publique, il va falloir jouer avec les sous-domaines j'imagine.</p>
<p>Merci de votre aide</p><div><a href="https://linuxfr.org/forums/general-general/posts/optimisation-d-un-serveur-de-base-de-donnees-pg.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/109315/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/forums/general-general/posts/optimisation-d-un-serveur-de-base-de-donnees-pg#comments">ouvrir dans le navigateur</a>
</p>
goabbear
https://linuxfr.org/nodes/109315/comments.atom
tag:linuxfr.org,2005:News/34380
2013-07-05T09:52:38+02:00
2013-07-12T12:54:26+02:00
Où vont les supercalculateurs ? D’où on vient, quels sont les problèmes, où l’on va (1re partie)
Licence CC By‑SA http://creativecommons.org/licenses/by-sa/3.0/deed.fr
<div><p>Il y a un bail, j’avais dit que je voulais un jour parler des architectures haute performance, et de leur potentiel futur. Je me lance donc ici, en espérant que certains se permettront de me corriger là où j’aurai fait des erreurs (sans doute nombreuses).</p>
<p>Je vais diviser ces explications en trois parties. La première (qui suit juste après) va juste faire un rappel sur les architectures « séquentielles » de base. La deuxième partie (à venir très bientôt) s’occupera de décrire les systèmes multi‐processeurs et multi‐cœurs, ainsi que la raison de leur existence. J’en profiterai pour aussi expliquer les problèmes récurrents liés à l’exploitation de systèmes haute performance. La dernière partie parlera des efforts effectués en ce moment pour fabriquer les supercalculateurs du futur (disons à l’horizon 2020-2025).</p></div><ul><li>lien nᵒ 1 : <a title="http://www.cs.umd.edu/class/fall2001/cmsc411/projects/dynamic/scoreboard.html" hreflang="en" href="https://linuxfr.org/redirect/86997">Dynamic Scheduling (Scoreboard)</a></li><li>lien nᵒ 2 : <a title="https://en.wikipedia.org/wiki/Reduced_instruction_set_computing" hreflang="en" href="https://linuxfr.org/redirect/86998">Reduced Instruction Set Computing</a></li><li>lien nᵒ 3 : <a title="http://en.wikipedia.org/wiki/Branch_predictor" hreflang="en" href="https://linuxfr.org/redirect/86999">Branch Predictor</a></li><li>lien nᵒ 4 : <a title="http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html?wapkw=manual" hreflang="en" href="https://linuxfr.org/redirect/87000">Intel x86 and Intel 64 Manuals</a></li><li>lien nᵒ 5 : <a title="http://www.intel.com/content/www/us/en/processors/itanium/itanium-architecture-vol-1-2-3-4-reference-set-manual.html?wapkw=itanium+manual" hreflang="en" href="https://linuxfr.org/redirect/87001">Itanium Architecture Manual</a></li><li>lien nᵒ 6 : <a title="http://support.amd.com/us/Processor_TechDocs/24592_APM_v1.pdf" hreflang="en" href="https://linuxfr.org/redirect/87002">AMD64 Architecture Programmer’s Manual (vol 1)</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li>
<a href="#microarchitecture-de-base-dun-microprocesseur">Micro‐architecture de base d’un micro‐processeur</a><ul>
<li><a href="#le-risc-cest-bien">Le RISC, c’est bien</a></li>
<li><a href="#tout-%C3%A7a-fonctionne-impec-%C3%A0-une-condition">Tout ça fonctionne impec’, à une condition…</a></li>
<li><a href="#faitesmoi-le-plein-de-super">Faites‐moi le plein de super !</a></li>
<li><a href="#ce-dont-je-ne-parlerai-pas">Ce dont je ne parlerai pas</a></li>
<li>
<a href="#exploiter-la-localit%C3%A9-des-donn%C3%A9es">Exploiter la localité des données</a><ul>
<li><a href="#localit%C3%A9-temporelle">Localité temporelle</a></li>
<li><a href="#localit%C3%A9-spatiale">Localité spatiale</a></li>
</ul>
</li>
<li><a href="#sp%C3%A9culer-sur-le-chargement-des-donn%C3%A9es">Spéculer sur le chargement des données</a></li>
</ul>
</li>
<li><a href="#conclusions">Conclusions</a></li>
</ul><h2 id="microarchitecture-de-base-dun-microprocesseur">Micro‐architecture de base d’un micro‐processeur</h2>
<h3 id="le-risc-cest-bien">Le RISC, c’est bien</h3>
<p>La plupart des architectures qui nous intéressent sont soit de type <a href="http://fr.wikipedia.org/wiki/Reduced_instruction_set_computer" title="Reduced instruction set computer">RISC</a> (microprocesseur à jeu d’instructions réduit), soit de type <a href="http://fr.wikipedia.org/wiki/VLIW" title="Very Long Instruction Word">VLIW</a> (microprocesseur à mots d’instructions très longs). Bien qu’Intel garde (pour des raisons de compatibilité ascendante) un jeu d’instructions <a href="http://fr.wikipedia.org/wiki/Complex_instruction_set_computer" title="Complex Instruction Set Computer">CISC</a> (microprocesseur à jeu d’instructions étendu), en pratique, chaque instruction est ensuite décomposée en micro-instructions qui suivent les principes du RISC.</p>
<p>La plupart des processeurs traitent les données dans l’ordre qui suit :</p>
<p><em>Instruction Fetch</em> → <em>Instruction Decode</em> → <em>Execute</em> → <em>Memory</em> → <em>Write Back</em></p>
<p>En d’autres termes : on cherche la prochaine instruction à exécuter (Instruction Fetch, IF) ; puis on la décode (Instruction Decode, ID) ; on l’exécute (EX) ; si besoin on accède à la mémoire (Mem) ; et enfin, on écrit le résultat (soit en mémoire, soit dans les registres).</p>
<p>L’intérêt de ce découpage par étages (en pipeline) est qu’on peut récupérer la prochaine instruction à exécuter pendant qu’on décode celle qui a été récupérée au cycle précédent, et qu’on exécute celle qui est venue encore avant. Ainsi, l’exécution de 1 000 instructions dans un <em>pipeline</em> de longueur <em>n</em> va prendre 1000 + n instructions. Les cinq étages précédemment décrits sont les étages de base d’un processeur RISC. Certains processeurs ont eu jusqu’à 33 étages (comme le [[Pentium 4]], par exemple). De nouveaux étages peuvent être introduits pour la multiplication et l’addition entières, par exemple. Les unités de calcul flottant peuvent aussi nécessiter l’ajout d’étages additionnels.</p>
<p>Tout un tas d’optimisations existent à ce niveau (si je n’ai pas besoin d’accéder à la mémoire, par exemple, je peux simplement « sauter » l’étage mémoire du <em>pipeline</em>, pour aller directement écrire les résultats dans les registres).</p>
<p>Cette utilisation d’un <em>pipeline</em> est cruciale. Avant, l’utilisation de jeux d’instructions CISC était nécessaire pour deux raisons :</p>
<ol>
<li>La mémoire des machines étant extrêmement limitée, proposer un ensemble d’instructions qui permette d’effectuer plusieurs opérations en une permettait de gagner en concision lors de l’écriture du code, et de gagner de précieux octets dans le programme.</li>
<li>Il n’y avait pas réellement de <em>pipeline</em> comme expliqué précédemment. En revanche, chaque instruction CISC était optimisée pour prendre le moins de temps possible.</li>
</ol><h3 id="tout-ça-fonctionne-impec-à-une-condition">Tout ça fonctionne impec’, à une condition…</h3>
<p>L’approche RISC décrite ci‐dessus permet de plus ou moins garantir l’exécution d’une opération par cycle. Enfin, dans le meilleur des cas. En pratique, un programme a besoin de pouvoir emprunter des chemins différents en fonction des paramètres. Et donc, il doit être possible pour un programme de « sauter » dans une autre partie du programme (oui, on parle bien d’un <code>if</code> … <code>else</code> …).</p>
<p>Ici, l’exécution au niveau du <em>pipeline</em> est problématique, puisque le <code>if</code> nécessite de savoir si la condition évaluée est vraie, il faut donc :</p>
<ol>
<li>évaluer la condition ;</li>
<li>vérifier sa valeur de vérité (vrai / faux) ;</li>
<li>potentiellement effectuer le branchement vers la partie du programme concernée.</li>
</ol><p>Visiblement, le programme ne peut pas continuer à tourner tant que ces trois étapes ne sont pas accomplies. Et, du coup, le <em>pipeline</em> est « bloqué » <em>— « stalled »</em>, en anglais. Pas de panique, des gens plutôt malins ont trouvé une réponse à ceci : on va prédire quelle branche de la conditionnelle sera prise. On va appeler ça de la <a href="http://fr.wikipedia.org/wiki/pr%C3%A9diction%20de%20branchement" title="Définition Wikipédia">prédiction de branchement</a>, et le mécanisme qui la met en œuvre un prédicteur de branchement. Le fonctionnement est plutôt simple, et c’est bien pour ça qu’il est possible de mettre un mécanisme pareil directement dans le matériel. En gros, le prédicteur va faire un choix arbitraire au début, par exemple, tous les <code>if</code> sont prédits comme étant pris (c’est‐à‐dire que si l’on rencontre un <code>if</code>, on considère que la condition qu’il évalue sera considérée vraie par défaut).</p>
<p>Grâce à cela, on n’a pas besoin d’attendre que l’évaluation se termine : on continue l’exécution comme si de rien n’était. Évidemment, l’évaluation de la condition doit être terminée <em>avant</em> que les instructions qui suivent le bloc « <code>if</code> … <code>else</code> » qui ont été exécutées finissent par être réellement effectives.</p>
<p>Deux cas de figure se posent :</p>
<ol>
<li>la prédiction était juste, on n’a rien à faire, les instructions sont réellement effectuées <em>— « retired »</em>, en anglais ;</li>
<li>la prédiction était fausse, il faut purger l’intégralité des étages du <em>pipeline</em> qui suivent le branchement, puisque les résultats qui en découlent sont faux, eux aussi.</li>
</ol><p>Sans trop entrer dans les détails, les prédicteurs de branchement sont mis en œuvre grâce à une machine à états et une petite mémoire. Pour chaque instruction de branchement conditionnel, la mémoire se souvient de l’adresse du <code>if</code>, puis détermine si la condition sera « vraie », « plutôt vraie », « plutôt fausse », ou « fausse » (je simplifie un maximum, bien entendu). On part d’un extrême (vrai ou faux). Cette méthode (et ses améliorations) fonctionne plutôt bien, principalement pour deux raisons. La première est le fait que prédire que le branchement conditionnel d’une boucle est toujours pris est, en règle générale, payant (la prédiction est fausse uniquement si la boucle est terminée). La deuxième est que, très souvent, les « motifs » d’exécution <em>— « patterns »</em>, en anglais — sont similaires d’une exécution d’un programme à une autre. Par exemple, si un <code>if</code> est pris 7 fois sur 10 dans un programme, à cause de la nature du programme, le prédicteur de branchement, bien qu’agnostique en ce qui concerne le programme lui‐même, aura malgré tout fait gagner au final un temps précieux en termes d’exécution.</p>
<p>Il existe une autre façon de faire : la spéculation de contrôle, via l’utilisation de <em>prédicats</em>. C’est, par exemple, une approche qu’on retrouve dans les processeurs <a href="http://fr.wikipedia.org/wiki/Itanium" title="Définition Wikipédia">Itanium</a>, un processeur superscalaire et VLIW produit par <a href="http://fr.wikipedia.org/wiki/Intel" title="Définition Wikipédia">Intel</a>, ou certaines versions des <a href="http://fr.wikipedia.org/wiki/Architecture_ARM" title="architecture">processeurs ARM</a>.</p>
<p>Le principe est simple : lorsqu’un branchement conditionnel est rencontré, on peut « prédicater » (barbarisme couramment utilisé dans les labos info) les branches du <code>if</code>. Par exemple, quelque chose du genre :</p>
<pre><code class="c"><span class="k">if</span> <span class="p">(</span> <span class="n">condition1</span><span class="o">:</span><span class="n">p0</span> <span class="p">)</span>
<span class="p">[</span><span class="n">p0</span><span class="p">]</span> <span class="n">instruction1</span>
<span class="p">[</span><span class="n">p0</span><span class="p">]</span> <span class="n">instruction2</span>
<span class="k">else</span>
<span class="p">[</span><span class="o">!</span><span class="n">p0</span><span class="p">]</span> <span class="n">instruction3</span>
<span class="p">[</span><span class="o">!</span><span class="n">p0</span><span class="p">]</span> <span class="n">instruction4</span></code></pre>
<p>Les instructions 1 à 4 vont <em>toutes</em> être exécutées. Cependant, leurs résultats ne seront réellement écrits qu’une fois la condition du <code>if</code> évaluée. Cette façon de faire permet de garantir une absence de <em>stalls</em>, et est souvent utilisée dans les processeurs VLIW (dont je ne parlerai pas trop ici, mais qui, en gros, prennent des « super‐instructions » en entrée, composés de plusieurs instructions, par exemple : <code>{ load r1 = @X; load r2 = @X+4; r3 = r5 * r6 }</code>).</p>
<p>L’approche de la prédication est très intéressante, car, en gros, le compilateur ou le programmeur expert peuvent décider de ce qu’il faut « prédicater » ou pas.</p>
<p>Cependant, le bilan énergétique peut être désastreux (après tout, <em>toutes</em> les instructions sont exécutées, qu’elles soient utiles ou pas !).</p>
<p>Je crois qu’on a fait le tour des processeurs scalaires (en fait, la prédication est un mécanisme plutôt utilisé dans le type de processeurs qui va suivre).</p>
<h3 id="faitesmoi-le-plein-de-super">Faites‐moi le plein de super !</h3>
<p>Tout ceci concerne un micro‐processeur <em>scalaire</em>, avec un seul <em>pipeline</em> d’instructions. Rien n’empêche de dupliquer les <em>pipelines</em>. C’est d’ailleurs ce qui est fait dans beaucoup de micro‐processeurs modernes. On parle de <a href="http://fr.wikipedia.org/wiki/Processeur_superscalaire">processeurs superscalaires</a>.</p>
<p>Ce genre de processeurs a besoin de correctement réserver les ressources dont il a besoin : additionneur pour calculer une adresse, UAL (unité arithmétique et logique) pour des calculs sur entiers, unité de calcul flottant (FPU), etc. Il peut bien y avoir deux <em>pipelines</em>, mais cela ne signifie pas qu’il y a deux UAL, par exemple… De même, l’accès à la mémoire depuis un micro‐processeur n’est peut‐être possible que via un seul port, et, du coup, si un chargement mémoire <em>— load —</em> doit être effectué dans un <em>pipeline</em>, il est parfaitement possible qu’un autre chargement soit bloquant dans l’autre <em>pipeline</em>. Dans un contexte d’exécution <em>dans l’ordre</em> <em>— in order —</em>, la qualité du compilateur ou du programmeur expert est déterminante pour obtenir un programme qui s’exécute au moins (en fait, il s’agit de correctement ordonnancer le flot d’instructions de manière statique).</p>
<p>Il existe plusieurs façons de régler le problème au niveau matériel. L’une d’entre elles est de passer par une méthode nommée <a href="http://en.wikipedia.org/wiki/Scoreboarding"><em>scoreboarding</em></a>. Pour faire simple, lorqu’une instruction écrit dans un registre donné, disons <code>r1</code>, alors l’état de <code>r1</code> est marqué comme « occupé ». Les instructions suivantes sont lues, ainsi que leurs dépendances sur les différentes ressources : lorsqu’une instruction qui veut lire <code>r1</code> est émise, elle est placée dans une file d’attente, tant que <code>r1</code> est « occupé ». Lorsque <code>r1</code> est de nouveau libre, <em>— i.e.</em> l’instruction qui écrit dans <code>r1</code> est terminée et a signalé le <em>scoreboard —</em>, alors l’instruction est « remise en place » dans le flot et peut s’exécuter.</p>
<p>Une autre méthode (bien plus connue) est l’<a href="http://fr.wikipedia.org/wiki/ex%C3%A9cution%20dans%20le%20d%C3%A9sordre" title="Définition Wikipédia">exécution dans le désordre</a>, appelée <em>out‐of‐order execution</em> (OoO), en anglais. Dans cette méthode, il existe un ensemble de « registres fantômes » <em>— shadow registers</em>, en anglais. L’idée est de renommer les registres dont les instructions ont besoin, afin de réduire au maximum les dépendances entre instructions. Par exemple :</p>
<pre><code class="c"><span class="n">a</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="c1">// ...</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">*</span> <span class="mi">3</span><span class="p">;</span>
<span class="c1">// ...</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// ...</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="mi">12</span><span class="p">;</span></code></pre>
<p>Si on suit un schéma de dépendance strict, il existe une <em>anti‐dépendance</em> (une dépendance de nommage) entre la première utilisation de <code>a</code> et la deuxième, alors qu’en pratique leur utilisation est complètement indépendante.</p>
<p>En renommant <code>a</code> pour sa seconde utilisation, on permet l’exécution en parallèle de l’affectation à <code>b</code> et <code>c</code> :</p>
<pre><code class="c"><span class="n">a</span> <span class="o">=</span> <span class="mi">10</span> <span class="o">||</span> <span class="n">a1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="c1">// ...</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">*</span> <span class="mi">3</span> <span class="o">||</span> <span class="n">c</span> <span class="o">=</span> <span class="n">a1</span> <span class="o">+</span> <span class="mi">12</span></code></pre>
<p>L’objectif n’est pas d’expliquer l’intégralité de l’<a href="http://fr.wikipedia.org/wiki/Algorithme_de_Tomasulo">algorithme de Tomasulo</a> ou du renommage de variables pour faire disparaître les fausses dépendances, mais ceci devrait permettre de donner une idée de ce que fait un moteur <em>OoO</em>. Une fois tous ces renommages effectués, il faut ensuite <em>réordonner</em> le flot d’instructions. C’est à cela que sert le <em>« reordering buffer »</em>, qui, comme son nom l’indique, sérialise le résultat des instructions, écrit dans les « vrais » registres (pour ensuite pouvoir écrire en mémoire, par exemple), etc.</p>
<h3 id="ce-dont-je-ne-parlerai-pas">Ce dont je ne parlerai pas</h3>
<p>Il existe tout un tas d’autres mécanismes qui existent pour augmenter la performance d’un programme. Certains sont destinés à être utilisés par le compilateur ; d’autres par le programmeur.</p>
<p>Un exemple de mécanisme à la fonctionnalité identique sur plusieurs architectures différentes, et avec un usage différent, est celui des <em>registres rotatifs</em>. Pour faire simple, il s’agit d’un ensemble de registres « glissants » qui sont automatiquement renommés lorsque certaines instructions sont utilisées. Les processeurs de type SPARC utilisent ces registres pour accélérer les changements de contexte entre <em>threads</em> ou processus. Lorsqu’un <em>thread</em> est interrompu par le système, nul besoin de sauvegarder ses registres en mémoire (ce qui est une opération coûteuse) : si une fenêtre de registres est disponible, il suffit de faire « glisser » la fenêtre courante vers la nouvelle fenêtre, et y placer les informations du nouveau <em>thread</em> ou de la nouvelle fonction. Encore mieux, si le <em>thread</em> à exécuter était déjà interrompu, il suffit de re‐glisser vers sa fenêtre (en une instruction ou presque).</p>
<p>L’Itanium utilise le même concept de registres rotatifs pour une autre raison : l’optimisation de boucles via l’utilisation de la technique de <a href="http://fr.wikipedia.org/wiki/Pipeline_logiciel"><em>pipeline</em> logiciel</a>. Je ne vais pas m’étendre sur la technique. Disons simplement qu’elle permet de maximiser les ressources disponibles et de recouvrir tout un tas de latences (notamment mémoire).</p>
<p>J’ai jusqu’ici soigneusement évité de parler de la mémoire en détails. Du point de vue du programmeur séquentiel, la mémoire est un ensemble de « cases » ayant un identifiant (une adresse) dans lesquelles on peut lire ou écrire des valeurs. La mémoire est, bien entendu, nécessaire car les registres sont en quantité limitée (32 ou 64 en moyenne pour les processeurs récents).</p>
<p>En pratique, malgré les progrès effectués pour produire de la mémoire rapide (on en est quand même à la DDR5…), il y a un fossé entre la fréquence d’horloge des micro‐processeurs modernes (qui souvent tourne autour de 2 ou 3 GHz) et celle de la mémoire (entre 0,8 et 1,3 GHz). Pour réduire l’impact de la latence due aux accès à la mémoire principale (DRAM), on a logiquement décidé d’insérer des niveaux de mémoire plus rapides. On retrouve la fameuse « antémémoire » que tout le monde appelle « mémoire cache ». </p>
<h3 id="exploiter-la-localité-des-données">Exploiter la localité des données</h3>
<p>La mémoire cache permet d’obtenir deux types d’optimisations :</p>
<h4 id="localité-temporelle">Localité temporelle</h4>
<p>En supposant qu’un ensemble de mots mémoire va être accédé séquentiellement (ou avec un pas régulier et relativement petit), alors charger une ligne de cache (souvent de 32 ou 64 octets dans les processeurs récents) permet de payer le coût du transfert mémoire (depuis la DRAM vers le cache) une seule fois, mais d’avoir plusieurs mots mémoire disponibles une fois le chargement effectué. Ainsi, si une boucle C suit le schéma d’accès mémoire suivant :</p>
<pre><code class="c"><span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">N</span><span class="o">-</span><span class="mi">2</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
<span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">f</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
<span class="p">}</span></code></pre>
<p>Alors, un entier (généralement 4 octets sur des architectures modernes) sera chargé ainsi (je fais une super simplification avec un pseudo‐langage assembleur ultra pas optimisé) :</p>
<pre><code class="asm"> <span class="c1">; on considère que r0 vaut toujours 0</span>
<span class="nf">mov</span> <span class="nv">r10</span><span class="p">,</span> <span class="kc">$</span><span class="nv">N</span> <span class="c1">; R10 = N</span>
<span class="nf">mov</span> <span class="nv">r11</span><span class="p">,</span> <span class="nv">r0</span> <span class="c1">; i = 0 </span>
<span class="nf">mov</span> <span class="nv">r12</span><span class="p">,</span> <span class="err">@</span><span class="nv">a</span>
<span class="nf">cmp</span> <span class="nv">r11</span><span class="p">,</span> <span class="nv">r10</span>
<span class="nf">jge</span> <span class="nv">EXIT</span>
<span class="nl">LOOP:</span>
<span class="c1">; Convention à la con : r2, ..., r6 sont les six premiers arguments </span>
<span class="c1">; passés aux fonctions ... le reste est passé sur la pile</span>
<span class="c1">; r1 contient la valeur de retour des fonctions</span>
<span class="nf">mov</span> <span class="nv">r2</span><span class="p">,</span> <span class="nv">r11</span> <span class="c1">; premier argument de f()</span>
<span class="nf">call</span> <span class="nv">f</span> <span class="c1">; r1 = f(i)</span>
<span class="nf">mul</span> <span class="nv">r2</span><span class="p">,</span> <span class="nv">r11</span><span class="p">,</span> <span class="kc">$</span><span class="mi">4</span> <span class="c1">; r2 = i * 4</span>
<span class="nf">add</span> <span class="nv">r5</span><span class="p">,</span> <span class="nv">r12</span><span class="p">,</span> <span class="nv">r2</span> <span class="c1">; calcul de l'adresse dans a: r5 = (a + i * 4)</span>
<span class="nf">store</span> <span class="p">[</span><span class="nv">r5</span><span class="p">],</span> <span class="nv">r1</span> <span class="c1">; a[i+2] = r1</span>
<span class="nf">add</span> <span class="nv">r2</span><span class="p">,</span> <span class="nv">r2</span><span class="p">,</span> <span class="kc">$</span><span class="mi">2</span> <span class="c1">; i += 2</span>
<span class="nf">cmp</span> <span class="nv">r11</span><span class="p">,</span> <span class="nv">r10</span>
<span class="nf">jlt</span> <span class="nv">LOOP</span>
<span class="nl">EXIT:</span>
<span class="c1">; Suite du programme</span></code></pre>
<p>Ainsi, si l’on a une ligne de cache de 64 octets, il y a 16 entiers de 4 octets directement accessibles par une boucle. Du coup, plutôt que charger (et payer) N/2 fois le coût de chargement depuis la DRAM, on va envoyer 16 mots (dont 8 seulement seront utiles ici). Je passe sur les détails architecturaux, mais grâce à la mise en cache, on va passer de latences de plusieurs dizaines, voire centaines de cycles (généralement entre 50 et 300, en fonction des architectures), à seulement entre 2 et 10 cycles par accès quand la donnée est située dans le cache.</p>
<p>Bref. Les caches permettent de recouvrir les latences lors des accès mémoire et d’accélérer significativement l’exécution d’un programme. C’est ce qu’on appelle la « localité temporelle » : payer le coût de chargement d’une ligne de cache permet d’accéder à <code>a[i+2]</code>, <code>a[i+4]</code>, …, <code>a[i+14]</code>, et donc, alors que le « temps » avance (c’est‐à‐dire l’indice de boucle est incrémenté), les données restent « proches » du processeur.</p>
<h4 id="localité-spatiale">Localité spatiale</h4>
<p>La « localité spatiale » est le fait de pouvoir réutiliser plusieurs mots de la même ligne de cache d’affilée. Je ne m’étends pas dessus, mais, en gros, dans un code, si l’on arrive à réutiliser <code>a[i]</code> plusieurs fois, alors on économise en « espace » (en nombre de registres ou de lignes de cache à utiliser).</p>
<p>À noter qu’il existe aussi des caches pour stocker les instructions les plus fréquemment utilisées, ou même des caches unifiés (qui contiennent code et données).</p>
<p>Reste un problème : lorsqu’une donnée ne se trouve pas en cache — un défaut de cache communément appelé <em>« cache miss » —</em>, alors on paie de nouveau le chargement de 64 octets pour chercher la prochaine ligne de cache, au prix fort.</p>
<h3 id="spéculer-sur-le-chargement-des-données">Spéculer sur le chargement des données</h3>
<p>Pour résoudre le problème évoqué précédemment, les concepteurs de micro‐processeurs ont ajouté un mécanisme supplémentaire : les préchargeurs de mémoire. En gros, si l’on connaît les caractéristiques du matériel (latence pour accéder à la DRAM, taille des lignes de cache, etc.), et si le motif d’accès à la mémoire est suffisamment régulier, alors un bon programmeur/compilateur peut insérer des instructions de <em>prefetch</em> aux endroits stratégiques (généralement, on en insère plusieurs avant le début d’une boucle, puis juste au début d’une itération). C’est ce qu’on appelle le <em>préchargement logiciel</em> <em>— software prefetching</em>. On peut le retrouver sur la plupart des processeurs modernes (ARM, x86, SPARC, POWER/PowerPC…).</p>
<p>Il existe une variante mise en œuvre de façon purement matérielle, logiquement appelée <em>préchargement matériel</em> <em>— hardware prefetching</em>. Il existe plusieurs variantes, aussi je vais en détailler deux, actuellement utilisées au moins dans certains processeurs x86. La première est appelée par Intel <em>« adjacent line prefetching »</em> (préchargement de ligne adjacente). Lors d’un <em>cache miss</em>, une ligne de cache est (logiquement) chargée depuis la mémoire principale. Cette méthode s’assure que la ligne suivante est chargée elle aussi, car statistiquement il y a de fortes chances que le programme en aura besoin lui aussi. À ma connaissance, tous les processeurs x86 (AMD, Intel) mettent en œuvre cette technique. La seconde technique est au moins utilisée dans les processeurs Intel (je ne me suis pas renseigné pour AMD). Il s’agit du <em>stride prefetcher</em> (préchargement par pas). L’idée est simple : le <em>stride prefetcher</em> retient en mémoire une liste des <em>n</em> dernières adresses accédées par le processeur. Si, pour une adresse donnée, on y accède par pas de <em>K</em> (comme mon <code>a[i+2]</code> de tout à l’heure), alors le <em>prefetcher</em> détectera le motif d’accès, et s’occupera de précharger correctement les lignes de cache correspondantes au pas. Le <em>stride prefetcher</em> est limité aux accès au sein d’une même page mémoire (4 Kio ou 4 Mio sur x86).</p>
<h2 id="conclusions">Conclusions</h2>
<p>Voilà, j’ai plus ou moins fini mon rappel rapide des micro‐processeurs séquentiels. Il faudrait aussi parler des instructions vectorielles (<a href="http://fr.wikipedia.org/wiki/MMX_%28jeu_d%27instructions%29">MMX</a>, <a href="http://fr.wikipedia.org/wiki/Streaming_SIMD_Extensions"><em>Streaming SIMD Extensions</em></a>, <a href="http://fr.wikipedia.org/wiki/AltiVec">AltiVec</a>, etc.), mais ça prendrait encore quelques paragraphes et j’ai la flemme. La prochaine fois, on passe aux multi‐processeur et multi‐cœur !</p></div><div><a href="https://linuxfr.org/news/ou-vont-les-supercalculateurs-d-ou-on-vient-quels-sont-les-problemes-ou-l-on-va-1re-partie.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/98918/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/ou-vont-les-supercalculateurs-d-ou-on-vient-quels-sont-les-problemes-ou-l-on-va-1re-partie#comments">ouvrir dans le navigateur</a>
</p>
lasher
Davy Defaud
Ontologia
Thomas Debesse
Nÿco
patrick_g
Benoît
Yala
Benoît Sibaud
palm123
https://linuxfr.org/nodes/98918/comments.atom