+\section{Synthèse des contraintes d'implémentation}
+Certaines de ces contraintes ont déjà été évoquées rapidement, mais il nous semble important d'en donner ici une présentation synthétique.
+\begin{enumerate}
+\item \textbf{Contiguïté}.
+Les accès aléatoires à la mémoire globale sont en règle générale très pénalisants. Toutefois, il est possible de tirer parti du cache de niveau 1 (à une dimension) en organisant les données pour que tous les threads d'un même warp accèdent à des données appartenant au même bloc de 128 octets de mémoire. Le non-respect de cette contrainte de contiguïté (\textit{coalescence}) induit des accès réalisés en plusieurs transactions serialisées et donc une perte potentiellement importante de performances.
+\item \textbf{Conflits de banques}.
+La mémoire partagée, plus rapide que la mémoire globale, peut sembler une solution évidente pour obtenir des performances élevées. Cependant, elle est physiquement organisée en 32 \og banques \fg{} de largeur 32 bits et présente elle aussi une contrainte majeure. Sur architecture Fermi, l'exécution des threads d'un warp est assurée par deux \textit{moteurs d'exécution} qui activent en parallèle chacun des deux demi-warp.
+Un \textbf{conflit de banque} se produit lorsque deux threads n'appartenant pas au même demi-warp accèdent à des données localisées dans la même banque de mémoire partagée. La transaction parallèle est alors interrompue et sérialisée.
+Ici encore, la perte de performance peut être importante, mais il peut s'avérer très complexe, coûteux, voire impossible d'organiser les données en mémoire partagée de sorte à éviter tout conflit de banque.
+\item \textbf{Branches divergentes}.
+Toute branche d'exécution divergente entraine une sérialisation des exécution des threads du warp auquel ils appartiennent. Il convient donc d'éviter cette situation et d'organiser les traitements en conséquence en privilégiant le plus souvent un découpage plus fin en plusieurs kernels élémentaires plutôt que des kernels \og lourds \fg{}.
+\item \textbf{Transferts GPU$\leftrightarrow$CPU}.
+Les transferts de données entre la mémoire globale du GPU et celle de son hôte CPU peuvent représenter l'essentiel du temps de traitement total et doivent donc être optimisées pour en réduire la fréquence et le volume des données à copier. Cela peut parfois être contradictoire avec la multiplication de petits kernels élémentaires.
+Il est toutefois possible, lorsque la séquence de traitement le permet, de réaliser des transferts en temps masqué, pendant l'exécution d'un kernel, en créant plusieurs flux d'exécution.
+\item \textbf{Partage des ressources d'un SM}
+Le paragraphe sur l'\textit{occupancy} a abordé cet aspect par un exemple. Il faut retenir que chaque SM possède des ressources mémoire (registres, mémoire partagée) que les threads des blocs logiques de la grille de calcul se partagent au cours de l'exécution d'un kernel. L'équilibre entre l'utilisation de ces ressources et le dimensionnement de la grille de calcul relève d'un compromis parfois délicat à trouver pour obtenir les meilleures performances possibles.
+\item \textbf{Les latences}. L'exécution des kernels subit l'effet de latences d'origines diverses. Les latences d'accès aux mémoires (voir Table \ref{tab-gpu-memoire}), les latences des différentes instructions arithmétiques ou encore les latences crées par l'inter dépendance d'instructions consécutives. Il est impératif de les prendre en considération et de mettre en \oe uvre des techniques adaptées pour les masquer au mieux.
+\end{enumerate}
+L'ensemble de ces aspects rend difficile la conception d'implémentations GPU rapides car rares sont les transcriptions directes d'un code CPU qui ne se heurtent pas sévèrement à l'une ou l'autre des contraintes que l'on vient d'énumérer. Les performances qui en résultent sont alors très en deça de celles attendues, voire inférieures à celles de l'implémentation CPU.