Commit 183c735b authored by CHAMONT David's avatar CHAMONT David
Browse files

Move to NatationSynchronizee

parent bf946a04
# Vectorisation avec Boost.SIMD
Cette bibliothèque a pour objectif de faciliter l'exploitation des registres vectoriels des processeurs modernes (SIMD). Actuellement distribuée au sein de NT2, elle sera proposée comme nouvelle bibliothèque à la communauté BOOST.
* prérequis : connaissance du C++ moderne.
* hauteur du plongeoir : ?.
* préinstallation : Docker, image `gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd`.
* fichiers : https://gitlab.in2p3.fr/MaitresNageurs/PiscineJI/tree/master/CalculBsimd
* maître(s) nageur(s) : [David Chamont](http://informatique.in2p3.fr/?q=user/3).
* <img src="img/david.jpeg" height=50>
---
## Vérification de votre environnement de travail
Nous vous proposons de modifier vos fichiers de code au sein du système
d'exploitation natif de votre ordinateur portable, avec vos outils d'édition
habituels, et d'utiliser une machine Docker uniquement pour compiler et exécuter
le programme résultant.
Vous devriez avoir déjà installé Docker, conformément aux instructions du [README.md](../README.md)
principal de ce projet. Vous devriez également déjà disposer de l'image `gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd`.
Si ce n'est pas le cas et que vous disposez de réseau, tapez
`docker pull gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd`.
En partant de l'hypothèse que vous éditerez vos fichiers dans le répertoire
local <LOCAL>, nous vous proposons de monter ce répertoire en tant que
`/piscine` au sein de la machine Docker.
Créez le programme suivant au sein de <LOCAL> :
```c++
// Fichier hello_world.cpp
#include <iostream>
int main()
{
std::cout<<"Hello World !"<<std::endl ;
}
```
A présent, compilez le et exécutez le au sein d'une machine Docker
`piscineri3/bsimd` :
```shell
> docker run --rm -it -v <LOCAL>:/piscine gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd /bin/bash
>> cd /piscine
>> g++ -std=c++14 hello_world.cpp -o hello_world.exe
>> ./hello_world.exe
Hello World !
>> exit
```
Vous etes prêt.
---
## La notion de bloc (pack)
Le principal élément de Boost.SIMD est la classe `boost::simd::pack`, accessible via le fichier d'entête `boost/simd/sdk/simd/pack.hpp`.
Une instance de `pack<T,N>` représente un bloc de `N` éléments de type `T`, de façon similaire à une instance de `std::array`. La principale différence, c'est que `boost::simd::pack` s'efforcera d'utiliser les registres et les instructions SIMD.
La classe `boost::simd::pack` propose de multiples constructeurs. Elle est évidemment copiable et constructible par défaut. Elle propose également de multiples méthodes d'initialisation du contenu.
Dans l'exemple ci-dessous, nous utilisons les registres vectoriels pour calculer un jeu valeurs 42. Un typedef permet d'alléger l'écriture :
``` cpp
#include <boost/simd/sdk/simd/pack.hpp>
#include <boost/simd/sdk/simd/io.hpp>
#include <boost/simd/include/functions/splat.hpp>
#include <boost/simd/include/functions/plus.hpp>
#include <boost/simd/include/functions/multiplies.hpp>
#include <iostream>
int main()
{
typedef boost::simd::pack<double> pdouble ;
int size = pdouble::static_size ;
std::cout << "pack size: " << size << std::endl ;
pdouble res ;
pdouble u(10) ;
pdouble r = boost::simd::splat<pdouble>(11) ;
res = (u + r) * 2.f ;
std::cout << res << std::endl ;
return 0 ;
}
```
Le constructeur de `u` réplique la valeur scalaire `10` dans tous les éléments du bloc, de façon équivalente à l'instruction suivante, qui réplique la valeur `11` via la fonction `splat`. On peut également intialiser les élémets d'un bloc en énumérant ses valeurs :
``` cpp
boost::simd::pack<float> r(11,11,11,11) ;
```
Ce dernier constructeur fait l'hypothèse que le nombre de valeurs fournies correspond très exactement à la taille du bloc. Dans la mesure du possible, il faut éviter de dépendre d'une taille donnée pour `boost::simd::pack`.
Une fois initialisés, les blocs peuvent être manipulés de façon similaire à des valeurs scalaires, et tous les opérateurs et fonctions mathématiques ordinaires sont disponibles, à condition d'inclure le fichier d'entête approprié. Dans notre exemple, nous incluons `plus.hpp` et `multiplies.hpp` afin de pouvoir utiliser les opérateurs + et \*.
``` cpp
res = (u + r) * 2.f ;
```
Notez que la vérification de types est plus stricte que d'habitude quand des blocs SIMD entrent en jeu. Ci-dessus, on ne peut multiplier notre somme de blocs que par `2.f` et non par `2`.
Enfin, on affiche le contenu d'un bloc à l'aide de l'opérateur `<<`, mis à disposition par le fichier d'entête `boost/simd/sdk/simd/io.hpp`.
``` cpp
std::cout << res << std::endl ;
```
---
## Compilation du code
Boost.SIMD étant uniquement fait de fichiers d'entête, sa compilation est assez simple : incluez le chemin des fichiers d'entête de Boost.SIMD et de Boost, et l'option du compilateur permettant d'activer les instructions SIMD. Par exemple, pour `gcc` :
g++ my_code.cpp -O3 -o my_code.exe -I/path/to/boostsimd/ -I/path/to/boost/ -msse2
La sortie de notre premier exemple devrait ressembler à quelque chose comme ca :
{42,42,42,42}
Jetons un coup d'oeil à l'assembleur (par exemple en utilisant `objdump`) :
movaps 0x300(%rip),%xmm0
addps 0x2e6(%rip),%xmm0
mulps 0x2ff(%rip),%xmm0
movaps %xmm0,(%rsp)
Tout va bien, nous avons bien obtenu des instructions `*ps`, et le niveau d'abstraction introduit par `boost::simd::pack` n'a pas généré d'instructions supplémentaires.
---
## Comment traiter une collection de données façon SIMD ?
Observons un nouvel exemple, qui effectue le produit terme à terme d'une grande collection de valeurs :
``` cpp
void produit( int n, const double * v1, const double * v2, double * res )
{
int i ;
for ( i=O ; i<n ; ++i )
{
res[i] = v1[i]*v2[i] ;
}
}
```
Au lieu de traiter les éléments un par un, on peut les regrouper par blocs en utilisant les `pack` de Boost.SIMD. Pour charger les données et récupérer les résultats, on s'appuiera sur `load` et store `store` :
``` cpp
...
#include <boost/simd/include/functions/load.hpp>
#include <boost/simd/include/functions/store.hpp>
using namespace boost ;
typedef simd::pack<double> pdouble ;
const int psize = pdouble::static_size ;
void produit( int n, const double * v1, const double * v2, double * res )
{
int i = 0 ;
while ( i < n )
{
// prepare packs
pdouble pv1 = simd::load<pdouble>(v1) ;
pdouble pv2 = simd::load<pdouble>(v2) ;
// computation
pdouble pres = pv1*pv2 ;
// store the result
simd::store<pdouble>(pres,res) ;
// advance to the next pack
i += psize ;
v1 += psize ;
v2 += psize ;
res += psize ;
}
}
```
---
## Exercice
Le fichier `simd.cpp` est une adaptation de notre exemple habituel de calculs complexes... réécrit sans complexes, qui ne sont pas encore pleinement supportés par Boost.SIMD. Modifiez le pour faire les calculs principaux grâce aux registres vectoriels du processeur. Testez de la façon habituelle :
./simd_make.sh simd 32768 50000
---
## Sortie de bain
Merci d'envoyer quelques commentaires à l'auteur :
* Environnement de travail opérationnel ? oui [ ], non [ ]
* Vous aviez les pre-requis ? oui [ ], non [ ]
* Comment jugez-vous la difficulté du plongeon ? trop simple [ ], adapté [ ], trop compliqué [ ]
* Durée du plongeon ? trop court [ ], 15-20 minutes [ ], trop long [ ]
* Pourquoi avez-vous choisi ce plongeon ? :
* Signalement de typos, erreurs, etc :
* Commentaires libres :
---
## Sources
* [boost.simd @ GitHub](https://github.com/NumScale/boost.simd)
* [bSIMD @ NumScale](https://developer.numscale.com/bsimd/)
* [boost.simd @ Google Groups](https://groups.google.com/forum/#!forum/boost-simd)
---
© *CNRS 2016*
*Assemblé et rédigé par David Chamont, cette œuvre est mise à disposition selon les termes de la*
*[Licence Creative Commons - Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International](http://creativecommons.org/licenses/by-nc-sa/4.0/)*
FROM gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculflottant
# Ensure use of bash
SHELL ["/bin/bash","-c"]
# boost
RUN export BOOST_VERSION="1.62.0" && export BOOST_VERSION_=${BOOST_VERSION//./_} && \
wget -O boost-${BOOST_VERSION_}.tar.gz http://downloads.sourceforge.net/project/boost/boost/${BOOST_VERSION}/boost_${BOOST_VERSION_}.tar.gz && \
tar xf boost-${BOOST_VERSION_}.tar.gz && \
cd boost_${BOOST_VERSION_}/ && \
./bootstrap.sh --prefix=/opt/boost/ --with-libraries=program_options,test && \
./b2 install && \
cd .. && rm boost-${BOOST_VERSION_}.tar.gz && rm -r boost_${BOOST_VERSION_}
ENV BOOST_ROOT /opt/boost
# bSIMD
RUN git clone https://github.com/NumScale/boost.simd.git -b master /opt/boost.simd
ENV BOOST_SIMD_ROOT /opt/boost.simd
# Tuto files
ADD src /srcls
#!/bin/bash
rm -rf src
cp -rf ../src src
docker build -t gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd .
rm -rf src
docker push gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd
#!/bin/bash
docker run -it --rm -w /src gitlab-registry.in2p3.fr/maitresnageurs/piscineji:calculbsimd /bin/bash
#!/usr/bin/env sh
boost=${BOOST_ROOT}
boost_simd=${BOOST_SIMD_ROOT}
prog=${1}
shift
rm -f ${prog}.exe \
&& icpc -std=c++11 -mavx -wd1595 -I${BOOST_ROOT}/include -I${BOOST_SIMD_ROOT}/include ${prog}.cpp -o ${prog}.exe \
&& time ./${prog}.exe $*
#include <iostream>
#include <iomanip>
#include <cassert>
#include <cstdlib>
#include <cmath>
using namespace std ;
// generates a random complex number on the complex unit circle
void random_dcmplx( double & real, double & imag )
{
int r = rand() ;
double d = ((double) r)/RAND_MAX ;
double e = 2*M_PI*d ;
real = cos(e) ;
imag = sin(e) ;
}
// writes the array of n doubles in x
void write_numbers ( int n, double * real, double * imag )
{
for(int i=0; i<n; i++)
std::cout << scientific << std::setprecision(4)
<< "x[" << i << "] = ( " << real[i] << " , " << imag[i] << ")\n";
}
// global product, to check result
void global_product( int n, double * real, double * imag )
{
double zreal_tmp ;
double zreal = 1.0 ;
double zimag = 0.0 ;
for(int i=0; i<n; i++)
{
zreal_tmp = zreal*real[i] - zimag*imag[i] ;
zimag = zreal*imag[i] + zimag*real[i] ;
zreal = zreal_tmp ;
}
std::cout
<< scientific << std::setprecision(4)<<"x*... = ( "
<< zreal << " , " << zimag << ")\n" ;
}
// for arrays x and y of length n, on return y[i] equals x[i]**d
void compute_powers ( int n, double * xreal, double * ximag, double * yreal, double * yimag, int d )
{
for(int i=0; i < n; i++) // y[i] = pow(x[i],d); pow is too efficient
{
double rreal_tmp ;
double rreal = 1.0 ;
double rimag = 0.0 ;
for (int j=0; j < d; j++)
{
rreal_tmp = rreal*xreal[i] - rimag*ximag[i] ;
rimag = rreal*ximag[i] + rimag*xreal[i] ;
rreal = rreal_tmp ;
}
yreal[i] = rreal ;
yimag[i] = rimag ;
}
}
// programme principal
int main ( int argc, char * argv[] )
{
assert(argc==4) ;
int dim = atoi(argv[1]) ;
int degree = atoi(argv[2]) ;
int verbosity = atoi(argv[3]) ;
// prepare arrays
double * const inputr = new double [dim] ;
double * const inputi = new double [dim] ;
double * const outputr = new double [dim] ;
double * const outputi = new double [dim] ;
// generate input
srand(20120203) ; //srand(time(0));
for(int i=0; i<dim; i++)
random_dcmplx(inputr[i],inputi[i]) ;
if (verbosity)
{ write_numbers(dim,inputr,inputi) ; }
global_product(dim,inputr,inputi) ;
compute_powers(dim,inputr,inputi,outputr,outputi,degree) ;
if (verbosity)
{
std::cout<<"power : "<<degree<<std::endl ;
write_numbers(dim,outputr,outputi) ;
}
global_product(dim,outputr,outputi) ;
delete [] inputr ;
delete [] inputi ;
delete [] outputr ;
delete [] outputi ;
return 0 ;
}
#include <boost/simd/sdk/simd/pack.hpp>
#include <boost/simd/include/functions/load.hpp>
#include <boost/simd/include/functions/store.hpp>
#include <boost/simd/include/functions/plus.hpp>
#include <boost/simd/include/functions/multiplies.hpp>
#include <iostream>
#include <iomanip>
#include <cassert>
#include <cstdlib>
#include <cmath>
using namespace std ;
// generates a random complex number on the complex unit circle
void random_dcmplx( double & real, double & imag )
{
int r = rand() ;
double d = ((double) r)/RAND_MAX ;
double e = 2*M_PI*d ;
real = cos(e) ;
imag = sin(e) ;
}
// writes the array of n doubles in x
void write_numbers ( int n, double * real, double * imag )
{
for(int i=0; i<n; i++)
std::cout << scientific << std::setprecision(4)
<< "x[" << i << "] = ( " << real[i] << " , " << imag[i] << ")\n";
}
// global product, to check result
void global_product( int n, double * real, double * imag )
{
double zreal_tmp ;
double zreal = 1.0 ;
double zimag = 0.0 ;
for(int i=0; i<n; i++)
{
zreal_tmp = zreal*real[i] - zimag*imag[i] ;
zimag = zreal*imag[i] + zimag*real[i] ;
zreal = zreal_tmp ;
}
std::cout
<< scientific << std::setprecision(4)<<"x*... = ( "
<< zreal << " , " << zimag << ")\n" ;
}
// for arrays x and y of length n, on return y[i] equals x[i]**d
void compute_powers ( int n, double * xreal, double * ximag, double * yreal, double * yimag, int d )
{
using namespace boost ;
typedef simd::pack<double> pdouble ;
static const int psize = pdouble::static_size ;
int i = 0 ;
while ( i < n )
{
pdouble prreal(1.0) ;
pdouble primag(0.0) ;
pdouble ptmp(0.0) ;
// load an simd set of values
pdouble pxreal = simd::load<pdouble>(xreal) ;
pdouble pximag = simd::load<pdouble>(ximag) ;
// Computation
for ( int j=0 ; j < d ; j++ )
{
ptmp = prreal*pxreal - primag*pximag ;
primag = prreal*pximag + primag*pxreal ;
prreal = ptmp ;
}
// store the result
simd::store<pdouble>(prreal,yreal) ;
simd::store<pdouble>(primag,yimag) ;
// advance to the next simd vector
i += psize ;
xreal += psize ;
ximag += psize ;
yreal += psize ;
yimag += psize ;
}
}
// programme principal
int main( int argc, char * argv[] )
{
assert(argc==4) ;
int dim = atoi(argv[1]) ;
int degree = atoi(argv[2]) ;
int verbosity = atoi(argv[3]) ;
// prepare arrays
double * const inputr = new double [dim] ;
double * const inputi = new double [dim] ;
double * const outputr = new double [dim] ;
double * const outputi = new double [dim] ;
// generate input
srand(20120203) ; //srand(time(0));
for(int i=0; i<dim; i++)
random_dcmplx(inputr[i],inputi[i]) ;
if (verbosity)
{ write_numbers(dim,inputr,inputi) ; }
global_product(dim,inputr,inputi) ;
compute_powers(dim,inputr,inputi,outputr,outputi,degree) ;
if (verbosity)
{
std::cout<<"power : "<<degree<<std::endl ;
write_numbers(dim,outputr,outputi) ;
}
global_product(dim,outputr,outputi) ;
delete [] inputr ;
delete [] inputi ;
delete [] outputr ;
delete [] outputi ;
return 0 ;
}
# Profilage numérique avec Cadna
C++ est statiquement et fortement typé : le programmeur doit préciser au compilateur
le type de toutes les variables qu'il utilise. C'est généralement considéré comme nécessaire
pour garantir le bon fonctionnement des programmes de très grande taille, mais cela peut
conduire à un code difficile à écrire comme à lire. Pour alléger la tâche du
programmeur sans renoncer au typage, C++11 autorise le compilateur à deviner
certains types dans les cas les moins ambigus. Ce mécanisme, appelé **inférence de type**,
s'appuie en C++ sur le mot-clef `auto`.
* prérequis : bases du calcul flottant.
* hauteur du plongeoir : 3m.
* préinstallation : Docker, image `piscineri3/calculcadna`.
* fichiers : https://gitlab.in2p3.fr/MaitresNageurs/PiscineJI/tree/master/CalculCadna
* maître(s) nageur(s) : [David Chamont](http://informatique.in2p3.fr/?q=user/3).
* <img src="img/david.jpeg" height=50>
---
## Vérification de votre environnement de travail
Nous vous proposons de modifier vos fichiers de code au sein du système
d'exploitation natif de votre ordinateur portable, avec vos outils d'édition
habituels, et d'utiliser une machine Docker uniquement pour compiler et exécuter
le programme résultant.
Vous devriez avoir déjà installé Docker et VirtualBox, conformément aux instructions
du [README.md](../README.md) principal de ce projet. Vous devriez également déjà
disposer de l'image `piscineri3/gcc61:3`. Si ce n'est pas le cas et que vous disposez
de réseau, tapez `docker pull piscineri3/gcc61:3`.
En partant de l'hypothèse que vous éditerez vos fichiers dans le répertoire
local <LOCAL>, nous vous proposons de monter ce répertoire en tant que
`/piscine` au sein de la machine Docker.
Créez le programme suivant au sein de <LOCAL> :
```c++
// Fichier hello_world.cpp
#include <iostream>
int main()
{
std::cout<<"Hello World !"<<std::endl ;
}
```
A présent, compilez le et exécutez le au sein d'une machine Docker
`piscineri3/gcc61:3` :
```shell
> docker run --rm -it -v <LOCAL>:/piscine piscineri3/gcc61:3 /bin/bash
>> cd /piscine
>> g++ -std=c++14 hello_world.cpp -o hello_world.exe
>> ./hello_world.exe
Hello World !
>> exit
```
Vous etes prêt.
---
## Inférence de type : le mot-clef `auto`
C++ est statiquement et fortement typé : le programmeur doit préciser au
compilateur le type de toutes les variables qu'il utilise, ce qui peut conduire
à un code difficile à écrire comme à lire.
```c++
// Fichier auto_introduction_1.cpp
#include <iostream>
#include <vector>
void affiche( std::vector<int> & col )
{
std::vector<int>::iterator itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
std::vector<int> v {1,2,3,4,5} ;
affiche(v) ;
}
```
Traditionnellement, on allège les notations en ayant recours aux alias
de types :
```c++
// Fichier auto_introduction_2.cpp
#include <iostream>
#include <vector>