Direction du numérique (DN)


Compilation et Optimisation de codes

Compilation à la main
Les Makefiles
CMake
Démarche à suivre avant d'optimiser un code
Options d'optimisation de base

 

Tout code de calcul permettant de résoudre des  problèmes de physique, chimie, biologie, ... est écrit dans un langage de programmation. Ceux sont les codes ou programmes "sources". Ils correspondent à une suite d'instructions compréhensibles par l'homme mais pas toujours directement utilisable par un ordinateur.

Pour les langages dits non-interprétés (Fortran, C, C++), il faut traduire le programme "source" en un programme "objet", puis en un fichier exécutable pour le rendre compréhensible par un ordinateur : c'est la compilation. Cette opération se fait via un compilateur.

La compilation peut se faire soit à la main, soit en utilisant un fichier Makefile.

Pour les langages dits interprétés (Python, Matlab, Scilab, ...), il n'y a pas de compilation.

 

 

 

Compilation à la main

 

1. Compilation d'un programme source unique

 

  • Soit un programme source principal main.ext (où l'extension du nom du programme  '.ext' est '.f90' en fortran90, 'cc' en C++)  qu'on souhaite compiler.  Il faut pour cela connaître :
    1. le compilateur 'comp' installé sur la machine sur laquelle vous voulez compiler votre programme,
    2. les options de compilation propres au compilateur ainsi que les options d'optimisation 'Opt'.
  • Pour compiler votre programme source, il vous suffit de taper la commande suivante dans un terminal : 
[comp]  [Opt] -o main main.ext
 
  •  Exemples : 
  • Si vous avez le compilateur 'G95' ( [comp]=g95, [Opt]=-O2 ) pour compiler un programme 'main.f90' : g95 -O2 -o main main.f90
  • Si vous avez le compilateur 'GCC' ( [comp]=g++, [Opt]=-O2 ) pour compiler un programme 'main.cpp' en C++: g++ -O2 -o main main.cpp



2. Compilation d'un programme source principal et de plusieurs sous programmes

 

On considère un programme source principal (main.f90) qui fait appel à un module (mod.f90) et à 2 procédures (subr1.f90 et subr2.f90). On dit que main.f90 dépend de mod.f90, subr1.f90 et subr2.f90. On considère aussi que subr1.f90 dépend de mod1.f90 et que subr2.f90 dépend de subr1.f90 et mod.f90.



 
Attention, il faut compiler les fichiers sources dans un ordre précis car un fichier source ne peut être compilé que si  les fichiers sources dont il dépent ont été préalablement compilés.
Il faut donc commencer par compiler le fichier source ne dépendant d'aucun autre (ici mod.f90).
  • Construction des fichiers objets (.o) à partir des fichiers sources (.f90)
    [comp] -c mod.f90
    [comp] -c subr1.f90
    [comp] -c subr2.f90
    [comp] -c main.f90

     

  • Construction de l'exécutable 'main' à partir des fichiers objets (.o).

[comp]  [Opt] -o main main.o subr1.o subr2.o mod.o

  • Vous pouvez exécuter votre programme avec la commande:

./main

 

On se rend vite compte, que ce type de compilation "à la main" devient vite fastidieux lorsque le nombre de fichiers sources augmentent. Une solution consiste à exécuter un script contenant les noms des fichiers sources à compiler ainsi que les caratéristiques de compilation: ce fichier script s'appelle un Makefile.

 

 

3. Gérer une bibliothèque statique

 

Attention : le nom de votre bibliothèque doit commencer par 'lib' et doit avoir le suffixe '.a'. Pour utiliser dans un programme, il vous suffira d'utiliser '-ltest' lors de la compilation.

[comp]  [Opt] -o main main.o subr1.o subr2.o mod.o -ltest 

 

 

Les Makefiles

 

1. Qu'est-ce qu'un Makefile?

 

Un Makefile est un fichier script dans lequel on indique les noms des fichiers sources à compiler (cibles), ceux dont ils dépendent pour être compilés (dépendances) et les règles de compilations.
Un fichier Makefile permet de construire votre programme exécutable 'main' en exécutant la commande 'make'.

 

2. Construction d'un Makefile de base

 

  • Un fichier Makefile est constitué d'une succession de lignes de commande du type: 

"cible :  dépendances

règle de compilation"

  • Les commentaires sont marqués par un "#" en début de ligne.
  • Voici comment on peut écrire un Makefile de base pour compiler les fichiers sources de lexemple précédent. La première ligne non commenter s'interprète de la façon suivante: l'exécutable "main" dépend des fichiers "mod.o", "subr1.o", "subr2.o" et "main.o". On utilise la règle de compilation "g95 -o main mod.o subr1.o subr2.o main.o".

 

  • Une fois que votre Makefile est écrit, vous devez rendre le fichier exécutable ('chmod 755 Makefile').

 

  • Ensuite, exécuter votre Makefile avec la commande 'make'. Votre exécutable 'main' est construit. Vous pouvez maintenant exécuter votre programme 'main' avec la commande './main'.

 

3. Améliorations du Makefile de base

 

On peut améliorer le Makefile de base en:

  • introduisant des variables qui contiennent des listes de fichiers ou des options de compilation (ex: G90=gfortran). Ensuite, l'appel à une variable se fera en précédent son nom par "$" (ex: $(G90)).
  • utilisant les règles génériques des scripts. Par exemple, "$<" donne le nom de la première dépendance.

 

 

Pour plus de détails sur les Makefiles, vous pouvez consulter les site suivants :

 

On remarque ici, que les Makefiles dépendent du compilateur choisi (nom et options de compilation) et de l'endroit où se trouvent les bibliothèques (ex: lapack). Donc, pour compiler un même code sur différentes machines, il faut adapter ce Makefile en fonction des compilateurs disponibles et de l'endroit où se trouvent les bibliothèques uttilisées.

Pour éviter ce problème, on peut utiliser ce qu'on appelle un système de construction logicielle ("Build System" en anglais) tel que CMake.

 

CMake

 

1. Définition

 

CMake est un système de construction logicielle ("Build System" en anglais) libre, multilangage (C, C++ , Fortran et java) et multiplateforme (Linux, MacOs et Windows).

Ce qui nous intéresse ici est sa capacité à compiler des fichiers sources, via la construction de Makefiles, indépendamment de la machine utilisée (système d'exploitation, compilateur, architecture, .....).

L'obtention d'un Makefile grâce à l'association de CMake et d'un générateur de Makefiles qu'on applique sur deux fichiers script: CMakeLists.txt et CMakeCache.txt.

  • CMakeLists.txt contient une description générique du projet, commune à tous les utilisateurs.
  • CMakeCache.txt contient des informations sur la configuration logicielle de la machine utilisée. Ce fichier est donc propre à chaque utilisateur.

 

2. Utilisation de CMake

 

Le fichier CMakeLists.txt

 

Celui-ci décrit les caractéristiques du projet de manière générique. Son écriture est indispensable pour utiliser CMake.

  • Il est composé d'une succession de lignes de commandes de la forme:
    • nom_commande(arg1  "arg deux ")
    •  nom_commande(
               arg1
               "arg deux "
      )
  • Les commentaires sont marqués par un '#' en début de ligne.

 

Choix du générateur de Makefile

 

Une fois le CMakeLists.txt écrit, il faut choisir le générateur de Makefile souhaité. Pour connaître la liste des générateurs disponibles, vous pouvez taper la commande 'cmake --help'.  Il apparait plusieurs informations dont la liste recherchée:

 The following generators are available on this platform:
  Unix Makefiles              = Generates standard UNIX makefiles.
  CodeBlocks - Unix Makefiles = Generates CodeBlocks project files.
  Eclipse CDT4 - Unix Makefiles
                              = Generates Eclipse CDT 4.0 project files.
  KDevelop3                   = Generates KDevelop 3 project files.
  KDevelop3 - Unix Makefiles  = Generates KDevelop 3 project files.

 

Génération du Makefile

 

On lance maintenant la commande qui va créer le Makefile:  cmake  .  -G"nom du générateur"

  • Le '.' après cmake indique l'endroit où se trouve le fichier CMakeLists.txt,
  • '-G' est l'option indiquant que le générateur va être spécifié,
  • "nom du générateur" est le nom du générateur choisi. Par exemple, "Unix Makefiles" pour un générateur de Makefiles Unix standard.

 

Si tout s'est bien passé:

  • vous voyez apparaître plusieurs lignes:

-- The C compiler identification is GNU
-- The CXX compiler identification is GNU
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/username/Monprojet

  • Plusieurs répertoires et fichiers ont été créés dont le CMakeCache.txt (vous pouvez être amené à modifier ce fichier si vous devez spécifier des chemins spécifiques) et le Makefile.

 

3. Exemples d'utilisation

 

Dans les exemples décrits ci-dessous, on dispose d'un répertoire 'Monprojet' dans lequel se trouvent le fichier CMakeLists.txt et un sous répertoire 'src' dans lequel se trouvent les fichiers sources.

Exemple 1:
  • Fichier source main.cc :

 

  • Ecriture du fichier CMakeLists.txt:


  • Amélioration du CMakeLists.txt
    • Si on a beaucoup de fichiers sources dans le répertoire 'src', leur écriture dans la commande 'add_executable' devient vite fastidieuse. Mieux vaut utiliser le symbole '*' pour désigner tous les fichiers sources: add_executable( main src/* )
    • En supposant que l'arborescence contenant vos fichiers sources se complexifient, vous pouvez lister vos sources dans une variable de votre choix ('SRC_FILE' par exemple) et utiliser la variable 'GLOB_RECURSE' pour faire une recherche dans les répertoires et sous-répertoires de 'src'.

 

 

  • Compilation  et exécution:
    • cmake  .  -G"Unix Makefiles"
    • make
    • ./bin/main

  

Exemple 2:

 

Cette fois-ci, le fichier source utilise la bibliothèque Boost. Il faut donc en tenir compte dans la compilation. La méthode la plus simple consiste à indiquer dans le fichier CMakeLists.txt la bibliothèque recherchée et de laisser faire CMake. Cette méthode fonctionne pour les bibliothèques les plus standards (voir la liste). 

Si vous utilisez une bibliothèque plus exotique ou que les répertoires où les bibliothèques sont installées ne sont pas standards, il vous faudra indiquer les chemins dans le fichier CMakeCache.txt (plus d'infos).

  • Fichier source main.cc :


 

  • Ecriture du fichier CMakeLists.txt:


 

Si CMake a bien trouvé le package de la bibliothèque Boost (commande 'find_package'), alors les variables 'Boost_LIBRARY_DIRS' et 'Boost_INCLUDE_DIRS' contiennent les chemins des répertoires recherchés.

 

  • Compilation  et exécution:
    • cmake  .  -G"Unix Makefiles"
    • make
    • ./bin/main

 

Pour plus d'informations, vous pouvez consulter le site officiel ou ce cours en ligne.

 

 

 

 

Démarche à suivre avant d'optimiser un code

 

  • PREMIÈRE ÉTAPE:

validez votre programme sans option d'optimisation (-O2, -O3, ...), c'est à dire : compilation, exécution et résultats tous ok.

  • DEUXIÈME ÉTAPE:

Lorsque vous ajoutez des options d'optimisation lors de la compilation (-O2, -O3), VÉRIFIEZ que les résultats obtenus après exécution sont identiques avec et sans optimisation.

 

 

Options d'optimisation de base "(issu cours CIMENT)"

 

1. Options de base On, n=0,…3

 

Il faut savoir que plus n grand: 

  • Plus l’optimisation est “sophistiquée”,
  • Plus le temps de compilation est important
  • Plus la taille du code peut devenir grande

 

Voici quelques options:

  • -O0 : désactive les optimisations
  • -O1 : optimisation des performances tout en préservant la taille du code.  Adaptée à de gros codes comprenant de nombreux branchements et des boucles peu volumineuses
  • -O2 : par défaut en l’absence de –g. Gestion globale du code, spéculation, prédiction...
  • -O3 : -O2 + optimisation plus “agressives” concernant les accès mémoire et la gestion des boucles

 

2. Option -fast

 

  • -fast : active toutes les optimisations -O3 et –ipo sur tout le code

 

3. Pour en savoir plus : Cours du CIMENT

 

CIMENT (Calcul Intensif / Modélisation / Expérimentation Numérique et Technologique)

est un projet du CPER coordonné par l'Université de Joseph Fourier de Grenoble et s'inscrit dans le cadre du pôle numérique de la Région Rhône Alpes.