Commit 484b7ee5 authored by CHAMONT David's avatar CHAMONT David
Browse files

reorganize

parent 8999ed60
# bSIMD
EN CONSTRUCTION
Single instruction, multiple data (SIMD) instructions or multimedia extensions have been available for many years. They are designed to significantly accelerate code execution, however they require expertise to be used correctly, depends on non-uniform compiler support, the use of low-level intrinsics, or vendor-specific libraries.
bSIMD is a C++ library which aims to simplify the error-prone process of developing applications using SIMD instructions sets. bSIMD is designed to seamlessly integrate into existing projects so that you can quickly and easily start developing high performance, portable and future proof software.
* prérequis : connaissance du C++ moderne.
* hauteur du plongeoir : ?.
* préinstallation : Docker, image `piscineri3/gcc61:3`.
* 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 `piscineri3/bsimd`. Si ce n'est pas le cas et
que vous disposez de réseau, tapez `docker pull piscineri3/bsimd`.
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 piscineri3/bsimd /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>
typedef std::vector<int> Collection ;
void affiche( Collection & col )
{
Collection::iterator itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
Collection v {1,2,3,4,5} ;
affiche(v) ;
}
```
Cependant, on peut juger inutile de devoir préciser le type de la variable
`itr`, qui est le même que celui de l'expression servant à calculer
sa valeur initiale (`col.begin()`).
Dans ce cas de figure, C++11 permet d'utiliser le mot-clef `auto` à la place du type
de la variable, indiquant ainsi au compilateur de réutiliser le type de la valeur
initiale.
```c++
// Fichier auto_introduction_3.cpp
#include <iostream>
#include <vector>
typedef std::vector<int> Collection ;
void affiche( Collection & col )
{
auto itr = col.begin() ;
while (itr!=col.end())
{
std::cout<<i<<std::endl ;
++itr ;
}
}
int main()
{
Collection v {1,2,3,4,5} ;
affiche(v) ;
}
```
Attention : lorsque vous utilisez `auto`, évitez d'utiliser en même temps l'initialisation
par accolades introduite par C++11 . Il existe en effet une possibilité d'interaction indésirable
entre ces deux nouveautés (`auto` risque de retenir `std::initializer_list` comme
type). Il est prévu de résoudre ce problème dans la norme C++17.
On notera que `auto`, lorsqu'il est utilisé avec des patrons de fonction, permet
d'implémenter des choses impossibles auparavant :
```c++
// Fichier auto_introduction_4.cpp
#include <iostream>
template< typename LHS, typename RHS >
void affiche_division( LHS lhs, RHS rhs )
{
auto res = lhs/rhs ;
std::cout<<res<<std::endl ;
}
int main()
{
affiche_division(3.,2) ;
}
```
Tant que l'on ne connait pas `T1` et `T2`, impossible de connaitre le type de `lhs/rhs`.
Seul `auto` permet de déclarer `res` avec le type qui préservera toute la précision
utile du calcul.
---
## Déclaration à droite du type de retour
Une nouvelle façon de déclarer le type de retour s'appuie également sur
le mot-clef `auto`, placé avant la signature de la fonction, et l'opérateur `->`
placé après la signature :
```c++
// Fichier auto_return_1.cpp
#include <iostream>
auto division( double lhs, double rhs ) -> double
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
Certains programmeurs désapprouve le choix du comité de normalisation de
réutiliser le mot-clef `auto` pour ce mécanisme. En effet, il n'y
a ici aucune inférence de type.
---
## Le mot-clé `decltype`
Plutôt que de laisser tout le travail au compilateur, on peut également
récupérer le type d'une expression quelconque grâce au mot-clef `decltype`,
et s'en servir pour déclarer une nouvelle variable :
```c++
int n ;
double x ;
decltype(2*x+n) z ; // z sera du type de 2*x+n, soit ici double
.....
std::map<int,std::string> m1 ;
decltype(m1) m2 ; // m2 aura le même type que m1 ;
```
En réalité, `decltype` est utilisable partout ou le compilateur attend un nom
de type, par exemple dans la définition d'un alias :
```c++
// Fichier auto_decltype_1.cpp
#include <iostream>
template< typename LHS, typename RHS >
void affiche_division( LHS lhs, RHS rhs )
{
typedef decltype(lhs/rhs) Resultat ;
Resultat res = lhs/rhs ;
std::cout<<res<<std::endl ;
}
int main()
{
affiche_division(3.,2) ;
}
```
Ou encore :
```c++
// Fichier auto_decltype_2.cpp
#include <iostream>
template< typename LHS, typename RHS >
auto division( LHS lhs, RHS rhs ) -> decltype(lhs/rhs)
{ return lhs/rhs ; }
int main()
{
std::cout<<division(3.,2)<<std::endl ;
}
```
Dans l'exemple ci-dessus, nous n'aurions pas pu placer `decltype(lhs/rhs)`
à la place de `auto`, à la gauche du nom de fonction, car à ce stade
les arguments `lhs` et `rhs` ne sont pas encore connus. Il est donc
nécessaire d'utiliser la déclaration à droite du type de retour...
ou ne rien déclarer du tout, car le compilateur peut déduire le
type de retour à partir de l'instruction `return`.
---
## `decltype` n'est pas `auto`
Attention : pour conclure avec le mot-clef `decltype`, notons qu'
**il n'est pas complètement équivalent** à `auto`. En effet il prend
l'exact type de l'expression passée en argument, sans retirer aucun
`const` et aucun `&`.
Si vous avez envie d'utiliser l'inférence de type, mais sans toucher
au type original d'aucune manière... utilisez `decltype(auto)` à la
place de `auto`.
---
## Afficher le type d'une variable
Lorsqu'interviennent `auto` et `decltype`, hors des cas triviaux,
il peut devenir difficile de connaitre à coup sûr le type d'une
variable. Comment s'en assurer ?
Pour soutirer certaines informations à un compilateur C++, il est courant
de lui fournir une instruction volontairement erronée, écrite de telle sorte
que le message d'erreur contiennent l'information recherchée.
Ci-dessous, nous déclarons un patron de classe qui n'est jamais défini, ce qui permet
indirectement d'en savoir plus sur le type déduit par auto pour x et y :
```c++
// Fichier auto_td_1.cpp
template <typename T> ;
class TypeDisplayer ;
int main()
{
const int the_answer = 42 ;
auto x = the_answer ;
auto y = & the_answer ;
TypeDisplayer<decltype(x)> x_type ; // errors containing
TypeDisplayer<decltype(y)> y_type ; // x's and y's types
}
```
Par ailleurs, certains outils d'édition de code tentent de vous informer de
façon interactive sur les types associés aux utilisations de `auto`
dans votre code, avec plus ou moins de succès.
Vous pourriez être tentés d'utiliser `typeid` et `std::type_info::name`, mais ces fonctions
sont requises d'éliminer les références, les const, et présentent souvent leur résultat
sous une forme mutilée (mangled).
Si vous voulez mettre des instructions d'affichage de type dans votre code, la piste la plus
aboutie est sans doute la bibliothèque Boost TypeIndex.
---
## Un risque d'utilisation excessive
L'utilisation de l'inférence de type apporte beaucoup de bénéfices :
* une écriture simplifiée,
* un algorithme plus lisible,
* une protection contre les fautes de frappes dans les noms de types,
* une plus grande généricité, donc un remaniement et une évolution plus facile du code.
Devant le succès et l'efficacité d'`auto`, le comité C++ a cherché d'élargir autant que possible ses
usages possibles. Cependant, nous conclurons sur une mise en garde : tout à la joie de ne plus avoir à expliciter
tous les types, on peut couvrir son code d'`auto`, même lorsque le type d'une variable est parfaitement
connu, déterministe et rapide à taper. Ceci pour tendre vers un style de programmation ou tous les types
sont déduits par le compilateur.
Il faut être bien conscient que le code résultant peut devenir moins lisible, car il y est plus difficile
de visualiser le type d'une variable, donc de voir et corriger les erreurs de type. De plus, l'utilisation
excessive d'`auto` peut briser une abstraction, en amenant un compilateur à accepter un objet d'un type
incorrect dans l'initialisation d'une variable, ou comme paramètre d'une fonction, pour ne s'en plaindre
que plus tard (lorsque le code en fait un usage incohérent).
Enfin, le mot-clé `auto` peut apparaitre dans de nombreux contextes, chacun utilisant des règles
d'inférence subtilement différentes et complexes, par exemple lors de la combinaison avec l'initialisation
par accolades. Il est aisé de commettre une erreur dès que l'on sort du cas trivial d'une nouvelle
variable avec une valeur initiale.
En toute chose, mesure est bonne...
---
## Jeu
Dans toutes les questions ci-dessous, nous vous invitons à deviner le type de la
variable `x`, ou bien le type `X`, et à vérifier votre réponse en utilisant la technique
de "Type Displayer" décrite ci-dessus.
Nous commencons par les cas triviaux et les usages simples et "sains". Nous
terminons par les cas complexes et les pièges, afin de vous invitez à la prudence
et à éviter un usage "excessif" de ce nouveau jouet.
**Question 1**
```c++
const int the_answer = 42 ;
auto x = the_answer ;
```
**Question 2**
```c++
const int the_answer = 42 ;
const int * px = &the_answer ;
auto x = px ;
```
---
## 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
* [bSIMD](https://developer.numscale.com/bsimd/)
---
© *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 piscineri3/calculflottant
ADD . /work
WORKDIR /work
# 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 v4.17.3.0 /opt/boost.simd
ENV BOOST_SIMD_ROOT /opt/boost.simd
#!/bin/bash
docker build -t piscineri3/calculbsimd .
#!/bin/bash
docker run -it --rm -v $PWD:/work -w /work piscineri3/calculbsimd
#!/bin/bash
g++ -std=c++14 auto_${1}_${2}.cpp -o auto_${1}_${2}.exe && ./auto_${1}_${2}.exe
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment