]> AND Private Git Repository - book_gpu.git/blob - BookGPU/Chapters/chapter3/ch3.tex
Logo AND Algorithmique Numérique Distribuée

Private GIT Repository
2afb337b65a60da673a9816f46fcc4ae939755e5
[book_gpu.git] / BookGPU / Chapters / chapter3 / ch3.tex
1 \chapterauthor{Zulu pero}{Zulumachine Institute}
2 %\graphicspath{{img/}}
3
4
5 % \begin{VF}
6 % ``A ''
7
8 % \VA{Thomas Davenport}{Senior Adjutant to the Junior Marketing VP}
9 % \end{VF}
10
11
12
13 % \begin{shadebox}
14 % A component part for an electronic item is
15 % manufactured at one of three different factories, and then delivered to
16 % the main assembly line.Of the total number supplied, factory A supplies
17 % 50\%, factory B 30\%, and factory C 20\%. Of the components
18 % manufactured at factory A, 1\% are faulty and the corresponding
19 % proportions for factories B and C are 4\% and 2\% respectively. A
20 % component is picked at random from the assembly line. What is the
21 % probability that it is faulty? 
22 % \end{shadebox}
23
24
25 % \begin{equation}
26 % \mbox{var}\widehat{\Delta} = \sum_{j = 1}^t \sum_{k = j+1}^t
27 % \mbox{var}\,(\hat{\alpha}_j - \hat{\alpha}_k)  = \sum_{j = 1}^t
28 % \sum_{k = j+1}^t \sigma^2(1/n_j + 1/n_k). \label{2delvart2}
29 % \end{equation}
30
31
32 % \begin{shortbox}
33 % \Boxhead{Box Title Here}
34 % \end{shortbox}
35
36 % \begin{theorem}\label{1th:Z_m}
37 % Let $m$ be a prime number. With the addition and multiplication as 
38 % defined above, $Z_m$ is a field.
39 % \end{theorem}
40
41 % \begin{proof}
42 % \end{proof}
43
44 % \begin{notelist}{000000}
45 %  \notes{Note:}{The process of integrating reengineering is best accomplished with an engineer, a dog, and a cat.}
46 % \end{notelist}
47
48
49 % \begin{VT1}
50 % \VH{Think About It...}
51 % Com
52 % \VT
53 % \VTA{The Information Revolution}{Business Week}
54 % \end{VT1}
55
56
57 %\begin{definition}\label{1def:linearcomb}{}\end{definition}
58
59
60
61 % \begin{extract}
62 % text 
63 % \end{extract}
64
65 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
66 %      Listings
67 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68 \lstset{
69   language=C,
70   columns=fixed,
71   basicstyle=\footnotesize\ttfamily,
72   numbers=left,
73   firstnumber=1,
74   numberstyle=\tiny,
75   stepnumber=5,             
76   numbersep=5pt,              
77   tabsize=3,                  
78   extendedchars=true,         
79   breaklines=true,       
80   keywordstyle=\textbf,
81   frame=single,         
82   % keywordstyle=[1]\textbf,   
83   %identifierstyle=\textbf,
84   commentstyle=\color{white}\textbf,
85   stringstyle=\color{white}\ttfamily,
86   % xleftmargin=17pt,
87   % framexleftmargin=17pt,
88   % framexrightmargin=5pt,
89   % framexbottommargin=4pt,
90   backgroundcolor=\color{lightgray},
91   }
92
93 %\DeclareCaptionFont{blue}{\color{blue}} 
94 %\captionsetup[lstlisting]{singlelinecheck=false, labelfont={blue}, textfont={blue}}
95
96 %\DeclareCaptionFont{white}{\color{white}}
97 %\DeclareCaptionFormat{listing}{\colorbox{gray}{\parbox{\textwidth}{\hspace{15pt}#1#2#3}}}
98 %\captionsetup[lstlisting]{format=listing,labelfont=white,textfont=white, singleline}
99 %%%%%%%%%%%%%%%%%%%%%%%% Fin Listings %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
100
101 \newcommand{\kl}{\includegraphics[scale=0.6]{Chapters/chapter3/img/kernLeft.png}~}
102 \newcommand{\kr}{\includegraphics[scale=0.6]{Chapters/chapter3/img/kernRight.png}}
103
104 \chapter{Setting up the environnement.}
105 Image processing using a GPU often means using it as a general purpose computing processor, which soon brings up the issue of data transfers, especially when kernel runtime is fast and/or when large data sets are processed.
106 The truth is that, in certain cases, data transfers between GPU and CPU are slower than the actual computation on GPU. 
107 It remains that global runtime can still be faster than similar processes run on CPU.
108 Therefore, to fully optimize global runtimes, it is important to pay attention to how memory transfers are done.
109 This leads us to propose, in the following section, an overall code structure to be used with all our kernel examples. 
110
111 Obviously, our code originally accepts various image dimensions and can process color images. 
112 However, so as to propose concise and more readable code, we will assume the following limitations:
113 8 or 16~bit-coded gray-level input images whose dimensions $H\times W$ are multiples of 512 pixels. 
114
115 \section{Data transfers, memory management.}
116 This section deals with the following issues: 
117 \begin{enumerate}
118 \item data transfer from CPU memory to GPU global memory: several GPU memory areas are available as destination memory but the 2-D caching mechanism of texture memory, specifically designed for fetching neighboring pixels, is currently the fastest way to fetch gray-level pixel values inside a kernel computation. This has lead us to choose \textbf{texture memory} as primary GPU memory area for images.
119 \item data fetching from GPU global memory to kernel local memory: as said above, we use texture memory. Depending on which process is run, texture data is used either by direct fetching in kernel local memory or through a prefetching in thread block shared memory.
120 \item data outputting from kernels to GPU memory: there is actually no alternative to global memory, as kernels can not directly write into texture memory and as copying from texture to CPU memory would not be faster than from simple global memory.
121 \item data transfer from GPU global memory to CPU memory: it can be drastically accelerated by use of \textbf{pinned memory}, keeping in mind it has to be used sparingly.
122 \end{enumerate}
123 Algorithm \ref{algo:memcopy} summarizes all the above considerations and describe how data are handled in our examples. For more information on how to handle the different types of GPU memory, we suggest to refer to CUDA programmer's guide. 
124
125 At debug stage, for simplicity's sake, we use the \textbf{cutil} library supplied by the NVidia developpement kit (SDK). Thus, in order to easily implement our examples, we suggest readers download download and install the latest NVidia-SDK (ours is SDK4.0), create a new directory \textit{SDK-root-dir/C/src/fast\_kernels} and adapt the generic \textit{Makefile} present in each sub-directory of \textit{SDK-root-dir/C/src/}. Then, only two more files will be enough to have a fully operational environnement: \textit{main.cu} and \textit{fast\_kernels.cu}. 
126 Listings \ref{lst:main1}, \ref{lst:fkern1} and \ref{lst:mkfile} implement all the above considerations minimally, while remaining functional. 
127
128 The main file of Listing \ref{lst:main1} is a simplified version of our actual main file. 
129 It has to be noticed that cutil functions \texttt{cutLoadPGMi} and \texttt{cutSavePGMi} only operate on unsigned integer data. As data is coded in short integer format for performance reasons, the use of these functions involves casting data after loading and before saving. This may be overcome by use of a different library. Actually, our choice was to modify the above mentioned cutil functions.
130
131 Listing \ref{lst:fkern1} gives a minimal kernel skeleton that will serve as the basis for all other kernels. Lines 5 and 6 determine the coordinates $(i, j)$ of the pixel to be processed. Each pixel is associated with one thread.
132 The instruction in line 8 combines writing the output gray-level value into global memory and fetching the input gray-level value from 2-D texture memory.
133 The Makefile given in Listing \ref{lst:mkfile} shows how to adapt examples given in SDK.
134
135 \begin{algorithm}
136  \SetNlSty{textbf}{}{:}
137  allocate and populate CPU memory \textbf{h\_in}\;
138  allocate CPU pinned-memory \textbf{h\_out}\;
139  allocate GPU global memory \textbf{d\_out}\;
140  declare GPU texture reference \textbf{tex\_img\_in}\;
141  allocate GPU array in global memory \textbf{array\_img\_in}\;
142  bind GPU array \textbf{array\_img\_in} to texture \textbf{tex\_img\_in}\;
143  copy data from \textbf{h\_in} to \textbf{array\_img\_in}\label{algo:memcopy:H2D}\; 
144  kernel\kl gridDim,blockDim\kr()\tcc*[f]{outputs to d\_out}\label{algo:memcopy:kernel}\;
145  copy data from \textbf{d\_out} to \textbf{h\_out} \label{algo:memcopy:D2H}\;
146 \caption{Global memory management on CPU and GPU sides.}
147 \label{algo:memcopy}
148 \end{algorithm}
149
150 \lstinputlisting[label={lst:main1},caption=Generic main.cu file used to launch CUDA kernels]{Chapters/chapter3/code/mainSkel.cu}
151
152 \lstinputlisting[label={lst:fkern1},caption=fast\_kernels.cu file featuring one kernel skeleton]{Chapters/chapter3/code/kernSkel.cu}
153
154 \lstinputlisting[label={lst:mkfile},caption=Generic Makefile based on those provided by NV SDK]{Chapters/chapter3/code/Makefile}
155
156
157 \section{Performance measurements}
158 As our goal is to design very fast implementations of basic image processing algorithms, we need to make quite accurate time-measurements, within the order of magnitude of $0.01~ms$. Again, the easiest way of doing so is to use the helper functions of the cutil library. As usual, as the durations we are measuring are short and possibly suject to non neglectable variations, a good practice is to measure multiple executions and issue the mean runtime. All time results given in this chapter have been obtained through 1000 calls to each kernel.
159
160 Listing \ref{lst:chronos} shows how to use the dedicated cutil functions. Timer declaration and creation only need to be performed once while reset, start and stop can be used as often as necessary. Synchronization is mandatory before stopping the timer (Line 7), to avoid runtime measure being biased.
161 \lstinputlisting[label={lst:chronos},caption=Time measurement technique using cutil functions]{Chapters/chapter3/code/exChronos.cu}
162
163 In an attempt to provide relevant speedup values, we either implemented CPU versions of the algorithms studied, or used the values found in existing literature. Still, the large number and diversity of hardware platforms and GPU cards make it impossible to benchmark every possible combination and significant differences may occur between the speedups we announce and those obtained with different devices. As a reference, our developing platform details as follows:
164
165 \begin{itemize}
166 \item CPU codes run on: 
167   \begin{itemize}
168   \item Quad Core Xeon E31245 at 3.3GHz-8GByte RAM running Linux kernel 3.2 
169     \item Quad Core Xeon E5620 at 2.40GHz-12GByte RAM running Linux kernel 2.6.18 
170   \end{itemize}
171 \item GPU codes run on:
172 \begin{itemize}
173   \item Nvidia Tesla C2070 hosted by a PC QuadCore Xeon E5620 at 2.4GHz-12GByte RAM, running Linux kernel 2.6.18 
174     \item NVidia GeForce GTX 280 hosted by a PC QuadCore Xeon X5482 at 3.20GHz-4GByte RAM, running Linux kernel 2.6.32
175   \end{itemize}
176 \end{itemize}
177
178 All kernels have also been tested with various image sizes from 512$\times$512 to 4096$\times$4096 pixels. This allows to guess runtime dependancy over image size.
179
180 Last, like many authors, we chose to use the pixel throughput value of each process in Mega Pixels per second (MP/s) as a performance indicator, including data transfers and kernel runtimes. 
181 In order to estimate the potential for improvement of each kernel, a reference throughput measurement, involving identity kernel of Listing \ref{lst:fkern1}, was performed. As this kernel only fetches input values from texture memory and outputs them to global memory without doing any computation, it represents the smallest, thus fastest, possible process and is taken as the reference throughput value (100\%). The same measurement was performed on CPU, with a maximum effective pixel throughput of 130~Mpixel per second. On GPU, depending on grid parameters it amounts to 800~MPixels/s on GTX280 and 1300~Mpixels/s on C2070.
182
183
184
185 \chapter{Implementing a fast median filter}
186 \section{Introduction}
187 Median filtering is a well-known method used in a wide range of application frameworks as well as a standalone filter especially for \textit{salt and pepper} denoising. It is able to highly reduce power of noise without blurring edges too much.
188
189 First introduced by Tukey in \cite{tukey77}, it has been widely studied since then, and many researchers have proposed efficient implementations of it, adapted to various hypothesis, architectures and processors. 
190 Originally, its main drawbacks were its compute complexity, its non linearity and its data-dependent runtime. Several researchers have adress these issues and designed, for example, efficient histogram-based median filter with predictible runtime \cite{Huang:1981:TDS:539567, Weiss:2006:FMB:1179352.1141918}.  
191
192 More recently, the advent of GPUs opened new perspectives in terms of image processing performance, and some researchers managed to take advantage of the new graphic capabilities: in that respect, we can cite the Branchless Vectorized Median filter (BVM) \cite{5402362, chen09} which allows very interesting runtimes on CUDA-enabled devices but, as far as we know, the fastest implementation to date is the histogram-based CCDS median filter \cite{6288187}.
193
194 Some of the following implementations, feature very fast runtimes. They are targeted on Nvidia Tesla GPU (Fermi architecture, compute capability 2.x) but may easily be adapted to other models e.g. those of compute capability 1.3.
195
196 The fastest ones are based on one efficient parallel implementation of the BVM algorithm described in \cite{mcguire2008median}, improving its performance through fine tuning of its implementation.
197
198 \section{Median filtering}
199 \subsection{Basic principles}
200 DEsigning a 2-D median filter basically consists in defining a square window $H(i,j)$ for each pixel $I(i,j)$ of the input image, containing $n\times n$ pixels and centered on $I(i,j)$. The output value $I'(i,j)$ is the median value of the gray level values of the $n\times n$ pixels of $H(i,j)$. Figure \ref{fig:median_1} illustrates this principle with an example of a 5x5 median filter applied on pixel $I(5,6)$. The output value is the median value of the 25 values of the dark gray window centered on pixel $I(5,6)$. 
201  The generic filtering method is given by Algorithm \ref{algo_median_generic}. After the data transfer stage of line \ref{algo_median_generic:memcpyH2D} which copies data from CPU memory to GPU texture memory, the actual median computing occurs between lines \ref{algo_median_generic:cptstart} and lines \ref{algo_median_generic:cptend}, before the final transfer which copies data back to CPU memory at line \ref{algo_median_generic:memcpyD2H}. Obviously, on key issue is the selection method that identifies the median value. But, as shown in figure \ref{fig:median_overlap}, since two neighboring pixels share part of the values to be sorted, a second key issue is how to rule redundancy between consecutive positions of the running window $H(i,j)$. 
202 As mentioned earlier, the selection of the median value can be performed by por than one technique, using either histogram-based or sorting methods, each of them having its own benefits and drawbacks as will be discussed further down. 
203
204 \subsection{A naive implementation}
205 As a reference, Listing \ref{lst:medianGeneric} gives a simple, not to say simplistic implementation of a CUDA kernel (\texttt{kernel\_medianR}) achieving generic $n\times n$ histogram-based median filtering. Its runtime has a very low data dependency, but this implementation does not suit very well GPU architecture. Each pixel loads the whole of its $n\times n$ neighborhood meaning that one pixel is loaded multiple times inside one single thread block, and above all, the use of a local vector (histogram[]) considerably downgrades performance, as the compiler automatically stores such vectors in local memory (slow).
206
207 Table \ref{tab:medianHisto1} displays measured runtimes of \texttt{kernel\_medianR} and pixel throughputs for each GPU version and for both CPU and GPU implementations. Usual window sizes of $3\times 3$, $5\times 5$ and $7\times 7$ are shown. Though some specific applications require larger window sizes and dedicated algorithms , such small square window sizes are most widely used in general purpose image processing. GPU runtimes have been obtained with a grid of 64-thread blocks. This block size, is a good compromise in this case.
208
209 The first observation to make when analysing results of Table \ref{tab:medianHisto1} is that, on CPU, window size has almost no influence on the effective pixel throughput. 
210 Since inner loops that fill the histogram vector contain very few fetching instructions (from 9 to 49, depending on the window size), it is not surprising to note neglectable runtime compared to the runtime of outer loops that fetch image pixels (from 256k to 16M instructions). 
211 One could be tempted to claim that CPU has no chance to win, which is not so obvious as it highly depends on what kind of algorithm is run and above all, how it is implemented. Despite a maximum effective throughput potential that is almost five times higher, measured GTX280 throughput values sometimes prove slower than CPU values, as shown in Table \ref{tab:medianHisto1}.
212
213 On the GPU's side, we note high dependence on window size due to the redundancy induced by the multiple fetches of each pixel inside each block, becoming higher with the window size as illustrated by Figure \ref{fig:median_overlap}. On C2070 card, thanks to a more efficient caching mechanism, this effect is lesser. On GPUs, dependency over image size is low, due to slightly more efficient data transfers when copying larger data amounts. Thus transferring a 4096$\times$4096 pixel image (32~MBytes) is a bit faster than transferring 64 times a 512$\times$512 pixel image (0.5~MBytes).
214
215 %% mettre l'eau à la bouche
216
217 \lstinputlisting[label={lst:medianGeneric},caption=Generic CUDA kernel achieving median filtering]{Chapters/chapter3/code/medianGeneric.cu}
218
219 \begin{figure}
220    \centering
221    \includegraphics[width=8cm]{Chapters/chapter3/img/median_1.png}
222    \caption{Exemple of 5x5 median filtering}
223    \label{fig:median_1}
224 \end{figure}
225
226 \begin{algorithm}
227  \SetNlSty{textbf}{}{:}   
228   copy data from CPU to GPU texture memory\label{algo_median_generic:memcpyH2D}\; 
229   \ForEach(\tcc*[f]{in parallel}){pixel at position $(x, y)$}{
230     Read gray-level values of the n$\times$n neighborhood\label{algo_median_generic:cptstart}\;
231     Selects the median ($(n^2/2)^{th}$) value among those n$\times$n values\;
232     Outputs the new gray-level value \label{algo_median_generic:cptend}\;
233   }
234 copy data from GPU global memory to CPU memory\label{algo_median_generic:memcpyD2H}\;
235 \caption{generic n$\times$n median filter}
236 \label{algo_median_generic}
237 \end{algorithm}
238
239 \begin{figure}
240    \centering
241    \includegraphics[width=5cm]{Chapters/chapter3/img/median_overlap.png}
242    \caption{Illustration of window overlapping in 5x5 median filtering}
243    \label{fig:median_overlap}
244 \end{figure}
245
246
247 \begin{table}[h]
248 %\newcolumntype{I}{!{\vrule width 1.5pt}}
249 \newlength\savedwidth
250 \newcommand\whline{\noalign{\global\savedwidth
251   \arrayrulewidth\global\arrayrulewidth 1.5pt}
252   \hline \noalign{\global\arrayrulewidth
253   \savedwidth}
254 }
255 \renewcommand{\arraystretch}{1.5}
256 \centering
257 {\tiny
258 \begin{tabular}{|c|l||c|c|c|c|c|c|c|c|c|}
259 \hline
260 \multicolumn{2}{|l||}{Processor} & \multicolumn{3}{c|}{\textbf{GTX280}} & \multicolumn{3}{c|}{\textbf{C2070}} & \multicolumn{3}{c|}{\textbf{Xeon}} \\ \hline
261 \multicolumn{2}{|l||}{\shortstack{Performances$\rightarrow$\\sizes (pixels)$\downarrow$}} & \shortstack{t\\(ms)}& \shortstack{output\\(MP/s)}& \shortstack{rate\\\% }&\shortstack{t\\(ms)}& \shortstack{output\\(MP/s)}& \shortstack{rate\\\% }&\shortstack{t\\(ms)}& \shortstack{output\\(MP/s)}& \shortstack{rate\\\% }   \\ \whline
262 \multirow{3}{*}{\rotatebox{90}{512$^2$}} &3$\times$3&11.50 &22 &2.2 &7.58 &33 &3.4 & 19.25& 14&-\\
263                                          &5$\times$5&19.10 &14 &1.3 &8.60 &30 &3.0 &18.49 &14 &-\\
264                                          &7$\times$7&31.30 &8 &0.8 &10.60 &24 &2.5 &20.27 &13 &-\\\whline
265 \multirow{3}{*}{\rotatebox{90}{1024$^2$}}&3$\times$3&44.50 &23 &2.3 &29.60 &34 &3.5 &75.49 &14 &-\\
266                                          &5$\times$5&71.10 &14 &1.4 &33.00 &31 &3.2 &73.88 &14 &-\\
267                                          &7$\times$7&114.50 &9 &0.9 &39.10 &26 &2.7 &77.40 &13 &-\\\whline
268 \multirow{3}{*}{\rotatebox{90}{2048$^2$}}&3$\times$3&166.00 &24 &2.4 &115.20 &36 &3.6 &296.18&14 &-\\
269                                          &5$\times$5&261.00&16 &1.5 &128.20&32 &3.3 &294.55&14 &-\\
270                                          &7$\times$7&411.90 &10&1.0 &143.30&28 &2.8 &303.48&14 &-\\\whline
271 \multirow{3}{*}{\rotatebox{90}{4096$^2$}}&3$\times$3&523.80 &31 &3.0 &435.00 &38 &3.9 &1184.16&14 &-\\
272                                          &5$\times$5&654.10&25 &2.4 &460.20&36 &3.7 &1158.26&14 &-\\
273                                          &7$\times$7&951.30 &17&1.7 &509.60&32 &3.3 &1213.55&14 &-\\\whline
274
275 \end{tabular}}  
276 \caption{Performance results of \texttt{kernel medianR}. }
277 \label{tab:medianHisto1}
278 \end{table}
279
280 \section{NVidia GPU tuning recipes}
281 When designing GPU code, besides thinking of the actual data computing process, one must choose the memory type into which to store temporary data. Three type of GPU memory are available:
282 \begin{enumerate}
283 \item \textbf{Global memory, the most versatile:}\\Offers the largest storing space and global scope but is slowest (400 cycles latency). \textbf{Texture memory} is physically included into it, but allows access through an efficient 2-D caching mechanism.
284 \item \textbf{Registers, the fastest:}\\Allows access wihtout latency, but only 63 registers are available per thread (thread scope), with a maximum of 32K per Symetric Multiprocessor (SM).
285 \item \textbf{Shared memory, a complex compromise:}\\All threads in one block can access 48~KBytes of shared memory, which is faster than global memory (20 cycles latency) but slower than registers. 
286 However, bank conflicts can occur if two threads of a warp try to access data stored in one single memory bank. In such cases, the parallel process is re-serialized which may cause significant performance decrease. One easy way to avoid it is to ensure that two consecutive threads in one block always access 32 bit data at two consecutive adresses.  
287 \end{enumerate}
288
289 \noindent As observed earlier, designing a median filter GPU implementation using only global memory is fairly straightforward, but its performance remains quite low even if it is faster than CPU. 
290 To overcome this, the most frequent choice made in efficient implementations found in literature is to use shared memory. Such option implies prefetching data prior to doing the actual computations, a relevant choice, as each pixel of an image belongs to n$\times$n different neighborhoods. Thus, it can be expected that fetching each gray-level value from global memory only once should be more efficient than do it each time  it is required. One of the most efficient implementations using shared memory is presented in \cite{5402362}. In the case of the generic kernel of Listing \ref{lst:medianGeneric}, using shared memory without further optimization would not bring valuable speedup because that would just move redundancy from texture to shared memory fetching and would generate bank conflicts. For information, we wrote such a version of the generic median kernel and our measurements showed a speedup of around 3\% (for example: 32ms for 5$\times$5 median on a 1024$^2$ pixel image). 
291
292 As for registers, designing a generic median filter that would only use that type of memory seems difficult, due to the above mentioned 63 register-per-thread limitation. 
293 Yet, nothing forbids us to design fixed-size filters, each of them specific to one of the most popular window sizes. It might be worth the effort as dramatic increase in performance could be expected.
294
295 Another track to follow in order to improve performance of GPU implementations consists in hiding latencies generated by arithmetic instruction calls and memory accesses. Both can be partially hidden by introducing Instruction-Level Parallelism (ILP) and by increasing the data count output by each thread. Though such techniques may seem to break the NVidia occupancy paradigm, they can lead to dramatically higher data throughput values.
296 The following sections illustrate these ideas and detail the design of the fastest CUDA median filter known to date.
297   
298 \section{A 3$\times$3 median filter:  using registers }
299 Designing a median filter dedicated to the smallest possible square window size is a good challenge to start using registers. 
300 One first issue is that the exclusive use of registers forbids us to implement a naive histogram-based method. In a \textit{8-bit gray level pixel per thread} rule, each histogram requires one 256-element vector to store its values, e.g. four times the maximum register count allowed per thread (63). Considering a 3$\times$3 median filter involves only 9 pixel values per thread, it seem obvious they can be sorted within the 63-register limit.
301
302 \subsection{The simplest way}
303 In the case of a 3$\times$3 median filter, the simplest solution consists in associating one register to each gray-level value, then sorting those 9 values and selecting the fifth one, e.g. the median value.  For such a small amount of data to sort, a simple selection method is well indicated. As shown in Listing \ref{lst:kernelMedian3RegTri9} (\texttt{kernelMedian3RegTri9()}), the constraint of only using registers leads to adopt an unusual manner of coding. However, results are persuasive: runtimes are divided by around 120 on GTX280 and 80 on C2070, while only reduced by a 3.5 factor on CPU.
304 The diagram of Figure \ref{fig:compMedians1} summarizes these first results. Only C2070 throughputs are shown and compared to CPU results. We included the maximum effective pixel throughput in order to see the improvement potential of the different implementations. We also introduced throughputd achieved by \textit{libJacket}, a commercial implementation, as it was the fastest known implementation of 3$\times$3 median filter to date, as illustrated in \cite{chen09}. One of the authors of libJacket kindly posted the CUDA code of its  3$\times$3 median filter, that we inserted into our own coding structure. The algorithm itself is quite similar to ours, but running it in our own environement produced higher throughput values than those published in \cite{chen09}, not due to different hardware capabilities between our GTX280 and the GTX260 used in the paper, but to the way we perform memory transfers and to our register-only method of storing temporary data.
305
306 \lstinputlisting[label={lst:kernelMedian3RegTri9},caption= 3$\times$3 median filter kernel using one register per neighborhood pixel and bubble sort]{Chapters/chapter3/code/kernMedianRegTri9.cu}
307
308 \begin{figure}
309    \centering
310    \includegraphics[width=11cm]{Chapters/chapter3/img/debitPlot1.png}
311    \caption{Comparison of pixel throughputs on GPU C2070 and CPU for generic median, in 3$\times$3 median register-only and \textit{libJacket}.}
312    \label{fig:compMedians1}
313 \end{figure}
314
315 \subsection{Further optimization}
316 Running the above register-only 3$\times$3 median filter through the NVidia CUDA profiler teaches us that the memory throughput achieved by the kernel remains quite low. To improve this, two methods can be used: one is to increase the number of concurrent threads by reducing the number of registers used, the other to have each thread process more data which can be achieved by outputting the gray-level value of two pixels or more.
317 \subsubsection{Reducing register count}
318 Our current kernel (\texttt{kernelMedian3RegTri9}) uses one register per gray-level value, which amounts to 9 registers for the entire 3$\times$3 window. 
319 This count can be reduced by use of an iterative sorting process called \textit{forgetful selection}, where both \textit{extrema} are eliminated at each sorting stage, until only 3 elements remain. The question is to find out the minimal register count $k_n$ that allows the selection of the median amoung $n^2$ values. The answer can be evaluated  considering that, when eliminating the maximum and the minimum values, one has to make sure not to eliminate the global median value, e.g. $k_n=\lceil n^2/2\rceil+1$.
320 %To ensure this, the number of values that are not part of the process must remain lower than the number of values that would have had an index higher (or lower) than the middle one in the fully sorted $n^2$ value vector. 
321 This rule can be applied to the first eliminating stage and remains true with the next ones as each stage suppresses exactly two values.
322 In our 3$\times$3 pixel window example, the minimum register count becomes $k_9=\lceil 9/2\rceil+1 = 6$.
323
324 The \textit{forgetful selection} method, used in \cite{mcguire2008median} does not imply full sorting of values, but only selecting minimum and maximum values, which, at the price of a few iteration steps ($n^2-k$), reduces arithmetic complexity.
325 Listing \ref{lst:medianForget1pix3} details this process where forgetful selection is achieved by use of simple 2-value sorting function ($s()$, lines 1 to 5) that swaps input values if necessary. Moreover, whenever possible, in order to increase the Instruction-Level Parallelism, successive calls to $s()$ are done with independant elements as arguments. This is illustrated by the macro definitions of lines 7 to 14.
326
327 \lstinputlisting[label={lst:medianForget1pix3},caption= 3$\times$3 median filter kernel using the minimum register count of 6 and finding the median value by forgetful selection method]{Chapters/chapter3/code/kernMedianForget1pix3.cu}
328
329 Our such modified kernel provides significantly improved runtimes: a speedup of around 16\% is obtained, and pixel throughput reaches around 1000~MPixel/s on C2070.
330
331 \subsubsection{More data output per thread}
332 In the case of a kernel achieving an effective memory throughput much lower than the peak value, and if enough threads are run, another technique may help hiding memory latency and thus leverage performance: one thread produces multiple pixel outputs.
333 Attentive readers should notice that it would increase the register count per thread. That's true, but dividing thread block size by the same quantity allow, at least, to keep the same register count per block, which is the parallelism limiting factor.
334 Moreover, it is now possible to take advantage of the window overlapping, first illustrated In Figure \ref{fig:median_overlap}, and more detailed in Figure \ref{fig:median3_overlap}. As the selection is first processed on the first 6 gray-level values and as it is exactly the number of pixels that overlap between two neighbor window of adjacent pixels, it allows to save 6 texture fetches and one \texttt{minmax6} selection per thread. Again, speedup is expected through the modified kernel source code and the associated grid dimensions presented in Listing \ref{lst:medianForget2pix3}. Important differences to be noticed are pixel coordinates computation given thread index. As each thread has to process two pixels, the number of threads in each block is divided by 2, while the grid size remains the same. Consequently, in kernel code, each thread of block coordinates $(tx, ty)$ will be in charge of processing pixels of block coordinates $(2tx, ty)$ and $(2tx+1, ty)$; lines 5 and 6 implement this.
335
336 \begin{figure}
337    \centering
338    \includegraphics[width=4cm]{Chapters/chapter3/img/median3_overlap.png}
339    \caption{Illustration of how window overlapping is used to combine 2 pixel selections in 3$\times$3 median kernel.}
340    \label{fig:median3_overlap}
341 \end{figure}
342
343 \lstinputlisting[label={lst:medianForget2pix3},caption=kernel 3$\times$3 median filter processing 2 output pixel values per thread by a combined forgetfull selection.]{Chapters/chapter3/code/kernMedian2pix3.cu}
344
345 Running this ultimate kernel saves another 10\% of runtime, as shown in Figure \ref{fig:compMedians2} and provides the best peak pixel throughput known so far on C2070 of 1155~Mpixel/s which is 86\% of the maximum effective throughput.
346
347 \begin{figure}
348    \centering
349    \includegraphics[width=11cm]{Chapters/chapter3/img/debitPlot2.png}
350    \caption{Comparison of pixel throughput on GPU C2070 for the different 3$\times$3 median kernels.}
351    \label{fig:compMedians2}
352 \end{figure}
353
354 \section{Median filter 5$\times$5 and more}
355 Considering the maximum register count allowed dper thread (63) and trying to push this technique to its limit would let us design median filters up to 9$\times$9 pixel window. This maximum would actually use  $k_{81}=\lceil 81/2\rceil+1 = 42$ registers per thread plus a few ones used by the compiler to complete arithmetic operations (9) leading to a total register count of 51. 
356 This would oviously forbids us to compute more than one pixel per thread, but also would limit the number of concurrent threads per block. Our measurements show that this technique is still worth using for the 5$\times$5 median but that larger window sizes could take advantage of using shared memory.
357 The next two sections will first detail the particular case of the 5$\times$5 median through register-only method and then a generic kernel for larger window sizes.
358
359 \subsection{Median filter 5$\times$5: register only }
360 The minimum register count allowing to apply the forgetfull selection method to 5$\times$5 median filter is $k_{25}=\lceil 25/2\rceil+1 = 14$. Moreover, two adjacent overlapping windows share 20 pixels ($n^2-one\_column$) so that, when processing 2 pixels at once, from the first selection stage with 14 common values to the passing of the last common value, a count of 6 common selection stages can be carried out. That allows to limit the register count to 14+8=22 per thread. Figure \ref{fig:median5overlap}
361 \begin{figure}
362    \centering
363    \includegraphics[width=8cm]{Chapters/chapter3/img/median5_overlap.png}
364    \caption{Reduction of register count in 5$\times$5 register only median kernel, outputting 2 pixel at once. The first 6 forgetful selection stages are common to both processed center pixels. Only the last 5 selections have to be done separately.}
365    \label{fig:median5overlap}
366 \end{figure}
367 Listing \ref{lst:medianForget2pix5} reproduces the kernel \texttt{kernel\_medianForget2pix5} code where the common selection stages take place from line XX to line YY. The remaining separate selection stages occur between lines XX and YY after the separation of line GG.
368
369 \lstinputlisting[label={lst:medianForget2pix5},caption=kernel 5$\times$5 median filter processing 2 output pixel values per thread by a combined forgetfull selection.]{Chapters/chapter3/code/kernMedian2pix5.cu}
370
371 Timing results follow the same variations with image size than previous ones. That is why  Table \ref{tab:median5comp} shows only throughput values obtained for C2070 card and 4096$\times$4096 pixel image.
372
373 \begin{table}[h]
374 %\newlength\savedwidth
375 \newcommand\whline{\noalign{\global\savedwidth
376   \arrayrulewidth\global\arrayrulewidth 1.5pt}
377   \hline \noalign{\global\arrayrulewidth
378   \savedwidth}
379 }
380 \centering
381 {\scriptsize
382 \begin{tabular}{|l||c|c|c|c|}
383 \hline
384 \textbf{Implementation}&\shortstack{\textbf{registers only}\\\textbf{1 pix/thread}}&\shortstack{\textbf{registers only}\\\textbf{2 pix/thread}}&\shortstack{\textbf{libJacket}\\(interpolated)}&\shortstack{\textbf{shared mem}}\\\whline
385  \shortstack{\textbf{Throughput}\\\textbf{(MP/s)}}&551&738&152&540\\\hline
386 \end{tabular}
387 }  
388 \caption{Performance of various 5$\times$5 median kernel implementations, applied on 4096$\times$4096 pixel image with C2070 GPU card..}
389 \label{tab:median5comp}
390 \end{table}  
391
392 \subsection{True median filter n$\times$n}
393 Shared memory can represent an efficient way to reduce global or texture loads, but it is also a limiting factor for performance.
394 On Fermi GPUs (as C2070), a maximum of 48~kB of per block shared memory is avalaible. With 16-bit coded gray levels, that allows to store up to 24576 values, which can be organised as a square of 156$\times$156 pixels maximum.
395 A point is that it is not efficient to use the shared memory at its maximum, as it would reduce the number of blocks beeing run in parallel on each SM.
396 Another point is that it is not  possible to avoid bank conflicts when designing a generic median kernel.
397 Thus, the most efficient way to code a generic, large window, median filter, is to do without shared memory but use texture direct fetching.
398 Listing \ref{lst:medianForgetGeneric} reproduce such a code, where the most interesting part is between lines XX and YY, where the forgetfull selection has been generalized to an arbitrary window size.
399 Performance results summarized in table \ref{tab:medianForgetGeneric} demonstrate that such a method is far from beeing as efficient as small fixed-size implementations. 
400
401 \begin{table}[h]
402 %\newlength\savedwidth
403 \newcommand\whline{\noalign{\global\savedwidth
404   \arrayrulewidth\global\arrayrulewidth 1.5pt}
405   \hline \noalign{\global\arrayrulewidth
406   \savedwidth}
407 }
408 \centering
409 {\scriptsize
410 \begin{tabular}{|l||c|c|c|c|}
411 \hline
412 \shortstack{\textbf{Window size}\\(in pixels)}&\textbf{121}&\textbf{169}&\textbf{225}&\textbf{441}\\\whline
413  \shortstack{\textbf{Throughput}\\\textbf{(MP/s)}}& & & & \\\hline
414 \end{tabular}
415 }  
416 \caption{Performance of generic median kernel applied to various window sizes on 4096$\times$4096 pixel image.}
417 \label{tab:medianForgetGeneric}
418 \end{table} 
419
420 \lstinputlisting[label={lst:medianForgetGeneric},caption= generic median kernel by forgetfull selection.]{Chapters/chapter3/code/kernMedianForgetGeneric.cu}
421
422 \subsection{Fast approximated median filter n$\times$n}
423 If faster process is required, a possible technique is to split median selection in two separate 1-D stages: one in the vertical direction and the other in the horizontal direction. Image processing specialists would say that this method does not selects the actual median value. They would be right, but for large window sizes and \textit{real-life} images, the so selected value is statically near the true median value and often represents an acceptable approximation.
424 In this particular case, we use a Torben Morgensen sorting algorithm, as it only needs a few and fixed register count. 
425
426 \begin{table}[h]
427 %\newlength\savedwidth
428 \newcommand\whline{\noalign{\global\savedwidth
429   \arrayrulewidth\global\arrayrulewidth 1.5pt}
430   \hline \noalign{\global\arrayrulewidth
431   \savedwidth}
432 }
433 \centering
434 {\scriptsize
435 \begin{tabular}{|l||c|c|c|c|}
436 \hline
437 \shortstack{\textbf{Window size}\\(in pixels)}&\textbf{121}&\textbf{169}&\textbf{225}&\textbf{441}\\\whline
438  \shortstack{\textbf{Throughput}\\\textbf{(MP/s)}}& & & & \\\hline
439 \end{tabular}
440 }  
441 \caption{Performance of generic pseudo separable median kernel applied to various window sizes on 4096$\times$4096 pixel image.}
442 \label{tab:medianSeparable}
443 \end{table} 
444
445 \lstinputlisting[label={lst:medianSeparable},caption= generic pseudo median kernel.]{Chapters/chapter3/code/kernMedianSeparable.cu}
446
447
448 \section{Glossary}
449 \begin{Glossary}
450 \item[CUDA] Compute Unified Device Architecture.
451 \end{Glossary}
452
453 \putbib[Chapters/chapter3/biblio3]
454