]> AND Private Git Repository - book_gpu.git/commitdiff
Logo AND Algorithmique Numérique Distribuée

Private GIT Repository
modif
authorcouturie <couturie@extinction>
Wed, 28 Aug 2013 18:33:33 +0000 (20:33 +0200)
committercouturie <couturie@extinction>
Wed, 28 Aug 2013 18:33:33 +0000 (20:33 +0200)
BookGPU/BookGPU.tex
BookGPU/Chapters/chapter1/ch1.tex
BookGPU/Chapters/chapter2/biblio.bib
BookGPU/Chapters/chapter2/ch2.tex
BookGPU/Chapters/chapter2/ex1.cu
BookGPU/Chapters/chapter2/ex2.cu
BookGPU/Chapters/chapter3/ch3.tex
BookGPU/Chapters/chapter4/ch4.tex
BookGPU/frontmatter/preface.tex

index 299004069ab3f545515e814e4f9150aee90a38fe..16c1f8f038ccab442672150ebc73d924b7121cad 100755 (executable)
@@ -99,8 +99,8 @@
   frame=single,         
   % keywordstyle=[1]\textbf,   
   %identifierstyle=\textbf,
-  commentstyle=\color{white}\textbf,
-  stringstyle=\color{white}\ttfamily,
+  commentstyle=\color{darkgray}\textbf,
+  stringstyle=\color{darkgray}\ttfamily,
   % xleftmargin=17pt,
   % framexleftmargin=17pt,
   % framexrightmargin=5pt,
index 36820fd978b2221f032eafa6ebb2f14a49d5c61e..68605f604645cbbaadde0891af242f593447b628 100755 (executable)
@@ -8,15 +8,14 @@
 This chapter introduces the Graphics  Processing Unit (GPU) architecture and all
 the concepts needed to understand how GPUs  work and can be used to speed up the
 execution of some algorithms. First of all this chapter gives a brief history of
-the development  of the graphics  cards up to the point when they started being  used in order  to make
-general   purpose   computation.    Then   the   architecture  of   a   GPU   is
-illustrated.  There  are  many  fundamental  differences between  a  GPU  and  a
-tradition  processor. In  order  to benefit  from the  power  of a  GPU, a  CUDA
+the development  of the graphics cards up  to the point when  they started being
+used in order to perform general purpose computations.  Then the architecture of
+a GPU is illustrated.  There are  many fundamental differences between a GPU and
+a traditional  processor. In order to  benefit from the  power of a GPU,  a CUDA
 programmer needs to use threads. They have some particularities which enable the
 CUDA model to be efficient and scalable when some constraints are addressed.
 
-
-
+\clearpage
 \section{Brief history of the video card}
 
 Video  cards or graphics  cards have  been introduced  in personal  computers to
@@ -25,9 +24,9 @@ produce  high quality graphics  faster than  classical Central  Processing Units
 repetitive and very specific.  Hence,  some manufacturers have produced more and
 more sophisticated video cards, providing 2D accelerations, then 3D accelerations,
 then some  light transforms. Video cards  own their own memory  to perform their
-computation.  For at least two decades, every personal computer has had a video
+computations.  For at least two decades, every personal computer has had a video
 card which is simple for  desktop computers or which provides many accelerations
-for game and/or  graphic-oriented computers.  In the  latter case, graphic cards
+for game and/or  graphic-oriented computers.  In the  latter case, graphics cards
 may be more expensive than a CPU.
 
 Since  2000, video  cards have  allowed  users to  apply arithmetic  operations
@@ -41,7 +40,7 @@ handle the stream data with pipelines.
 
 Some researchers  tried to  apply those operations  on other  data, representing
 something different  from pixels,  and consequently this  resulted in  the first
-uses of video cards for  performing general purpose computation. The programming
+uses of video cards for  performing general purpose computations. The programming
 model  was not  easy  to use  at  all and  was very  dependent  on the  hardware
 constraints.   More precisely  it consisted  in using  either DirectX  of OpenGL
 functions  providing  an  interface  to  some classical  operations  for  videos
@@ -53,20 +52,20 @@ wrong, programmers had no way (and no tools) to detect it.
 
 In order  to benefit from the computing  power of more recent  video cards, CUDA
 was first proposed in 2007 by  NVIDIA. It unifies the programming model for some
-of  their most efficient  video cards.   CUDA~\cite{ch1:cuda} has  quickly been
+of  their most  efficient video  cards.  CUDA~\cite{ch1:cuda}  has  quickly been
 considered by  the scientific community as  a great advance  for general purpose
 graphics processing unit (GPGPU)  computing.  Of course other programming models
 have been  proposed. The  other well-known alternative  is OpenCL which  aims at
-proposing an alternative to CUDA  and which is multiplatform and portable. This
+proposing an alternative  to CUDA and which is  multiplatform and portable. This
 is a  great advantage since  it is even  possible to execute OpenCL  programs on
-traditional CPUs.  The main drawback is that it is less tight with the hardware
+traditional CPUs.  The main drawback is  that it is less tight with the hardware
 and  consequently sometimes  provides  less efficient  programs. Moreover,  CUDA
 benefits from  more mature compilation and optimization  procedures.  Other less
-known environments  have been proposed, but  most of them have  been discontinued, for
-example  we can  cite, FireStream  by ATI  which is  not maintained  anymore and
-has been replaced by  OpenCL, BrookGPU by  Standford University~\cite{ch1:Buck:2004:BGS}.
-Another environment based  on pragma (insertion of pragma  directives inside the
-code to  help the compiler to generate  efficient code) is called  OpenACC.  For a
+known environments have been proposed,  but most of them have been discontinued,
+such FireStream by ATI which is  not maintained anymore and has been replaced by
+OpenCL and  BrookGPU  by  Stanford  University~\cite{ch1:Buck:2004:BGS}.   Another
+environment based on  pragma (insertion of pragma directives  inside the code to
+help  the  compiler  to generate  efficient  code)  is  called OpenACC.   For  a
 comparison with OpenCL, interested readers may refer to~\cite{ch1:Dongarra}.
 
 
@@ -92,7 +91,7 @@ only  the data  change. It  is important  to keep  in mind  that multiprocessors
 inside a GPU have 32 cores. Later we will see that these 32 cores need to do the
 same work to get maximum performance.
 
-\begin{figure}[b!]
+\begin{figure}[t!]
 \centerline{\includegraphics[]{Chapters/chapter1/figures/nb_cores_CPU_GPU.pdf}}
 \caption{Comparison of number of cores in a CPU and in a GPU.}
 %[Comparison of number of cores in a CPU and in a GPU]
@@ -101,10 +100,10 @@ same work to get maximum performance.
 
 On the most powerful  GPU cards, called Fermi, multiprocessors  are called streaming
 multiprocessors  (SMs). Each  SM contains  32  cores and  is able  to perform  32
-floating points or integer operations per clock on  32 bit numbers  or 16 floating
-points per clock  on  64 bit numbers. SMs  have  their  own registers,  execution
+floating points or integer operations per clock on  32-bit numbers  or 16 floating
+points per clock  on  64-bit numbers. SMs  have  their  own registers,  execution
 pipelines and caches.  On Fermi architecture,  there are 64Kb shared memory plus L1
-cache  and 32,536 32 bit  registers per  SM. More  precisely the  programmer can
+cache  and 32,536 32-bit  registers per  SM. More  precisely the  programmer can
 decide what amounts  of shared memory and  L1 cache SM are to be used.  The constraint is
 that the sum of both amounts should be less than or equal to 64Kb.
 
@@ -122,13 +121,13 @@ through  the  use  of  cache  memories. Moreover,  nowadays  CPUs  carry out  ma
 performance optimizations  such as speculative execution  which roughly speaking
 consists of executing  a small part of the code in advance even if  later this work
 reveals itself  to be  useless. GPUs  do not have  low latency
-memory.   In comparison GPUs  have small  cache memories.  Nevertheless the
+memory.   In comparison GPUs  have small  cache memories; nevertheless the
 architecture of GPUs is optimized  for throughput computation and it takes into
 account the memory latency.
 
 
 
-\begin{figure}[b!]
+\begin{figure}[t!]
 \centerline{\includegraphics[scale=0.7]{Chapters/chapter1/figures/low_latency_vs_high_throughput.pdf}}
 \caption{Comparison of low latency of a CPU and high throughput of a GPU.}
 \label{ch1:fig:latency_throughput}
@@ -138,7 +137,7 @@ Figure~\ref{ch1:fig:latency_throughput}  illustrates   the  main  difference  of
 memory latency between a CPU and a  GPU. In a CPU, tasks ``ti'' are executed one
 by one with a short memory latency to get the data to process. After some tasks,
 there is  a context switch  that allows the  CPU to run  concurrent applications
-and/or multi-threaded  applications.  Memory latencies  are longer in a  GPU. Thhe
+and/or multi-threaded  applications.  Memory latencies  are longer in a  GPU. The
  principle  to   obtain  a  high  throughput  is  to   have  many  tasks  to
 compute. Later we  will see that these tasks are called  threads with CUDA. With
 this  principle, as soon  as a  task is  finished the  next one  is ready  to be
@@ -187,11 +186,7 @@ threads,  called  warps. Each  SM  alternatively  executes
 active warps  and warps becoming temporarily  inactive due to waiting of data
 (as shown in Figure~\ref{ch1:fig:latency_throughput}).
 
-\begin{figure}[b!]
-\centerline{\includegraphics[scale=0.65]{Chapters/chapter1/figures/scalability.pdf}}
-\caption{Scalability of GPU.}
-\label{ch1:fig:scalability}
-\end{figure}
+
 
 The key to scalability in the CUDA model is the use of a huge number of threads.
 In practice, threads are  gathered not only in warps but also in thread blocks. A
@@ -210,17 +205,24 @@ independence between thread blocks provides the scalability of CUDA codes.
 
 
 A kernel is a function which  contains a block of instructions that are executed
-by the  threads of a GPU.   When the problem considered  is a two-dimensional or three-dimensional  problem,  it is  possible  to group  thread  blocks  into a grid.   In
-practice, the number of  thread blocks and the size of thread  blocks are given as
-parameters  to  each  kernel.   Figure~\ref{ch1:fig:scalability}  illustrates  an
+by the  threads of a GPU.  When  the problem considered is  a two-dimensional or
+three-dimensional problem,  it is possible to  group thread blocks  into a grid.
+In practice, the number of thread blocks and the size of thread blocks are given
+as parameters  to each kernel.   Figure~\ref{ch1:fig:scalability} illustrates an
 example of a kernel composed of 8 thread blocks. Then this kernel is executed on
-a small device containing only 2 SMs.  So in  this case, blocks are executed 2
-by 2 in any order.  If the kernel is executed on a larger CUDA device containing
-4 SMs, blocks are executed 4 by 4 simultaneously.  The execution times should be
-approximately twice faster in the latter  case. Of course, that depends on other
+a small device containing only 2 SMs.  So in this case, blocks are executed 2 by
+2 in any order.  If the kernel  is executed on a larger CUDA device containing 4
+SMs, blocks are  executed 4 by 4 simultaneously.  The  execution times should be
+approximately twice as fast in the latter case. Of course, that depends on other
 parameters that will be described later (in this chapter and other chapters).
 
 
+\begin{figure}[t!]
+\centerline{\includegraphics[scale=0.65]{Chapters/chapter1/figures/scalability.pdf}}
+\caption{Scalability of GPU.}
+\label{ch1:fig:scalability}
+\end{figure}
+
 Thread blocks provide a way to cooperate  in the sense that threads of the same
 block   cooperatively    load   and   store   blocks   of    memory   they   all
 use. Synchronizations of threads in the same block are possible (but not between
@@ -245,10 +247,10 @@ very fast, so it is a good idea to use them whenever possible.
 
 Likewise each thread can access local  memory which, in practice, is much slower
 than registers.  Local memory is automatically used by the compiler when all the
-registers are  occupied. So the  best idea is  to optimize the use  of registers
+registers are  occupied, so the  best idea is  to optimize the use  of registers
 even if this involves reducing the number of threads per block.
 
-\begin{figure}[hbtp!]
+\begin{figure}[b!]
 \centerline{\includegraphics[scale=0.60]{Chapters/chapter1/figures/memory_hierarchy.pdf}}
 \caption{Memory hierarchy of a GPU.}
 \label{ch1:fig:memory_hierarchy}
index 0f3ad7e7ee809d986202bfcd2fccd9911a918f70..70a2c17f0e6e1e3e132e62a672d8168e77986dcf 100644 (file)
@@ -3,7 +3,7 @@
   title =      "{CUDA} by example: An Introduction To General-Purpose
                 {GPU} Programming",
   publisher =  "Ad{\-d}i{\-s}on-Wes{\-l}ey",
-  address =    "pub-AW:adr",
+  address =    "Upper Saddle River, NJ",
   pages =      "xix + 290",
   year =       "2010",
   LCCN =       "QA76.76.A65",
index 75be84bc7e690206142d347d57a3eacdca84f642..7fc84710cadc77fb079a3824bdf3b150aac0a281 100755 (executable)
@@ -23,16 +23,17 @@ are executed on a GPU. This code is in Listing~\ref{ch2:lst:ex1}.
 
 
 As GPUs have  their own memory, the first step consists  of allocating memory on
-the   GPU.   A   call   to  \texttt{cudaMalloc}\index{CUDA functions!cudaMalloc}
-allocates memory  on the GPU.  The  second parameter represents the  size of the
-allocated variables, this size is expressed in bits.
+the  GPU.    A  call  to   \texttt{cudaMalloc}\index{CUDA  functions!cudaMalloc}
+allocates memory on  the GPU. {\bf REREAD The first parameter of this  function is a pointer
+on a  memory on the  device, i.e. the  GPU.} The second parameter  represents the
+size of the allocated variables, this size is expressed in bits.
 \pagebreak
 \lstinputlisting[label=ch2:lst:ex1,caption=simple example]{Chapters/chapter2/ex1.cu}
 
 
 In this example, we  want to compare the execution time of  the additions of two
 arrays in  CPU and  GPU. So  for both these  operations, a  timer is  created to
-measure the  time. CUDA proposes to  manipulate timers quite  easily.  The first
+measure the  time. CUDA  manipulates timers quite  easily.  The first
 step is to create the timer\index{CUDA functions!timer}, then to start it, and at
 the end to stop it. For each of these operations a dedicated function is used.
 
@@ -64,7 +65,7 @@ CUDA). Blocks of threads and thread  indexes can be decomposed into 1 dimension,
 the dimension of blocks of threads  must be chosen carefully. In our example,  only one dimension is
 used.   Then using the notation  \texttt{.x}, we  can access  the  first dimension
 (\texttt{.y}  and \texttt{.z},  respectively allow access  to the  second and
-third dimension).   The variable \texttt{blockDim}\index{CUDA keywords!blockDim}
+third dimensions).   The variable \texttt{blockDim}\index{CUDA keywords!blockDim}
 gives the size of each block.
 
 
@@ -74,12 +75,12 @@ gives the size of each block.
 \section{Second example: using CUBLAS \index{CUBLAS}}
 \label{ch2:2ex}
 
-The Basic Linear Algebra Subprograms  (BLAS) allows programmers to use efficient
+The Basic Linear Algebra Subprograms  (BLAS) allow programmers to use efficient
 routines for basic linear operations. Those  routines  are heavily  used in  many
 scientific applications  and are optimized for  vector operations, matrix-vector
 operations,                           and                           matrix-matrix
 operations~\cite{ch2:journals/ijhpca/Dongarra02}. Some  of those operations seem
-to be  easy to  implement with CUDA.   Nevertheless, as  soon as a  reduction is
+to be  easy to  implement with CUDA; however, as  soon as a  reduction is
 needed, implementing an efficient reduction routine with CUDA is far from being
 simple. Roughly speaking, a reduction operation\index{reduction operation} is an
 operation  which combines  all the  elements of  an array  and extracts  a number
@@ -144,7 +145,7 @@ three loops. We assume that $A$, $B$  represent two square matrices and the
 result   of    the   multiplication    of   $A   \times    B$   is    $C$.   The
 element \texttt{C[i*size+j]} is computed as follows:
 \begin{equation}
-C[size*i+j]=\sum_{k=0}^{size-1} A[size*i+k]*B[size*k+j];
+C[size*i+j]=\sum_{k=0}^{size-1} A[size*i+k]*B[size*k+j].
 \end{equation}
 
 In Listing~\ref{ch2:lst:ex3},  the CPU computation  is performed using  3 loops,
index 8f2b404eff64f6c67d9674b52fdf5e6a03c00fd4..64c08dd68b68857d8ae2a15e9d3c8147eb18ec52 100644 (file)
@@ -42,7 +42,7 @@ int main( int argc, char** argv)
 
        unsigned int timer_cpu = 0;
        cutilCheckError(cutCreateTimer(&timer_cpu));
-  cutilCheckError(cutStartTimer(timer_cpu));
+       cutilCheckError(cutStartTimer(timer_cpu));
        for(i=0;i<size;i++) {
                h_arrayC[i]=h_arrayA[i]+h_arrayB[i];
        }
@@ -52,7 +52,7 @@ int main( int argc, char** argv)
 
        unsigned int timer_gpu = 0;
        cutilCheckError(cutCreateTimer(&timer_gpu));
-  cutilCheckError(cutStartTimer(timer_gpu));
+       cutilCheckError(cutStartTimer(timer_gpu));
        cudaMemcpy(d_arrayA,h_arrayA, size * sizeof(int), cudaMemcpyHostToDevice);
        cudaMemcpy(d_arrayB,h_arrayB, size * sizeof(int), cudaMemcpyHostToDevice);
        
index 156764dc320c5aa9e8c398d6ee73c170dbfbdd65..62931cdce0dc439a8bca3de46d8926d21723ce5a 100644 (file)
@@ -54,7 +54,7 @@ int main( int argc, char** argv)
 
        unsigned int timer_cpu = 0;
        cutilCheckError(cutCreateTimer(&timer_cpu));
-  cutilCheckError(cutStartTimer(timer_cpu));
+       cutilCheckError(cutStartTimer(timer_cpu));
        double dot=0;
        for(i=0;i<size;i++) {
                h_arrayC[i]=h_arrayA[i]+h_arrayB[i];
@@ -66,7 +66,7 @@ int main( int argc, char** argv)
 
        unsigned int timer_gpu = 0;
        cutilCheckError(cutCreateTimer(&timer_gpu));
-  cutilCheckError(cutStartTimer(timer_gpu));
+       cutilCheckError(cutStartTimer(timer_gpu));
        stat = cublasSetVector(size,sizeof(double),h_arrayA,1,d_arrayA,1);
        stat = cublasSetVector(size,sizeof(double),h_arrayB,1,d_arrayB,1);
        int nbBlocs=(size+nbThreadsPerBloc-1)/nbThreadsPerBloc;
index 1b2e263ffa5f3dbaebeea25d86c25383b41f6aa7..1cf40c71ef7eee1c3faa31009dc2beac20f441b1 100755 (executable)
@@ -59,9 +59,9 @@ The Makefile given in Listing \ref{lst:mkfile} shows how to adapt examples given
 
 
 \section{Performance measurements}
-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 \textbf{cutil} library. As usual, because the durations we are measuring are short and possibly subject to non negligible variations, a good practice is to measure multiple executions and report the mean runtime. All time results given in this chapter have been obtained through 1000 calls to each kernel.
+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 \textbf{cutil} library. As usual, because the durations we are measuring are short and possibly subject to nonnegligible variations, a good practice is to measure multiple executions and report the mean runtime. All time results given in this chapter have been obtained through 1000 calls to each kernel.
 
-Listing \ref{lst:chronos} shows how to use the dedicated \textbf{cutil} functions \index{Cutil library!timer usage}. Timer declaration and creation need to be performed only once while reset, start and stop functions can be used as often as necessary. Synchronization is mandatory before stopping the timer (Line 7), to avoid runtime measurement being biased.
+Listing \ref{lst:chronos} shows how to use the dedicated \textbf{cutil} functions\index{Cutil library!timer usage}. Timer declaration and creation need to be performed only once while reset, start and stop functions can be used as often as necessary. Synchronization is mandatory before stopping the timer (Line 7), to avoid runtime measurement being biased.
 \lstinputlisting[label={lst:chronos},caption=Time measurement technique using cutil functions]{Chapters/chapter3/code/exChronos.cu}
 
 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 makes it impossible to benchmark every possible combination and significant differences may occur between the speedups we report and those obtained with different devices. As a reference, our developing platform details as follows:
@@ -135,7 +135,7 @@ The first observation to make when analysing results of Table \ref{tab:medianHis
 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 their negligible impact compared to outer loops that fetch image pixels (from 256k to 16M instructions). 
 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. To illustrate this, we can observe that, 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}.
 
-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. Figure \ref{fig:median_overlap} shows for example that two $5\times 5$ windows, centered on two neighbor pixels share at least 16 pixels. On C2070 card, thanks to a more efficient caching mechanism, this effect is less. On GPUs, dependency on image size is low, and due to slightly more efficient data transfers when copying larger data amounts, pixel throughputs increases with image size. As an example, transferring a 4096$\times$4096 pixel image (32~MBytes) is a bit faster than transferring  a 512$\times$512 pixel image (0.5~MBytes) 64 times.
+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. Figure \ref{fig:median_overlap} shows for example that two $5\times 5$ windows, centered on two neighbor pixels share at least 16 pixels. On C2070 card, thanks to a more efficient caching mechanism, this effect is less. On GPUs, dependency on image size is low, and due to slightly more efficient data transfers when copying larger data amounts, pixel throughputs increases with image size. As an example, transferring a 4096$\times$4096 pixel image (32~MBytes) is a bit faster than transferring  a 512$\times$512 pixel image (0.5~MBytes) 64 times.
 \begin{figure}[h]
    \centering
    \includegraphics[width=5cm]{Chapters/chapter3/img/median_overlap.png}
@@ -192,7 +192,7 @@ On the GPU's side, we note high dependence on window size due to the redundancy
 \section{NVIDIA GPU tuning recipes}
 When designing GPU code, besides thinking of the actual data computing process, one must choose the memory type in which to store temporary data. Three types of GPU memory are available:
 \begin{enumerate}
-\item \textbf{Global memory, the most versatile:} \index{memory hierarchy!global memory}\\Offers the largest storing space and global scope but is the slowest (400 to 800 clock cycles latency). \textbf{Texture memory} is physically included into it, but allows access through an efficient 2D caching mechanism.
+\item \textbf{Global memory, the most versatile:} \index{memory hierarchy!global memory}\\Offers the largest storing space and global scope but is the slowest (400 to 800 clock cycles latency). \textbf{Texture memory} is physically included in it, but allows access through an efficient 2D caching mechanism.
 \item \textbf{Registers, the fastest:} \index{memory hierarchy!registers}\\Allow access without latency, but only 63 registers are available per thread (thread scope), with a maximum of 32K per Streaming Multiprocessor (SM). \index{register count}
 \item \textbf{Shared memory, a complex compromise:} \index{memory hierarchy!shared memory}\\All threads in one block can access $48~KBytes$ of shared memory, which is faster than global memory (20 clock cycles latency) but slower than registers. 
 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 serialized which may cause significant performance decrease. One easy way to avoid this is to ensure that two consecutive threads in one block always access 32-bit data at two consecutive addresses.  
@@ -201,7 +201,7 @@ However, bank conflicts can occur if two threads of a warp try to access data st
 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. 
 To overcome this, the most frequent choice made in efficient implementations found in literature is to use shared memory. Such option implies prefetching \index{prefetching}data prior to doing the actual computations, a relevant choice, as each pixel of an image belongs to $n^2$ different neighborhoods. Thus, it can be expected that fetching each gray-level value from global memory only once should be more efficient than doing 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\% (as an example, $32~ms$ for $5\times 5$ median on a 1024$^2$ pixel image, i.e., $33~MP/s$ ). 
 
-As for registers, designing a generic median filter that would use only that type of memory seems difficult, due to the above mentioned 63 register-per-thread limitation. \index{register count} 
+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. \index{register count} 
 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.
 
 Another track to follow in order to improve performance of GPU implementations consists of hiding latencies generated by arithmetic instruction calls and memory accesses. Both can be partially hidden by introducing Instruction-Level Parallelism \index{instruction-level parallelism}(ILP) and by increasing the data count outputted by each thread. Though such techniques may seem to break the NVIDIA occupancy paradigm, they can lead to dramatically higher data throughput values.
@@ -212,7 +212,7 @@ Designing a median filter dedicated to the smallest possible square window size
 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, i.e., more than four times the maximum register count allowed per thread (63).\index{register count} Considering that 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.
 
 \subsection{The simplest way}
-In the case of a 3$\times$3 median filter, the simplest solution consists of associating one register to each gray-level value, then sorting those 9 values and selecting the fifth one, i.e., 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{kernel\_Median3RegSort9()}), the constraint of using only registers forces the adoption of 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 (CPU median3 bubble sort).
+In the case of a 3$\times$3 median filter, the simplest solution consists of associating one register to each gray-level value, then sorting those 9 values and selecting the fifth one, i.e., 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{kernel\_Median3RegSort9()}), the constraint of only using registers forces the adoption of 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 (CPU median3 bubble sort).
 The diagram of Figure \ref{fig:compMedians1} summarizes these first results for C2070, obtained with a block size of 256 threads, and Xeon CPU. We included the maximum effective pixel throughput in order to see the improvement potential of the different implementations. We also introduced throughput achieved by libJacket, a commercial implementation, as it was the fastest known implementation of a $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, which 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 those authors used, but due to the way we perform memory transfers and our register-only method of storing temporary data.
 
 \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}
@@ -308,7 +308,7 @@ Considering the maximum register count allowed per thread (63) and trying to pus
 The next two sections will first detail the particular case of the 5$\times$5 median through register-only method and eventually a generic kernel for larger window sizes.
 
 \subsection{A register-only 5$\times$5 median filter \label{sec:median5}}
-The minimum register count required to apply the forgetful selection method to a 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 simultaneously, a count of 7 common selection stages can be carried out from the first selection stage with 14 common values to the processing of the last common value. This allows limiting register count to 22 per thread. Figure \ref{fig:median5overlap} describes the distribution of overlapping pixels, implemented in Listing \ref{lst:medianForget2pix5}: common selection stages take place from line 25 to line 37, while the remaining separate selection stages occur between lines 45 and 62 after the separation of line 40.
+The minimum register count required to apply the forgetful selection method to a 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 simultaneously, a count of 7 common selection stages can be carried out from the first selection stage with 14 common values to the processing of the last common value. This allows limiting the register count to 22 per thread. Figure \ref{fig:median5overlap} describes the distribution of overlapping pixels, implemented in Listing \ref{lst:medianForget2pix5}: common selection stages take place from line 25 to line 37, while the remaining separate selection stages occur between lines 45 and 62 after the separation of line 40.
 \begin{figure}
    \centering
    \includegraphics[width=6cm]{Chapters/chapter3/img/median5_overlap4.png}
@@ -347,7 +347,7 @@ which favors the use of shared memory. The 1D operation almost completely avoids
 Furthermore, the above-described forgetful selection method cannot be used anymore, as too many registers would be required. Instead, the Torben Morgensen sorting algorithm is used, as its required register count is both low and constant, and avoids the use of a local vector, unlike histogram-based methods.
 
 Listing \ref{lst:medianSeparable} presents a kernel code that implements the above considerations and achieves a 1D vertical $n \times 1$ median filter. The shared memory vector is declared as \texttt{extern} (Line 16) as its size is determined at runtime and passed to the kernel call as an argument. Lines 20 to 29 perform data prefetching, including the $2n$-row halo ($n$ at the bottom and $n$ at the top of each block). Then one synchronization barrier is mandatory (line 31) to ensure that all needed data is ready prior to its use by the different threads.
-Torben Morgensen sorting takes place between lines 37 and 66 and eventually, the transposed output value is stored in global memory at line 69. Outputting the transposed image in global memory saves time and allows to reuse the same kernel to achieve the second step, e.g 1D horizontal $n \times 1$ median filtering.
+Torben Morgensen sorting takes place between lines 37 and 66 and eventually, the transposed output value is stored in global memory at line 69. Outputting the transposed image in global memory saves time and allows the reuse of the same kernel to achieve the second step, e.g 1D horizontal $n \times 1$ median filtering.
 It has to be noticed that this smoother, unlike the technique we proposed for fixed-size median filters, cannot be considered as a state-of-the-art technique as, for example, the one presented in \cite{4287006}. However, it may be considered as a good, easy to use and efficient alternative as confirmed by the results presented in Table \ref{tab:medianSeparable}. Pixel throughput values achieved by our kernel, though not constant with window size, remain very competitive if window size is kept under $120\times 120$ pixels, especially when outputting 2 pixels per thread (in \cite{4287006}, pixel throughput is around 7MP/s).
 Figure \ref{fig:sap_examples2} shows an example of a $512\times 512$ pixel image, corrupted by a  \textit{salt and pepper} noise, and the denoised versions, outputted respectively by a $3\times 3$, a $5\times 5$, and a $55\times 55 $ separable smoother.
 \begin{figure}
index bd66882a86c3214df3b9ada877f4cb56a4a620f9..90612c9d798fdaee746e263d63901772d95ed11c 100644 (file)
@@ -20,7 +20,7 @@ to $I$ as an $H\times L$ pixel gray-level image and to $I(x,y)$ as the gray-leve
 value of each pixel of coordinates $(x,y)$.
 
 
-
+\clearpage
 \section{Definition}
 Within a digital image $I$, the convolution operation is performed between
 image $I$ and convolution mask \emph{h} (To avoid confusion with other
index 178e0cc3d9475f83ab3e103d5fe1ce3beda7e7d7..70eed509fc03c1f26e8ac5db5518a1def3daff1e 100644 (file)
@@ -4,23 +4,24 @@
 This book is intended to present the design of significant scientific
 applications on GPUs. Scientific applications require more and more
 computational power in a large variety of fields: biology, physics,
-chemisty, phenomon model and prediction, simulation, mathematics, etc.
+chemistry, phenomon model and prediction, simulation, mathematics, etc.
 
 In order to be able to handle more complex applications, the use of
 parallel architectures is the solution to decrease the execution
-times of these applications. Using simulataneously many computing
-cores can significantly speed up the processing time.
+times of these applications. Using  many computing
+cores simulataneously can significantly speed up the processing time.
 
 Nevertheless using parallel architectures is not so easy and has always required
 an  endeavor  to  parallelize  an  application. Nowadays  with  general  purpose
 graphics processing units (GPGPU), it  is possible to use either general graphic
 cards or dedicated graphic cards to  benefit from the computational power of all
-the cores  available inside these  cards. The NVidia company  introduced Compute
+the cores  available inside these  cards. The NVIDIA company  introduced Compute
 Unified Device Architecture (CUDA) in 2007 to unify the programming model to use
 their video card. CUDA is currently  the most used environment for designing GPU
-applications   although   some   alternatives   are   available,   for   example,
-Open Computing Language (OpenCL). According to  applications and the GPU considered, a speed  up from 5 up
-to 50, or even more can be expected using a GPU over computing with a CPU.
+applications although  some alternatives are  available, such as  Open Computing
+Language (OpenCL). According to applications  and the GPU considered, a speed up
+from 5 up to 50, or even more  can be expected using a GPU over computing with a
+CPU.
 
 The programming model of GPU is quite different from the one of
 CPU. It is well adapted to data parallelism applications. Several