]> AND Private Git Repository - these_gilles.git/blob - THESE/Chapters/chapter5/chapter5.tex
Logo AND Algorithmique Numérique Distribuée

Private GIT Repository
v1.2 19 décembre
[these_gilles.git] / THESE / Chapters / chapter5 / chapter5.tex
1 \section{Introduction}
2 Au cours de nos expérimentations, en particulier concernant le débruitage par lignes de niveaux décrit dans le chapitre précédent, nous avons cherché à comparer les performances d'un certain nombre d'algorithmes de filtrage portés sur GPU. Comme nous l'avons dit dans le chapitre \ref{ch-filtrage}, il s'est avéré que le filtre médian n'avait pas fait l'objet de beaucoup de publications. On a tout de même recensé quelques  implémentations intéressantes des algorithmes BVM et PCMF, ainsi que l'existence d'une solution commerciale libJacket/Arrayfire (se reporter au paragraphe \ref{sec-median}).
3
4 Les performances annoncées pour des fenêtres de petite taille comme 3$\times$3 pouvent atteindre jusqu'à 180 millions de pixels traités à la seconde dans le cas d'Arrayfire. En regard du petit nombre d'opérations à effectuer pour sélectionner la valeur médiane dans une fenêtre 3$\times$3, il nous a semblé que ces débits étaient très en deçà des possibilités des GPUs employés.
5
6 Un rapide prototypage a conforté cette idée et nous a conduit à chercher plus avant une technique d'implémentation du filtre médian qui exploite pleinement les capacités de nos GPU.
7
8 \section{Les transferts de données}
9
10 Le chapitre \ref{ch-GPU}, présentant l'architecture et les caractéristiques principales des GPUs, donne également la liste et les spécificités des types de mémoire accessibles par un kernel. Lorsqu'il s'agit de stocker des volumes importants de données, comme les images d'entrée et de sortie, les alternatives sont assez limitées. En effet, le seul espace mémoire suffisamment important est celui la mémoire dite globale, malheureusement la plus lente. On dispose cependant de plusieurs modes pour y accéder, comme la déclaration de textures, qui offre un mécanisme de cache 2D permettant d'augmenter assez nettement les débits en lecture dans le cas d'accès au voisinage d'une donnée.
11 Dans le cadre de nos travaux, cette mémorisation sous forme de texture s'est montrée la plus performante pour les images d'entrée.
12
13 Les images de sortie filtrées sont produites en mémoire globale standard, hors texture, puis copiées vers une zone de mémoire de l'hôte (CPU) dont les pages sont réservées à l'avances et verrouillées, ce qui évite les pertes de performances liées aux défauts de page. L'algorithme \ref{algo-median-memcpy} synthétise ces pratiques en introduisant aussi les notations pour la suite. 
14 Cet emploi de mémoire que l'on qualifiera dorénavant de \og non paginée \fg{}, apporte un gain de temps important dans les transferts même s'il peut aussi s'avérer limitant lorsqu'il s'agit de traiter de très grands volumes de données. Les quantités de mémoire vive dont disposent les ordinateurs modernes permettent cependant de traiter sans restriction des images de plusieurs centaines de millions de pixels. Nos essais ont été conduits avec des images d'au maximum 100~MP.
15
16 \begin{algorithm}
17 %\SetNlSty{textbf}{}{:}
18 \footnotesize
19  allocation et affectation en mémoire CPU \textbf{h\_img\_in}\;
20  allocation de mémoire CPU non-paginée \textbf{h\_img\_out}\;
21  allocation de mémoire globale GPU  \textbf{d\_img\_out}\;
22  allocation de mémoire texture GPU \textbf{tex\_img\_in}\;
23  copie image de \textbf{h\_img\_in} vers \textbf{tex\_img\_in}\; 
24  kernel\kl gridDim,blockDim\kr\tcc*[f]{sortie dans d\_img\_out}\;
25  copie image de sortie de \textbf{d\_img\_out} vers \textbf{h\_img\_out}\;
26 \caption{Gestion des transferts mémoire vers et depuis le GPU.}
27 \label{algo-median-memcpy}
28 \end{algorithm}
29
30
31 Ces choix concernant les types de mémoire employés sont un facteur déterminant de la performance globale de l'implémentation. Cela sera confirmé par les mesures présentées à la fin de ce chapitre, mais une première expérience permet de s'en convaincre : le kernel médian 3$\times$3 d'Arrayfire, aimablement mis à disposition par l'un des développeurs, voit son débit global pratiquement doublé lorsqu'on remplace ses accès mémoire par la combinaison texture/non-paginée que l'on vient de présenter.
32  
33 Le tableau \ref{tab-median-memcpy} donne le détail des temps de transfert pour quelques tailles usuelles d'images en niveaux de gris, codés en 8 ou 16 bits, et compare les temps globaux avec ceux mesurés lorsque l'on utilise uniquement la mémoire globale. L'impact du choix de la configuration mémoire est évident, avec des gains constatés de 15\% à 75\%.
34
35 \begin{table}[ht]
36 \renewcommand{\arraystretch}{1.5}
37 \centering
38 {\scriptsize
39 \begin{tabular}{cccccc}
40 \toprule
41  \shortstack{Dimension\\(pixels)} & \shortstack{Profondeur\\(bits)} & \shortstack{CPU $\rightarrow$ GPU\\(ms)}& \shortstack{GPU $\rightarrow$ CPU\\(ms)}& \shortstack{\textbf{Total}\\(ms) }&\shortstack{\textbf{Mém. globale}\\(ms) } \\
42 \midrule
43 \multirow{2}*{512$\times$512} &8    &0.08 &0.06 &\textbf{0.14}&0.23 \\
44                               &16   &0.14 &0.10 &\textbf{0.24}&0.42 \\
45 \midrule
46 \multirow{2}*{1024$\times$1024}& 8  &0.24 &0.19 &\textbf{0.43}&0.81 \\
47                                & 16 &0.45 &0.35 &\textbf{0.80}&1.23 \\
48 \midrule
49 \multirow{2}*{2048$\times$2048}& 8  &0.85 &0.68 &\textbf{1.53}&2.15 \\
50                                & 16& 1.59& 1.32&\textbf{2.91}&3.83 \\
51 \midrule
52 \multirow{2}*{4096$\times$4096}& 8  &3.27 &2.61 &\textbf{5.88}&7.10 \\
53                                & 16 &6.21 &5.21 &\textbf{11.42}&13.16 \\
54 \bottomrule
55 \end{tabular}}  
56 \caption{Temps de transfert vers et depuis le GPU, en fonction de la dimension de l'image et de la profondeur des niveaux de gris. La colonne ``Mémoire globale'' donne les temps mesurés lorsque cette seule mémoire est employée.}
57 \label{tab-median-memcpy}
58 \end{table}
59
60
61 \section{Utilisation des registres}
62
63 En traitement d'image, les filtres médians sont beaucoup employés avec des tailles de fenêtres modestes comme pré-traitement, éventuellement itératif, ou bien avec de grandes tailles de fenêtres pour de l'estimation d'intensité d'arrière plan. Les taille intermédiaires, de l'ordre de quelques dizaines de pixels, ne sont à notre connaissance pas employées.
64
65 Un filtre médian de petite taille ne réalise que peu d'opérations, sans complexité de surcroît, et doit donc atteindre des niveaux de performances élevés.
66 Le cadre général des traitements sur GPU présenté au paragraphe \ref{sec-bilateral} n'est alors plus pertinent, pour deux raisons :
67 \begin{enumerate}
68 \item La phase de pré-chargement des données nécessaires en mémoire partagée prend du temps et la lecture se faisant depuis la mémoire texture, elle est soumise aux latences qui lui sont attachées.
69 \item l'utilisation en lecture/écriture des données en mémoire partagée, outre le fait qu'elle puisse être contraignante en terme de motifs d'accès, n'atteint pas les débits permis par les registres individuels à la disposition des threads.
70 \end{enumerate}
71
72 Il est ainsi clair que la chaîne de traitement la plus performante consiste à ne faire qu'une lecture en texture par pixel puis  d'effectuer les calculs en registres. Les limites  de ce schéma général sont le nombre de registres disponibles, par thread et par bloc. Si on dépasse ces limites, le compilateur déporte les variables en mémoire locale très peu performante. 
73 Sans aller au delà de la limite, l'utilisation de trop nombreux registres va mécaniquement limiter le nombre de threads effectivement exécutés en parallèle par le GPU et bien souvent grever la performance du kernel. Un compromis est donc à définir entre la recherche de vitesse par l'emploi des registres et le ralentissement que provoque l'usage d'un trop grand nombre de ces derniers.
74
75 Prenons l'exemple d'un kernel qui ferait usage d'un total de 20 registres par thread et que l'on exécuterait par blocs de 128 threads. La limite des 63 registres par thread n'est évidemment pas atteinte, ni celle des 32K par bloc avec seulement $128 \times 20 = 2560$ registres par bloc de 128 threads. Dans ce cas, le GPU pourra exécuter en parallèle $32K/2560 = 12$ blocs, soit 1536 threads, ce qui représente le maximum possible et permet d'envisager un bon niveau de performance.
76
77 En revanche, si le même kernel utilise maintenant 24 registres, le GPU ne pourra plus exécuter en parallèle que 1280 threads sur les 1536 techniquement possibles. Une perte de performance est alors à craindre.  
78
79 De ce point de vue, l'architecture Fermi, et en particulier le modèle C2070, ne proposant que 63 registres par thread, représente une régression par rapport à la génération précédente avec par exemple le GPU C1060 et ses 128 registres par thread. 
80
81
82 \subsection{La sélection de la valeur médiane}
83
84 Dans le cas des filtres médians à petite fenêtre, on peut envisager d'attribuer un registre par valeur à trier. Dans ce cas, un médian 3$\times$3 emploiera 9 registres par thread, et cette méthode pourra théoriquement s'appliquer jusqu'au médian 7$\times$7 sur C2070 et 11$\times$11 sur C1060.
85 Comme la recherche de performance impose de rationaliser l'utilisation des registres, nous nous sommes orientés vers l'algorithme dit \textit{forgetful selection} (sélection par oubli) qui évite d'avoir recours à cette cardinalité de \og un registre pour un pixel\fg{} de la fenêtre (\cite{medianggems5}).
86
87 Cette méthode  de  \og sélection par oubli\fg{} est illustrée en figure \ref{fig-median-ffs3-a} par l'exemple de la sélection de la médiane parmi 9 valeurs. Plus généralement, il s'agit de 
88 \begin{enumerate}
89 \item former une liste initiale de $R_n$ valeurs prises parmi les $n=k\times k$ valeurs de la fenêtre du filtre,
90 \item identifier, puis éliminer de la liste la plus petite et la plus grande valeur,
91 \item insérer dans la liste une nouvelle valeur parmi celles non encore intégrées,
92 \item reprendre au point 2, et ce jusqu'à ce qu'il ne reste plus de valeur non utilisée. La médiane est alors la valeur restant dans la liste.
93 \end{enumerate}
94
95 Cet algorithme nécessite un nombre constant d'étapes, égal à $n - \lceil\frac{n}{2}\rceil$, ce qui devrait assurer une charge équivalente pour tous les threads. Cependant, il existe un léger déséquilibre dû au nombre d'opérations requis par l'identification des \textit{extrema} qui dépend des valeurs dans chaque liste. Cette variabilité, n'implique pas de branches d'exécution divergentes, et n'induit pas de perte de performances.    
96
97 Nous avons par ailleurs choisi de fixer le nombre $R_n$ de valeurs figurant initialement dans la liste, comme le plus petit nombre permettant de réaliser la sélection de la médiane. On obtient cette valeur limite en considérant qu'à chaque phase d'élimination des extrema, il faut garantir que la  médiane globale n'est pas éliminée. Or, la définition de la médiane indique que dans la liste triée complète, on trouve autant de valeurs dont l'indice est supérieur à celui de la médiane que de valeurs dont l'indice lui est inférieur. Sachant que les fenêtres des filtres comportent toujours un nombre impair de valeurs, la condition suffisante pour garantir la sélection est donc que le nombre de valeurs non-intégrées dans la liste initiale soit inférieur au nombre de valeurs d'indice supérieur (ou inférieur) à la médiane dans la liste complète triée, soit 
98 $$R_{n}=\lceil \frac{n}{2}\rceil+1$$
99
100 Cette valeur de $R_n$ représente donc aussi le nombre minimum de registres nécessaires à la sélection par oubli, ce qui permet de reculer la limite de taille admissible pour le filtre médian avec 9$\times$9 pour le GPU C2070.
101 \begin{figure}
102    \centering
103    \includegraphics[height=7cm]{Chapters/chapter5/img/forgetful_selectionb.jpg}
104    \caption{Application de la sélection de médiane par oubli à une fenêtre de  $3\times 3$ pixels. }
105    \label{fig-median-ffs3-a}
106 \end{figure}
107
108
109 \subsection{Masquage des latences}
110
111 Les lectures en texture ainsi que les écritures en mémoire globale sont soumises à des latences que nous avons déjà détaillées au chapitre \ref{ch-GPU}. La mémoire texture bénéficie d'un cache permettant d'optimiser les lectures dans un voisinage à deux dimensions. Cela permet de réduire nettement les latences apparentes lors de l'accès aux éléments de la fenêtre du filtre. L'algorithme que nous proposons ne requiert qu'une lecture par élément de la fenêtre, dont la taille est assez petite pour que tous les éléments soient mis en cache. Aucune latence superflue n'est donc générée à la lecture.
112
113 Un autre moyen de réduire la latence moyenne constatée d'une séquence d'instructions est d'augmenter le niveau d'ILP (Instruction Level Parallelism ou parallélisme d'instructions). On cherche pour cela à réduire autant que possible la dépendance entre instructions successives au sein d'un kernel, de sorte à ne pas forcer les pipelines d'instructions des SMs à se vider. Nous avons appliqué ce principe à la phase d'identification des extrema de la liste en arrangeant les instructions élémentaires de permutation de sorte à éloigner au maximum les instructions inter-dépendantes.  L'exemple de la figure \ref{fig-median-ffs3-b} montre la séquence des permutations conditionnelles permettant l'identification des extrema lors de la première étape de sélection d'un filtre médian 5$\times$5. On retrouve les $R_n=14$ éléments de la liste initiale en haut de la figure, et la même liste au bas avec la valeur minimale à gauche et la valeur maximale à droite. Les séquences d'instructions indépendantes étant séparées par les lignes pointillées horizontales.
114
115 \begin{figure}
116    \centering
117    \includegraphics[height=5cm]{Chapters/chapter5/img/bitonic.png}
118    \caption{Première étape d'identification des extrema pour un filtre 5$\times$5, avec maximisation de l'ILP (Instruction Level Parallelism) pour l'identification des extrema.}
119    \label{fig-median-ffs3-b}
120 \end{figure}
121
122 Enfin, il est également possible de réduire la latence moyenne d'accès à la mémoire globale en faisant en sorte que chaque thread produise, non pas la valeur de sortie d'un seul pixel, mais de plusieurs, et ce par autant d'écritures immédiatement consécutives, seule la première de la série générant une latence. Pour que l'application de ce principe produise l'effet attendu, il faut tout de même garantir la contiguïté des accès par demi warp, ce qui est le cas ici si les valeurs multiples issues  par chaque thread se trouvent également à des adresses consécutives en mémoire globale.  
123
124 Nous faisons l'hypothèse que chaque thread traite deux pixels voisins et cela impose de gérer la superposition partielle des fenêtres du filtre. La méthode de sélection que nous avons choisie nous interdit en effet d'employer les techniques habituelles, comme la mise à jour incrémentale de l'histogramme des niveaux de gris. Cependant, une partie des traitements est commune aux 2 processus de sélection. En effet, les fenêtres associées aux deux  pixels partagent un certain nombre de données, égal à $S_n = n-k = n-\sqrt{n}$. Or, pour $n\ge 9$ :
125 $$  n-\sqrt{n} \ge \lceil\frac{n}{2}\rceil +1$$
126
127 % Au contraire, les sélections doivent être menées conjointement et cela est rendu possible par le fait que la liste initiale ne comporte que $R_n$ éléments, ce qui est toujours inférieur ou égal au nombre de valeurs partagées par les fenêtres associées à deux pixels voisins (sous-entendu : horizontalement). Deux fenêtres voisines partagent $S_n = n-k = n-\sqrt{n}$ éléments  et, pour  $n\ge 9$ :  
128 % $$  n-\sqrt{n} \ge \lceil\frac{n}{2}\rceil +1$$ 
129
130 \begin{figure}
131    \centering
132    \includegraphics[width=6cm]{Chapters/chapter5/img/median5_overlap.png}
133    \caption{Gestion des éléments communs aux fenêtres de deux pixels centraux voisins dans un filtre médian 5$\times$5. La liste initiale comprend les 14 premiers éléments communs, puis les 7 premières étapes de sélection sont conduites en commun avant que les 5 dernières le soient en parallèle, mais de manière disjointe.}
134    \label{fig-median-overlap}
135 \end{figure}
136
137 Nous pouvons donc initialiser la liste de sélection  avec $R_n$  valeurs choisies parmi les $S_n$ valeurs communes en étant sur que le processus de sélection n'écartera pas les deux médianes. Ensuite, on mène les $\left(S_n-R_n+1\right)$ premières étapes de sélection permettant d'intégrer progressivement l'ensemble des valeurs communes à la liste. Il ne reste alors que les $k$ éléments propres à chaque fenêtre à intégrer dans deux séquences de sélection rendues distinctes mais réalisées en alternance par le même thread. Cette répartition des éléments pour un filtre médian 5$\times$5 est représentée à la figure \ref{fig-median-overlap}. Comme on le voit sur la figure, les $R_n$ premières valeurs sont simplement prises au début de la liste des valeurs communes, sans que cela ne génère des défauts de cache pour les fenêtres de taille supérieure (le cache de texture 2D contient environ 5Ko).
138
139 Chaque thread utilise plus de registres ($R_n+2k$) pour traiter deux pixels que pour un seul ($R_n$), mais cela ne modifie pas les capacités  théoriques de traitement, avec toujours une taille maximale de 9$\times$9 sur C2070. Il suffit alors de réduire le nombre de threads par bloc pour retrouver le niveau de parallélisme souhaité. Mais globalement, traiter deux pixels par threads permet d'utiliser $k+1$ registres de moins par paire de pixels par rapport au traitement par threads distincts, ce qui représente  un gain de parallélisme au niveau de chaque bloc.
140
141 Nous avons fait jusqu'ici l'hypothèse d'un traitement de deux pixels par thread. Le passage à quatre pixels par thread implique $n-3k$ éléments communs aux quatre fenêtres, ce qui est inférieur à $R_n$ pour les tailles 3$\times$3 et 5$\times$5, et ne permet que deux étapes de sélections communes pour le 7$\times$7. cela ne compense pas le coût des copies nécessaires au dédoublement. Le cas du 9$\times$9 pourrait sembler plus pertinent, mais là encore, les sélections communes aux quatre pixels ne sont pas suffisamment nombreuses pour compenser les coûts inhérents aux sélections disjointes. La solution deux pixels par thread s'avère la plus performante.
142
143 L'ensemble des choix que nous venons de décrire et qui ont présidé à l'élaboration de notre filtre médian GPU conduisent à adopter un style de codage assez inhabituel, du fait de l'usage intensif des registres dont une des caractéristiques est de ne pas être indexables. Le code du filtre médian 3$\times$3 est reproduit au listing \ref{lst-median3}.
144
145 \lstinputlisting[label={lst-median3},caption=Kernel réalisant un filtre médian 3$\times$3 en registres.]{Chapters/chapter5/code/median3-2pix.cu} 
146
147 \section{Résultats}
148
149 \begin{figure}
150    \centering
151    \subfigure[image 512$\times$512 pixels.]{\includegraphics[width=7cm]{Chapters/chapter5/img/comp512.png}}\qquad
152    \subfigure[image 4096$\times$4096 pixels.]{\includegraphics[width=7cm]{Chapters/chapter5/img/comp4k.png}}
153    \caption{Comparaison des débits (MP/s) atteints par notre implémentation notée PRMF, avec les principales solutions de référence. De gauche à droite : PCMF, BVM, PRMF, ArrayFire (impossible en  4096$\times$4096)}
154    \label{fig-median-comp}
155 \end{figure}
156
157 Les valeurs présentées dans les tableaux  \ref{tab-median-coutcpy}, \ref{tab-median-chronos} et la figure \ref{fig-median-comp} sont obtenues par moyennage du chronométrage de 1000 exécutions du même kernel, développé en variantes 8 et 16 bits de profondeurs de niveau de gris. 
158  
159 La première analyse que nous pouvons en faire est la pertinence des choix faits quant aux transferts de données, qui représentent entre 13\% et 82\% du temps total d'exécution des configurations testées. 
160
161 \begin{table}[ht]
162 \renewcommand{\arraystretch}{1.5}
163 \centering
164 {
165 \begin{tabular}{ccccc}
166 \toprule
167 {\bf Taille d'image}& {\bf Profondeur}& \textbf{3$\times$3} & \textbf{5$\times$5} & \textbf{7$\times$7} \\
168 \midrule
169 \multirow{2}{*}{512$\times$512} &8 bits&73\% &44\% &20\% \\
170                                          &16 bits&82\% &57\% &29\% \\
171 \midrule
172 \multirow{2}{*}{1024$\times$1024}&8 bits&68\% &37\% &15\% \\
173                                          &16 bits&80\% &53\% &25\% \\
174 \midrule
175 \multirow{2}{*}{2048$\times$2048}&8 bits&66\% &34\% &14\% \\
176                                          &16 bits&79\% &59\% &23\% \\
177 \midrule
178 \multirow{2}{*}{4096$\times$4096}&8 bits&65\% &33\% &13\% \\
179                                          &16 bits&78\% &50\% &23\% \\
180 \bottomrule
181 \end{tabular}}  
182 \caption{Pourcentage du temps d'exécution pris par les transferts de données en fonction de la taille de fenêtre du filtre, pour les profondeurs  8 et 16 bits sur GPU C2070.}
183 \label{tab-median-coutcpy}
184 \end{table}
185
186
187 Les valeurs du tableau \ref{tab-median-chronos} détaillent les débits de pixels réalisés par les différents kernels. Ils prennent en compte le temps d'exécution ainsi que les temps de transfert. Par ailleurs, afin d'évaluer le niveau de performance absolue de notre méthode, nous avons également mesuré le débit maximum effectif permis par le couple GPU/CPU, ce qui  nous permet d'évaluer la pertinence d'éventuelles recherches postérieures visant à améliorer encore les débits. 
188
189 La valeur de ce débit maximum est obtenue en exécutant un kernel \og identité\fg{} qui n'effectue aucune opération mais se contente de faire les lectures et écritures en mémoire. Les débits ainsi mesurés sont regroupés dans le tableau  \ref{tab-median-debitmax} où l'on constate en particulier que plus l'image est de grande dimension, plus on peut espérer un débit élevé. On vérifie aussi notre intuition initiale avec des valeurs d'environ 2000~MP/s, à comparer aux moins de 200~MP/s permis par les implémentations de référence. 
190
191 \begin{table}[ht]
192 \centering
193 {
194 \begin{tabular}{clccc}
195 \toprule
196 {\bf Taille d'image}&\shortstack{{\bf $t$ : temps kernel }\\{\bf $T_x$ débit en prof. x }} & \textbf{3$\times$3} & \textbf{5$\times$5} & \textbf{7$\times$7} \\\midrule
197 \multirow{3}{*}{{512$\times$512}} &t (ms)    &0.05 &0.19 &0.60 \\
198                                          &$T_{8}$ (Mpix/s)&1291 &773  &348 \\
199                                          &$T_{16}$ (Mpix/s)&865  &607   &307 \\
200 \midrule
201 \multirow{3}{*}{{1024$\times$1024}}&t (ms)    &0.20 &0.74 &2.39 \\
202                                          &$T_{8}$ (Mpix/s)&1644 &889  &371 \\
203                                          &$T_{16}$ (Mpix/s)&1045   &692   &329 \\
204 \midrule
205 \multirow{3}{*}{{2048$\times$2048}}&t (ms)    &0.79 &2.95 &9.53 \\
206                                          &$T_{8}$ (Mpix/s)&1805 &936  &379 \\
207                                          &$T_{16}$ (Mpix/s)&1130   &729   &338 \\
208 \midrule
209 \multirow{3}{*}{{4096$\times$4096}}&t (ms)    &3.17 &11.77 &38.06 \\
210                                          &$T_{8}$ (Mpix/s)&1854 &951   &382 \\
211                                          &$T_{16}$ (Mpix/s)&1151   &738    &340  \\
212 \bottomrule
213 \end{tabular}}  
214 \caption{Performances des filtres médians rapides en fonction des tailles d'image et de fenêtre du filtre, en variantes 8 et 16 bits de profondeursur GPU C2070.}
215 \label{tab-median-chronos}
216 \end{table}
217
218 \begin{table}[h]
219 \centering
220 {
221 \begin{tabular}{ccc}
222 \toprule
223 {\bf Taille d'image}& {$\mathbf{ T_8}$} & {$\mathbf{T_{16}}$} \\
224 \midrule
225 512$\times$512   &1598 &975 \\
226 1024$\times$1024 &2101 &1200 \\
227 2048$\times$2048 &2359 &1308 \\
228 4096$\times$4096 &2444 &1335 \\
229 \bottomrule
230 \end{tabular} }
231 \caption{Débits maximum effectifs $T_8$ and $T_{16}$ (en MP/s), respectivement pour les variantes 8 et 16 bits sur C2070.}
232 \label{tab-median-debitmax}
233 \end{table}
234
235 Les performances des kernels en variante 16 bits ne différent pas de celles en variante 8 bits, seuls les temps de transfert des données sont à l'origine des variations du débit global. 
236
237 Enfin, considérant que l'algorithme implémenté dans les kernels d'Arrayfire est proche du notre, nous avons mesuré les débits d'une version modifiée de leur kernel appliquant nos techniques de gestion mémoire, avec pour résultat un filtre médian 3$\times$3 capable de traiter 670~MP/s, soit 3,7 fois plus que la version commerciale. L'écart restant ($\times$2,7) étant à mettre au crédit de notre implémentation du kernel.
238
239 \section{Conclusion}
240
241 L'implémentation GPU du filtre médian que nous avons décrite permet de traiter jusqu'à 1854 millions de pixels à la seconde, soit aussi 900 images haute définition (1080p), et surclasse les solutions jusqu'alors proposées dans la littérature, dont la plus performante ne débitait que 180 millions de pixels par seconde (voir \cite{sanchezICASSP12}). Elle a fait l'objet d'un article dans la revue \textit{Journal of Signal Processing Systems} (voir \cite{perrot2013fine}). L'important gain de vitesse est la conséquence de l'attention toute particulière que nous avons apportée à la gestion de la mémoire, tant du côté GPU que CPU, et qui nous a conduit à concevoir des kernels utilisant exclusivement des registres pour effectuer les opérations de sélection. 
242
243 Le débit de pixels constaté approche cette fois le débit maximal effectif de la plateforme (2444~MP/s), ce qui limite le gain que pourraient apporter des implémentations futures. Toutefois, notre algorithme appartient à la classe des solutions de filtrage médian par tri (incomplet) des valeurs et à ce titre, ses temps d'exécution sont fortement dépendants de la taille de la fenêtre de filtrage, comme le montrent les diagrammes de la figure \ref{fig-median-comp}. Il n'est donc pertinent que pour les petites tailles de fenêtre, qui sont aussi les plus communément employées en traitement d'image.
244
245 Dans les grandes tailles de fenêtre, la plupart des solutions adoptent des méthodes approchées de détermination de la médiane. Les principes que nos avons appliqués peuvent alors apporter des gains de performance comme nous l'avons montré dans \cite{perrotbookgpu}, ainsi qu'à beaucoup d'autres méthodes de calcul comme les filtres de convolution abordés dans le chapitre suivant. 
246
247 Enfin nous renvoyons le lecteur à la conclusion du prochain chapitre qui présentera l'outil en ligne que nous proposons et qui permet de générer les codes des kernels médians et de convolution.
248 % LocalWords:  cardinalité warp incrémentale indexables