Marc BUFFAT

Professeur au département de Mécanique, Lyon 1 e-mail

Blog scientifique et pédagogique utilisant des notebooks IPython et Linux

cours en ligne INPROS: chapitre 2


Ipython notebook : cours INPROS LyonHPC

Auteur: Violaine Louvet, Institut Camille Jordan, UCB Lyon 1

Contributeurs: Marc Buffat, Michel Kern, Loic Gouarin, Laurence Viry </h5>

Licence Creative Commons
Mise à disposition selon les termes de la Licence Creative Commons
Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 2.0 France
.
In [2]:
%matplotlib inline
%autosave 300
from IPython.display import HTML
from IPython.display import HTML,display
css_file = 'style.css'
try:
    display(HTML(open(css_file, "r").read()))
    print("using ",css_file)
except :
    print("using default css")
Autosaving every 300 seconds
using default css

Fonctionnement d’un ordinateur

LyonHPC LyonHPC
</div> sommaire

Comment des composants matériels reliés entre eux arrivent-ils à afficher une vidéo, simuler un feu de forêt et permettre à des gens de communiquer d’un bout à l’autre de la terre ?

Un ordinateur est capable d’exécuter très rapidement et très mécaniquement des instructions extrêmement simples :

1+2
2<10
...

C’est le programmeur qui, à partir d’un problème donné, va le traduire sous une forme compréhensible par l’ordinateur. L’objectif de cette partie du cours est de comprendre le fonctionnement de l’ordinateur pour mieux appréhender le comportement des programmes.

Architecture matérielle

Constituants élémentaires

Tous les ordinateurs sont construits à base de circuits électroniques, qui sont eux même constitués de transistors.

Exemples de transistors

Loi de Moore

  • Exprimée par Gordon Moore en 1965

    Gordon Moore
  • Tous les 18 / 24 mois, le nombre de transistors sur une puce double.

  • Davantage une observation / prédiction qu’une loi.
  • Elle semble continuer à se vérifier.
  • C’est une loi exponentielle : si on double 10 fois, on multiplie par 1000 !
  • A coût constant, la capacité des matériels électronique augmente. Il suffit de penser à la taille mémoire des clés USB.
Loi de Moore



Un ordinateur vu de l’intérieur

Un ordinateur est un être complexe. Mais il n’a formellement besoin que de deux choses pour calculer : de la mémoire et d’un processeur.

Processeur et Mémoire

Les données se déplacent : elles transitent de la mémoire vers le processeur et inversement. Le chemin emprunté est appelé le bus.

Voyons cela plus en détails.

Ordinateur vu de l'extérieur



Carte mère

Elément essentiel de l’ordinateur, elle permet la connexion de tous les constituants de la machine.

Ordinateur vu de l'intérieur



Processeur, CPU, cerveau ou coeur de la machine

C’est le composant de l’ordinateur qui exécute les instructions machine des programmes informatiques. Un processeur est constitué :

  • d’une horloge qui rythme le processeur,
  • d’unités arithmétiques et logiques en charge des calculs élémentaires et des tests,
  • de registres mémoire et de mémoire cache qui permettent au processeur d’accéder très rapidement aux données à traiter,
  • d’unités de contrôle qui permettent de synchroniser les différents éléments du processeur,
  • d’unités d’entrée-sortie en charge de la communication avec la mémoire.

Dans la réalité, les processeurs actuels sont extrêmement plus complexes mais ces principaux éléments suffisent à en comprendre le fonctionnement général.

Processeur



Mémoire vive, RAM, mémoire temporaire

RAM = Random Access Memory

C’est un stockage temporaire, non persistant (volatile) : lorsqu’un programme s’arrête, l’espace mémoire dont il s’est servi est vidé.

RAM

L’accès par le processeur aux données en RAM est beaucoup plus lent que l’accès aux données dans les mémoires caches ou les registres, mais beaucoup plus rapide que l’accès aux données stockées sur disque par exemple.

Perte des données en RAM



Stockage persistant

Il existe plusieurs types de stockage persistant : les disques durs, les flash disk (clés USB, SD Card …) … qui correspondent à des technologies différentes. Mais le principe reste le même : lorsque l’ordinateur est éteint, les données perdurent sur le support.

Ce type de stockage est organisé grâce à un système de fichiers qui permet de stocker les informations à travers des fichiers et des répertoires. Il offre à l’utilisateur une vue abstraite sur ses données et permet de les localiser à partir d’un chemin d’accès.

Disque dur



Bus de communication

Un bus informatique est un système de communication entre les composants d’un ordinateur. C’est un ensemble de liaisons physiques (câbles, pistes de circuits imprimés, etc.) pouvant être exploitées en commun par plusieurs éléments matériels afin de communiquer :

  • communication entre le processeur et la RAM
  • communication entre le processeur et la carte graphique
  • communication vers l’extérieur à travers un réseau …



Carte graphique

Une carte graphique ou carte vidéo est une carte d’extension dont le rôle est de produire une image affichable sur un écran. Les cartes grapĥiques actuelles, en raison de leur importante capacité de calcul, peuvent aussi être utilisées en complément du processeur pour traiter des données (autres que les calculs spécifiques à l’affichage).



Réseau

L’interface réseau assure le lien entre l’ordinateur et un ensemble d’autres équipements connectés sur le même réseau. Différents médias de transmission peuvent être utilisés : filaire, WIFI, …




Binaire et flottants

Les transistors ne savent gérer que deux états stables liés à des tensions électriques. Ils comptent en binaire à partir de bits (binary digit) qui ne peuvent donc prendre que les valeurs 0 ou 1.

Le bit est la plus petite unité de stockage informatique.




Le codage binaire

Un calcul informatique n’est donc qu’une suite d’opérations sur des paquets de 0 et de 1. Mais comment traduire une information plus complexe comme un chiffre ou une lettre afin de la rendre compréhensible et traitable par un ordinateur qui ne comprend que les 0 et les 1 ?

Pour bien appréhender cette problématique, commençons par énumérer le nombre de motifs différents représentables par plusieurs bits.

Nombre de bits Motifs Nombre de motifs
1 bit 0 1 2
2 bits 00 01 10 11 4
3 bits 000 001 010 011 100 101 110 111 8
n bits 2^n

Avec n bits, on représente donc 2^n motifs.

En informatique, on utilise les Bytes ou les octets.

1 byte = 1 octet = 8 bits

Avec 1 octet, on a donc 256 possibilités : on peut représenter les nombres de 0 à 255. L’ordinateur ne calcule jamais sur 1 bit à la fois, mais sur un ou plusieurs octets.


Quelques ordres de grandeur

Les valeurs de stockage (tailles des disques durs, des fichiers, de la mémoire …) vont s’exprimer en Bytes ou en octets.

Les valeurs concernant les vitesses de transmissions (entre le processeur et la mémoire, pour télécharger un fichier depuis internet …) sont le plus souvent exprimées en bits (quelque fois en octets) par seconde.

Voici quelques exemples d’ordres de grandeurs :

  • KB (KiloBytes) ou Ko (Kilooctets)
    • 1 Ko = 1 000 octets
    • Le code source d’un programme fait en général de l’ordre de quelques ko
  • MB (MégaBytes) ou Mo (Mégaoctets)
    • 1 Mo = 1 000 Ko = 1 000 000 octets
    • Une image codée en 24 bits/pixel (donc 3 octets/pixels) d’une résolution de 10 Mégapixels aura un poids de 30 Mo (avant toute compression)
  • GB (GigaBytes) ou Go (Gigaoctets)
    • 1 Go = 1 000 Mo = 10^9 octets
    • Une matrice de taille 50 000 x 50 000 nombres décimaux (flottants) codés sur 64 bits donc 8 octets prendra 50 000 x 50 000 x 8 = 20 Go en mémoire
  • TB (TéraBytes) ou To (Téraoctets)
    • 1 To = 1 000 Go = 10^12 octets
    • Le LHC (Large Hadron Collider, grand accélérateur de particules du CERN) produit tous les ans 15 Pétaoctets de données, soit 15 000 To.

On peut également regarder les tailles de différents types de fichiers : fichier vidéo, fichier word, code exécutable, code source et image.



<img src=”./taille_fichier.png” alt=”Exemples de taille de fichiers”</img>


Représentation des nombres

On a vu que le codage binaire permettait de représenter de l’information sous une forme compréhensible par un ordinateur. Toutes les données sont stockées sous forme binaire de tailles différentes. Ainsi, le codage des entiers s’effectue usuellement sur 4 octets donc jusqu’à une valeur maximale de 2^(4*8) - 1

En simulation numérique, on manipule des nombres à virgule (réels) sur lesquels on effectue diverses opérations. Or, l’ordinateur ne peut stocker que des nombres finis en mémoire. Les nombres à virgule flottante utilisés par les machines sont donc des approximations des nombres réels.

Sans entrer dans les détails, ils sont représentés sous la forme :

$$x = s . m . b^e$$

  • s est le signe de x
  • m est la mantisse
  • b est la base (usuellement 2 ou 10)
  • e est l’exposant

Les points suivants illustrent ce qu’il faut retenir de la problématique des nombres flottants :

  • Leur précision est limitée, ce qui se traduit par des arrondis qui peuvent s’accumuler de façon gênante. En particulier, la soustraction de deux nombres très proches peut provoquer une grande perte de précision relative si le résultat n’est pas représentable exactement en machine.
  • Une plage d’exposants limitée, pouvant donner lieux à des « overflows » (lorsque le résultat d’une opération est plus grand que la plus grande valeur représentable) et à des « underflows » (lorsqu’un résultat est plus petit, en valeur absolue, que le plus petit flottant normalisé positif), puis à des résultats n’ayant plus aucun sens.
  • Les calculs en virgule flottante, contrairement aux calculs sur les réels, ne sont pas associatifs. Par exemple, dans un calcul en flottants (selon la norme IEEE) double précision, (2^60+1)-2^60 ne donne pas 1, mais 0 (en arrondi au plus près). La raison est que 2^60+1 n’est pas représentable exactement et est arrondi à 2^60.
  • En arithmétique flottante (norme IEEE), un calcul peut aboutir à des valeurs qui ne correspondent pas à des nombres réels :
    • NaN (« not a number »), qui sera par exemple le résultat de la tentative de division flottante de zéro par zéro, ou de la racine carrée d’un nombre strictement négatif. Les NaN se propagent : la plupart des opérations faisant intervenir un NaN donnent NaN (des exceptions sont possibles, comme NaN puissance 0, qui peut donner 1).
    • Un infini positif et un infini négatif, qui sont par exemple le résultat d’un « overflow » en arrondi au plus près.


Quelques exemples



In [3]:
# Exemple de la soustraction
5.555e-02 - 5.554e-02
Out[3]:
1.0000000000003062e-05
In [4]:
from math import pow
op1 = pow(2,60) + 1
op2 = pow(2,60)

res = op1 - op2
print(res)

res2 = pow(2,60) - pow(2,60) + 1
print(res2)
0.0
1.0



Performances et évolutions


Caractéristiques des processeurs

Un processeur actuel comporte plusieurs milliards de transistors. Le processeur travaille en cycles, et le nombre de cyles par seconde est caractérisé par sa fréquence, exprimée en Hertz :

1 cycle en 1 seconde = 1 Hz

Pour évaluer la performance théorique maximale d’un processeur (puissance crête), on évalue généralement le nombre d’opérations flottantes qu’il est capable d’effectuer par seconde : FLOPS (FLoating point Operations Per Second).

La plupart des processeurs actuels incluent une ou plusieurs unité de calcul en virgule flottante.

Exemple sur un processeur Intel Sandybridge

Pour évaluer la performance crête d’un processeur de type Intel SandyBridge, on considère ses caractéristiques suivantes :
  • la fréquence d’un coeur : 3.2GHz
  • le nombre de coeurs : 6
  • les registres vectoriels (c’est à dire le nombre de données flottantes pouvant être traitées simultanément) : 16 en simple précision (c’est à dire quand les flottants sont stockés sur 32 bits), 8 en double précision (c’est à dire quand les flottants sont stockés sur 64 bits)
La performance crête d’un processeur de ce type sera donc :
6 x 3.2 x 16 = 307.2 GFLOPS

D’un point de vue pratique, la puissance d’une machine dépend de l’ensemble de ses composants : fréquence du processeur, accès mémoire, vitesse des bus, complexité de l’architecture … mais aussi charge de la machine, système d’exploitation … On est souvent loin de la puissance théorique …




Evolutions attendues : comment faire des machines encore plus puissantes ?

L’augmentation du nombre de transistors sur une puce est possible grâce à la miniaturisation des transistors. Mais ce ne sera évidemment pas possible indéfiniment ! Les transistors actuels sont gravés à 22 nm (nanomètres) soit la taille de quelques dizaines d’atomes. Il faudra donc rapidement trouver une alternative.

Si on regarde un peu en arrière :

Année Puissance evolution
1997 Teraflops
2008 Petaflops
2020 Exaflops

Cette prédiction suppose que les calculateurs soient de plus en plus rapide, mais comment y arriver ?




Principales limitations

  • La consommation électrique augmente de façon exponentielle en fonction de la fréquence d’horloge :
    P ~ f^3
    avec P : puissance (Watt) et f : fréquence. Mais pas que !! Tous les éléments sont consommateurs : mémoires, carte mère, alimentation inefficace …
  • La dissipation thermique est directement liée à la puissance consommée : plus on augmente la fréquence, plus la dissipation thermique est importante. Il faut donc refroidir, tant au niveau des chips, qu’au niveau de l’infrastructure globale.
  • La finesse de gravure des transistors atteint actuellement 22 nm. Feuille de route Intel : 4 nm en 2022. Plus de transistors donc plus de coeurs, et moins de dissipation thermique (plus c’est petit moins ça dissipe). Mais des défis technologiques pour les fondeurs et des technologies de plus en plus coûteuses ! Jusqu’à quand ? ?




Les orientations les plus probables

Les orientations qui se dessinent aujourd’hui semblent être :

  • des processeurs dont la fréquence stagne voir diminue,
  • de plus en plus de coeurs par processeur,
  • une augmentation de la mémoire très limitée,
  • l’utilisation de cartes accélératrices en complément ou en remplacement de processeurs plus traditionnels : GPU (cartes graphiques, Graphical Processing Unit), carte many-core (comme les Xeon Phi de la gamme Intel).




Impacts de l’architecture sur la programmation

Il est important de connaître le fonctionnement d’un ordinateur pour adapter les méthodes numériques, les algorithmes et la programmation à l’architecture et comprendre le comportement d’un code.

On ne programmera pas de la même façon un ordinateur doté d’un processeur mono-coeur à haute fréquence qu’un serveur quadri-processeurs dotés de 8 coeurs chacun, de fréquence assez faible, boosté par une carte accélératrice :

  • les vitesses de traitement ne sont pas les mêmes,
  • l’accès à la mémoire ne se fait pas de la même façon,.
  • l’exploitation de tous les coeurs suppose une programmation complètement différente

Et les évolutions actuelles des architectures ne laissent rien présager de simple pour les années à venir !

Langages de programmation


Le langage machine

Le langage machine est la suite de bits qui est interprétée par le processeur exécutant un programme informatique. C’est le seul langage qu’un processeur puisse traiter. Il est composé d’instructions et de données codées en binaire.

Ainsi, au niveau matériel de l’ordinateur, le jeu d’instructions, composé des instructions machines, constitue l’ensemble des opérations élémentaires qu’un programme peut demander à un processeur de traiter. Ces instructions machines permettent d’effectuer des opérations élémentaires (addition, ET logique …) ou plus complexes (division, …), mais aussi de manipuler les données (stockage dans les registres du processeur …).

Chaque type de processeur possède son propre jeu d’instructions : un exécutable en code machine ne peut donc s’exécuter que sur la machine pour laquelle il a été préparé.

Le jeu d’instructions le plus courant sur les processeurs actuels est le jeu d’instructions x86. Il a subi de nombreux ajouts au fur et à mesure des années et de l’évolution des processeurs, avec notamment les instructions de type vectorielles SSE et AVX (possibilité de traiter en même temps plusieurs instructions de même type sur des données différentes).


Du langage “humain” au langage machine

Le langage machine est donc :

  • totalement incompréhensible par les humains,
  • spécifique au type de processeur sur lequel on travaille.

Pour remédier à ces spécificités, il faut donc pouvoir passer d’un langage humainement compréhensible à une exécution machine.


Langage assembleur

L’assembleur est un langage informatique très proche du langage machine mais il reste compréhensible pour des développeurs. Toutefois, sa très grande proximité avec le langage machine rend les programmes étroitement dépendant du type de processeur utilisé. Les programmes écrits en assembleur ne sont donc pas portables.


Exemple

Un processeur de la famille x86 reconnaît une instruction du type

10110000 01100001

En langage assembleur, cette instruction est représentée par un équivalent plus facile à comprendre pour le programmeur :

movb $0x61,%al

avec la correspondance :

10110000 movb %al
01100001 $0x61

Ce qui signifie : ” écrire le nombre 97 (la valeur est donnée en hexadécimal : 61 (base 16) = 97 (base 10)) dans le registre AL “.


Très bien, mais cette instruction reste incompréhensible pour le processeur qui, rappelons le, ne sait traiter que des 0 et des 1 !! Pour cela, on fait appel à un traducteur : le compilateur.


Compilateur

Un compilateur est un programme informatique qui transforme un code source écrit dans un langage de programmation (le langage source) en un autre langage informatique (le langage cible).

compilateur


Langages compilés, langages interprétés

Un langage est dit compilé quand l’optention du code exécutable requiert un compilateur. De la même manière, un langage interprété requiert un interprète.

La différence principale entre compilé et interprété est la suivante : là où le compilateur traduit une bonne fois pour toute un code source en un fichier indépendant exécutable (donc utilisant du code machine), l’interprète est nécessaire à chaque avancement des lignes du programme interprété, pour traduire au fur et à mesure et à la volée le code source en code machine.

L’interpréteur traduit donc le code source ligne par ligne là où le compilateur traduit l’ensemble du code source une fois pour toute. Cela permet au compilateur de réaliser un certain nombre d’optimisation et en général, les programmes compilés sont plus rapides à l’exécution que les programmes interprétés.


Langages de la programmation scientifique

Quels sont les langages qui sont couramment utilisés en programmation scientifique ?

Comme vous l’avez compris, il n’est pas question de programmer en assembleur et encore moins en langage machine. D’une façon générale, les programmes développés en informatique scientifique doivent être :

  • efficaces,
  • portables,
  • lisibles,
  • maintenables,
  • évolutifs,
  • ré-utilisables …

Il est donc nécessaire d’utiliser les langages de haut niveau nécessitant compilateur ou interpréteur.


Langages compilés

Les langages compilés que l’on retrouve le plus souvent dans le domaine de la programmation scientifique sont :

  • le Fortran
  • le C
  • le C++


langages interprétés

Les langages interprétés utilisés le plus fréquemment en programmation scientifique sont :

  • Python

A noter que tous ces langages, si ils ont une syntaxe différente, utilisent les mêmes concepts en ce qui concerne les types des données, les instructions et structures de contrôle … Ils sont également inter-opérables c’est-à-dire que l’on peut en général, dans un code écrit avec un langage X, intégrer l’appel d’une fonction développée dans un langage Y.

On peut aussi inclure dans la catégorie des langages interprétés des outils comme matlab, scilab, R.

Exemple

Une petite comparaison de la syntaxe de ces différents langages sur un exemple de boucle tout simple.

Fortran C++ Python
`do i = 1,n if (a(i) > 0.) b(i) = 2.*c(i) else b(i) = c(i) - 1. end if end do ` `for (int i = 0 ; i < n ; ++i) { if (a[i] > 0.) b[i] = 2.*c[i]; else b[i] = c[i] - 1.; } ` `for i in range(a.size): if a[i] > 0.: b[i] = 2.*c[i] else: b[i] = c[i] - 1. `

On peut constater que les syntaxes sont relativement proches.


Et le système d’exploitation dans tout ça ?

linux windows apple

Pour qu’un ordinateur soit capable de faire fonctionner un programme informatique, la machine doit être en mesure d’effectuer un certain nombre d’opérations préparatoires afin d’assurer les échanges entre le processeur, la mémoire, et les ressources physiques (périphériques : disques, réseau …).

Le système d’exploitation (Operating System), est chargé d’assurer la liaison entre les ressources matérielles, l’utilisateur et les applications. Ainsi lorsqu’un programme désire accéder à une ressource matérielle, il ne lui est pas nécessaire d’envoyer des informations spécifiques au périphérique, il lui suffit d’envoyer les informations au système d’exploitation, qui se charge de les transmettre au périphérique concerné via son pilote. En l’absence de pilotes il faudrait que chaque programme reconnaisse et prenne en compte la communication avec chaque type de périphérique !

Le système d’exploitation a aussi pour rôle de gérer le partage des ressources lorsque plusieurs programmes utilisent simultanément la machine.

Les systèmes d’exploitation les plus couramment utilisés en informatique scientifiques sont basés sur des noyaux Unix/Linux.