Ceci est une ancienne révision du document !
Comment facilement paralléliser les codes que nous avons à disposition pour exploiter les derniers développements matériels à notre disposition (multi-coeurs, GPU) ?
Par4All exploite un outil déjà vieux (et éprouvé) analysant le code et le transformant pour permettre d'effectuer pour nous ce travail de portage : PIPS.
Ses avantages sont nombreux :
apt-get install libncurses5 libreadline6 python ipython cproto indent flex bison automake libtool autoconf libreadline6-dev python-dev swig python-ply libgmp3-dev libmpfr-dev gfortran subversion git wget libmpfr4 python-docutils tex4ht
Voici les quelques commandes pour installer Par4All dans /opt
à partir des sources.
Récupération et expansion de l'archive :
cd /tmp wget http://download.par4all.org/development/ubuntu/x86_64/2012/11/2012-11-29/par4all-1.4.3-e2355ae_src.tar.gz tar xzf /tmp/par4all-1.4.3-e2355ae_src.tar.gz cd /tmp/par4all-1.4.3_src/
Compilation & Installation dans /opt
src/simple_tools/p4a_setup.py --prefix=/opt/par4all-1.4.3 -v --jobs=4
Paramétrage du lien :
cd /opt [ -d /opt/par4all ] && mv /opt/par4all /opt/par4all-$(date '+%Y%m%d') cd /opt ln -sf par4all-1.4.3 par4all
Ensuite, il faut disposer d'un code très simple et que nous savons aisément parallélisable : un produit de matrices par exemple. Le code source le plus élémentaire que nous puissions trouver est le suivant :
#include <stdio.h> #define SIZE 2048 #define FTYPE double int main(void) { FTYPE a[SIZE][SIZE]; FTYPE b[SIZE][SIZE]; FTYPE c[SIZE][SIZE]; FTYPE trace=0.; for (unsigned int i = 0; i < SIZE; ++i) { for (unsigned int j = 0; j < SIZE; ++j) { a[i][j] = (FTYPE)(i + j); b[i][j] = (FTYPE)(i - j); c[i][j] = 0.0f; } } for (unsigned int i = 0; i < SIZE; ++i) { for (unsigned int j = 0; j < SIZE; ++j) { for (unsigned int k = 0; k < SIZE; ++k) { c[i][j] += a[i][k] * b[k][j]; } } } for (unsigned int i = 0; i < SIZE; ++i) { trace+=c[i][i]; } printf("La trace de la matrice est %.2f\n",trace); return 0; }
Si nous appelons ce code matrix.c
, pour le compiler simplement avec GCC, nous avons :
gcc -std=c99 -o matrix matrix.c
En exécutant directement matrix par /usr/bin/time ./matrix
, nous obtenons un superbe Segmentation fault
.
Command terminated by signal 11 0.00user 0.00system 0:00.00elapsed 0%CPU (0avgtext+0avgdata 376maxresident)k 0inputs+0outputs (0major+134minor)pagefaults 0swaps
Pour y remédier, nous étendons la taille de la pile (stack en langue de Shakespeare) à l'infini :
# Pour eviter les Segmentation Fault ulimit -s unlimited
L'exécution précédente de matrix par la commande /usr/bin/time ./matrix
donne alors :
La trace de la matrice est 18428734073246580736.00 152.60user 0.02system 2:32.91elapsed 99%CPU (0avgtext+0avgdata 98796maxresident)k 0inputs+0outputs (0major+24740minor)pagefaults 0swaps
Le programme s'est exécuté en 2 minutes et 32 secondes et donne une trace de 18428734073246580736.
Nous avons plusieurs niveaux d'optimisation intégré à GCC.
Si nous voulons optimiser un peu la compilation, nous utilisons les options -O2
, -mtune=native
:
gcc -std=c99 -O2 -mtune=native -o matrix-O2 matrix.c
La trace de la matrice est 18428734073246580736.00 75.10user 0.02system 1:15.26elapsed 99%CPU (0avgtext+0avgdata 98796maxresident)k 0inputs+0outputs (0major+24740minor)pagefaults 0swaps
Si nous voulons optimiser un peu la compilation, nous utilisons les options -O3
, -mtune=native
:
gcc -std=c99 -O3 -mtune=native -o matrix-O3 matrix.c
L'exécution précédente de matrix par la commande /usr/bin/time ./matrix-O3
donne alors :
La trace de la matrice est 18428734073246580736.00 39.61user 0.01system 0:39.70elapsed 99%CPU (0avgtext+0avgdata 98796maxresident)k 0inputs+0outputs (0major+24740minor)pagefaults 0swaps
La première étape consiste à charger les variables d'environnements pour ensuite utiliser la commande “magique”, p4a
# Si vous utilisez ksh ou assimiles source /opt/par4all/etc/par4all-rc.sh # Si vous utilisez csh ou assimiles source /opt/par4all/etc/par4all-rc.csh
Utilisation directe
p4a --simple -vv matrix.c -o matrix-simple
L'exécution précédente de matrix par la commande /usr/bin/time ./matrix-simple
donne alors :
La trace de la matrice est 18428734073246580736.00 40.73user 0.03system 0:40.85elapsed 99%CPU (0avgtext+0avgdata 98796maxresident)k 0inputs+0outputs (0major+24740minor)pagefaults 0swaps
Utilisation directe
p4a --openmp -vv matrix.c --fine -o matrix-openmp
Utilisation avec compilation séparée
p4a --openmp -vv matrix.c gcc -fopenmp -O3 -mtune=native -o matrix-openmp matrix.p4a.c
Utilisation directe
export CUDA_DIR=/opt/cuda p4a --cuda -vv matrix.c --fine -o matrix-cuda
Utilisation avec compilation séparée
export CUDA_DIR=/opt/cuda # Appel de Par4all p4a --fine --cuda -vv matrix.c # Compilation des sources CUDA avec le compilateur Nvidia nvcc --cuda -I$CUDA_DIR/include -DP4A_ACCEL_CUDA -I/opt/par4all-1.3/share/p4a_accel -o matrix.p4a.cpp matrix.p4a.cu nvcc --cuda -I$CUDA_DIR/include -DP4A_ACCEL_CUDA -I/opt/par4all-1.3/share/p4a_accel -o p4a_accel.cpp /opt/par4all-1.3/share/p4a_accel/p4a_accel.cu # Compilation des deux sources g++ -c -I$CUDA_DIR/include -DP4A_ACCEL_CUDA -I/opt/par4all-1.3/share/p4a_accel -Wall -fno-strict-aliasing -fPIC -O3 -o matrix.p4a.o matrix.p4a.cpp g++ -c -I$CUDA_DIR/include -DP4A_ACCEL_CUDA -I/opt/par4all-1.3/share/p4a_accel -Wall -fno-strict-aliasing -fPIC -O3 -o p4a_accel.o p4a_accel.cpp # Compilation finale de l'executable g++ -L$CUDADIR/lib64 -L$CUDADIR/lib -Bdynamic -lcudart -o matrix-cuda matrix.p4a.o p4a_accel.o # Effacement des fichiers intermédiaires inutiles rm p4a_accel.o p4a_accel.cpp
Utilisation simple passe
p4a --opencl -vvv matrix.c -o matrix-ocl
Utilisation double passe
— Emmanuel Quemener 2011/11/06 17:38