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

Private GIT Repository
correc preface
[book_gpu.git] / BookGPU / Chapters / chapter8 / ch8.tex
1
2 \chapterauthor{Imen Chakroun}{Universit\'e Lille 1 CNRS/LIFL, INRIA Lille Nord Europe, Cit\'e scientifique - 59655, Villeneuve d'Ascq cedex, France\\}
3 \chapterauthor{Nouredine Melab}{Universit\'e Lille 1 CNRS/LIFL, INRIA Lille Nord Europe, Cit\'e scientifique - 59655, Villeneuve d'Ascq cedex, France\\}
4
5 \chapter{GPU-accelerated Tree-based Exact Optimization Methods}
6 \label{ch8:GPU-accelerated-tree-based-exact-optimization-methods}
7 \section{Introduction}
8 \label{ch8:introduction}
9
10 In practice, a wide range of problems can be modeled as NP-hard combinatorial optimization problems (COPs). Those problems consist in choosing the best combination out of a large finite set of possible combinations and are known to be large in size and difficult to solve to optimality. One of the most popular methods for solving exactly a COP (finding a solution having the optimal cost), is the Branch-and-Bound (B\&B) algorithm. This algorithm is based on an implicit enumeration of all the feasible solutions of the tackled problem. Enumerating the solutions of a problem consists in building a dynamically generated search tree whose nodes are subsets of solutions of the considered problem. The construction of such tree and its exploration is performed using four operators: branching, bounding, selection and pruning. Due to the exponentially increasing number of potential solutions, the B\&B algorithm explores only promising nodes of the search tree using an estimated optimal solution called ``lower bound'' of the associated sub-problem. 
11
12 \vspace{0.3cm}
13
14 Although this bounding mechanism allows to considerably reduce the exploration time, often only small or moderately-sized instances of COPs can be practically solved. For this reason, over the last decades, parallel computing has been revealed as an attractive way to deal with larger instances of COPs. However, while many contributions have been proposed for parallel B\&B methods using Massively Parallel Processors \cite{ch8:Allen_1997}, Networks or Clusters of Workstations \cite{ch8:Quinn_1990} and SMP machines \cite{ch8:Casadoa_2008}, very few contributions have been proposed for redesigning B\&B algorithms on Graphical Processing Units (GPUs) \cite{ch8:Carneiro_2011}. For years, the use of GPU accelerators was limited to graphics and video applications. Driven by the demand for high-definition 3D graphics on personal computers, GPUs have evolved into a highly parallel, multi-threaded and many-core environment. Their utilization has recently been extended to other application domains such as scientific computing \cite{ch8:Kurzak_2010}. 
15
16 \vspace{0.3cm}
17
18 In this work, we rethink the design and implementation of irregular tree-based algorithms such as B\&B algorithm on top of GPUs. During the execution of the B\&B algorithm, the number of new generated nodes and the number of not yet explored but promising nodes are variable and depend on the level of the tree being explored and on the best solution found so far. Therefore, due to such unstructured and unpredictable nature of its search tree, designing efficient B\&B on top of GPUs is not straightforward. We investigate two different approaches for designing GPU-based B\&B starting from the parallel models for B\&B identified in \cite{ch8:MelabHDR_2005}. The first one is based on the ``parallel tree exploration'' paradigm. This approach consists in exploring in parallel different sub-spaces of the tree. The second approach is based on the ``parallel evaluation of bounds'' approach. The two approaches have been applied to the permutation Flowshop Scheduling Problem \index{Flowshop Scheduling Problem} (FSP)(see Section~\ref{ch8:BB-FSP}) which is an NP-hard combinatorial optimization problem. The lower bound function used in this work for FSP is the one proposed in~\cite{ch8:Johnson_1954} for two machines and generalized in~\cite{ch8:Lenstra_1978} to more than two machines.
19
20 \vspace{0.3cm}
21
22 When rethinking those two parallel model for GPU's architectures, our main focus was on the lower bound function. Indeed, preliminary experiments we carried out on some Taillard's problem instances \cite{ch8:Taillard_1993} show that computing the lower bounds takes on average between 98\% and 99\% of the total execution time of the B\&B. The GPU-based lower bound's implementation raises mainly two challenges. On the one hand, having in mind that the execution model of GPUs is SIMD, irregular computations (containing loops and conditional instructions) contained in the lower bound function may lead to a very challenging issue: the thread or branch divergence. This problem drops down the performance and arises when threads of a same warp (the smallest executable unit of parallelism on the GPU) execute different data-dependent instructions. On the other hand, the lower bound computation usually uses large in size and frequently accessed data structures. Since GPU is a many-core co-processor device that provides a hierarchy of memories having different sizes and access latencies, the placement and sharing of these data sets become challenging.
23
24 \vspace{0.3cm}
25
26 The scope of this chapter is to design parallel B\&B algorithms on GPU accelerators to allow highly efficient solving of permutation-based COPs. To do so, our contributions consist in: (1) rethinking two approaches for parallel B\&B on top of GPUs, discussing the performances of each and identifying which best suits the GPU accelerators. (2) proposing a new approach for thread/branch divergence reduction through a thorough analysis of the different loops and conditional instructions of the bounding function. (2) defining an optimal mapping of the data structures of the bounding function on the hierarchy of memories provided in the GPU device through a careful analysis of both the data structures (size and access frequency) and the GPU memories (size and access latency). 
27
28 \vspace{0.3cm}
29
30 The chapter is organized in seven main sections. Section \ref{ch8:BB} presents the B\&B algorithm. Section \ref{ch8:Parallel-BB} introduces the different models used to parallelize B\&B algorithms. Section \ref{ch8:BB-FSP} briefly describes the Flowshop Scheduling permutation Problem. In Section~\ref{ch8:approach1}, we describe the GPU-accelerated B\&B based on the parallel tree exploration. In Section~\ref{ch8:approach2},  details about the second approach, the GPU-accelerated B\&B based on the parallel evaluation of lower bounds, are given. In Section \ref{ch8:ThreadDivergence}, the thread divergence issue related to the location of nodes in the B\&B tree and to the control flow instructions within the bounding operator is described. In Section \ref{ch8:DataAccessOpt}, the memory access optimization challenge is addressed and an overview of the GPU memory hierarchy and the used memory access pattern is given. In Section~\ref{ch8:Experiments}, we report experimental results showing the performances of each of two studied approaches compared to a sequential CPU-based execution of the B\&B and demonstrating the efficiency of the proposed optimizations.
31
32 \section{Branch-and-Bound \index{Branch-and-Bound} algorithm}
33 \label{ch8:BB}
34
35 Branch-and-bound algorithms are by far the most widely used methods for exactly solving large scale NP-hard combinatorial optimization problems. Indeed, they allow to find the optimal solution of a problem with proof of optimality. 
36
37 \vspace{0.3cm}
38
39 The basic idea of the B\&B algorithm consists in implicitly enumerating all the solutions of the original problem by only examining a subset of feasible solutions and eliminating the others when they are not likely to lead to a feasible or an optimal solution. Enumerating the solutions of a problem consists in building a dynamically generated search tree whose nodes are subsets of solutions of the considered problem. The construction of such tree and its exploration are performed using four operators: branching, bounding, selection and pruning.
40
41 \vspace{0.3cm}
42
43 The algorithm proceeds in several iterations during which the best solution found so far is progressively improved. During the exploration process, the search space is described by a pool of unexplored nodes and the best solution found so far. The generated and not yet examined (pending) nodes are kept into a list initialized with the original problem. At each iteration of the algorithm, the following steps are performed:
44
45 \begin{itemize}
46  \item The {\it selection operator} chooses one node to process among the pending nodes according to a defined strategy. If the selection is based on the depth of the sub-problem in the B\&B tree, we speak about a depth-first exploration strategy. A selection based on the breadth of the sub-problem is called a breadth-first exploration. A best-first selection strategy could also be used. It is based on the presumed capacity of the node to yield good solutions.
47  \item The {\it branching operator} subdivides a solution space into two or more disjointed sub-spaces to be investigated in a subsequent iteration.
48  \item The {\it bounding operator} computes a bound value of the optimal solution of each generated sub-problem.
49  \item Each sub-problem having a greater bound than the upper-bound, i.e. the cost of the best solution found so far, is eliminated using the {\it pruning operator}.
50 \end{itemize}
51
52 Algorithm \ref{ch8:algoBB} gives the general template of the Branch-and-Bound method.
53
54 \begin{algorithm}[H]
55
56 \SetAlgoLined  
57
58 \vspace{0.2cm}
59
60 Create the initial problem; \\
61 Inset the initial problem into the tree; \\
62 Set the Upper\_Bound to $\propto$;  \\
63 Set the Best\_Solution to $\emptyset$; \\
64
65 \While{ not\_empty\_tree() }
66 {
67     \vspace{0.2cm}      
68
69     Sub\_Problem = Take\_sub\_problem();  
70
71     \If{ Is\_leaf ( Sub\_Problem ) } 
72     {
73             Upper\_Bound = Cost\_Of( Sub\_Problem );\\
74             Best\_Solution = Sub\_Problem;
75     }
76     \Else
77     {
78        Lower\_Bound = compute\_lower\_bound(Sub\_Problem);
79       
80        \If{ Lower\_Bound $\leq$ Upper\_Bound } 
81        {        
82           Branch(Sub\_Problem); \\
83           Insert child sub problems into the tree; 
84        }        
85        \Else
86        {
87           Prune (Sub\_Problem); 
88        }
89     }
90 }
91
92 \caption{General template of the Branch-and-Bound Algorithm.}
93 \label{ch8:algoBB}
94 \end{algorithm}
95
96 \section{Parallel Branch-and-Bound algorithms}
97 \label{ch8:Parallel-BB}
98
99 Thanks to the bounding operator, B\&B allows to significantly reduce the computing time needed to explore the whole solution space. However, finding an optimal solution for large instances remains unpractical using a sequential B\&B. Therefore, parallel processing of these algorithms has been widely studied in the literature. In \cite{ch8:MelabHDR_2005}, a taxonomy of the various existing parallel paradigm used to parallelize the B\&B algorithm is presented.
100
101 \vspace{0.2cm}
102
103 This taxonomy based on the classification proposed in \cite{ch8:Gendron_1994} identified several models to accelerate the B\&B search. The first model we consider in this chapter is called ``parallel tree exploration model'' and belongs to the ``Tree-based'' strategies that aim to build and explore the B\&B tree in parallel. The second model called ``parallel evaluation of bounds model'' (evaluation of bounds in parallel) belong to the parallelization approach called ``Node-based''. This strategy aims to accelerate the execution of a particular operation at the node level.
104
105 \vspace{0.2cm}
106
107 \subsection{The parallel tree exploration model}
108 \label{ch8:para_tree}
109
110 Tree-based strategies consist in building and/or exploring the solution tree in parallel by performing operations on several sub-problems simultaneously. This coarse-grained type of parallelism affects the general structure of the B\&B algorithm and makes it highly irregular.\\
111
112 The parallel tree exploration \index{parallel tree exploration} model, illustrated in Figure \ref{ch8:parallel_tree}, consists in visiting in parallel different paths of the same tree. The search tree is explored in parallel by performing the branching, selection, bounding and elimination operators on several sub-problems simultaneously.\\
113
114 \begin{figure}
115   \begin{center}
116 \includegraphics[scale=0.5]{Chapters/chapter8/figures/parallel_exploration.eps}%
117
118 \caption{Illustration of the parallel tree exploration model}
119 \label{ch8:parallel_tree}
120   \end{center}
121 \end{figure}
122
123 \subsection{The parallel evaluation of bounds model}
124 \label{ch8:Node_parallel}
125
126 Node-based strategies introduce parallelism when performing the operations on a single problem. For instance, they consist in executing the bounding operation in parallel for each sub-problem to accelerate the execution. This type of parallelism has no influence on the general structure of the B\&B algorithm and is particular to the problem being solved.\\
127
128 The parallel evaluation of bounds \index{parallel evaluation of bounds} model, as shown in Figure \ref{ch8:bounds_parallel}, allows the parallelization of the bounding of sub-problems generated by the branching operator. This model is used in the case where the bounding operator is performed several times after the branching operator. The model does not change the order and the number of explored sub-problems in the parallel B\&B algorithm compared to the sequential B\&B.
129
130 \begin{figure}
131   \begin{center}
132 \includegraphics[scale=0.5]{Chapters/chapter8/figures/parallel_bounding.eps}%
133
134 \caption{Illustration of the parallel evaluation of bounds model}
135 \label{ch8:bounds_parallel}
136   \end{center}
137 \end{figure}
138
139 \section{The Flowshop Scheduling Problem}
140 \label{ch8:BB-FSP}
141
142 \subsection{Definition of the Flowshop Scheduling Problem} 
143 \label{ch8:LB-FSP}
144
145 As a case study for our GPU-based Branch-and-Bound, we considered the NP-hard and well-known problem in the scheduling theory: the "Permutation Flow-shop Scheduling Problem" (FSP). 
146 In this work, the mono-objective case is considered. The FSP aims to find the optimal schedule of n jobs on m machines so that the overall completion time of all jobs, called {\it makespan}, is minimized. 
147
148 \vspace{0.3cm}
149
150 Let us suppose the set of jobs is represented by J = {$j_1$, $j_2$, . . . $j_n$} and the set of machines is represented by M = {$m_1$,$m_2$, . . .$m_m$} organized 
151 in the line. Each job $j_i$ is a sequence of operations ji = { $oi_1$, $oi_2$, . . . $oi_m$ } where oim is the duration required for the job ji on the machine m. 
152 A feasible solution of the flowshop permutation should satisfy these constraints:
153
154 \begin{itemize}
155  \item A machine can not start processing a job if all the machines, which are located upstream, did not finish their treatment. Thus, the operation $oi_j$ cannot be processed by the machine $m_j$ if it is not completed on $m_j$ - 1. 
156 \item An operation can not be interrupted, and the machines are critical resources, because a machine processes one job at a time.
157 \item The sequence of jobs should be the same on every machine, e.g. if j3 is treated in position 2 on the first machine, j3 is also executed in position 2 on all machines.
158 \end{itemize}
159
160 Figure~\ref{flow-shop} illustrates a solution of a flow-shop problem instance defined by 6 jobs and 3 machines. 
161
162 \begin{figure}[h!]
163 \centering
164 \includegraphics[height=1.7cm,width=6.8cm]{Chapters/chapter8/figures/FlowShop.eps}
165 \caption{Flow-shop problem instance with 3 jobs and 6 machines.}
166 \label{flow-shop}
167 \end{figure}
168
169 \vspace{0.3cm}
170
171 \subsection{Lower Bound \index{Lower Bound} for the Flowshop Scheduling Problem}
172 \label{ch8:LB-FSP}
173
174 The lower bounding technique provides a lower bound (LB) for each sub-problem generated by the branching operator. The more the bound is accurate, the more it allows to eliminate not promising nodes from the search tree. Therefore, the efficiency of a B\&B algorithm depends strongly on the quality of its lower bound function. In this chapter, we use the lower bound proposed by Lenstra {\it et al.}~\cite{ch8:Lenstra_1978} for FSP, based on the Johnson's algorithm~\cite{ch8:Johnson_1954}.
175
176 \vspace{0.2cm}
177
178 The Johnson's algorithm allows to solve optimally FSP with two machines ($m=2$) using the following transitive rule $\preceq$:
179
180 $$J_i \preceq J_j \Leftrightarrow \min(p_{i,1}\ ;\ p_{j,2}) \leq
181 \min(p_{i,2}\ ;\ p_{j,1})$$
182
183 We recall that $p_{k,l}$ designates the processing time of the job $J_k$ on the machine $M_l$. From the above rule, it follows the Johnson's theorem: \\
184
185 \textbf{Jonhson's theorem} \emph{Given $P$ an FSP with $m=2$, if $J_i\preceq J_j$ there exists an optimal schedule for $P$ in which the job $J_i$ precedes the job $J_j$.}\\
186
187 According to Johnson's theorem, FSP with $m=2$ is solved with a time complexity of $O(n.log n)$. The optimal solution is obtained by first sorting in increasing order the jobs having a
188 processing time shorter on the first machine than on the second one~; Second, sorting in decreasing order the jobs having a shorter processing time on the second machine. 
189
190 \vspace{0.2cm}
191
192 In~\cite{ch8:JRJackson_1956} and~\cite{ch8:LGMitten_1959}, the Johnson's rule has been extended by Jackson and Mitten with lags which allowed further Lenstra {\it et al.} to propose a lower bound for FSP with $m \geq 3$. A lag~$l_j$ designates the minimum duration between the starting time of the job $J_j$ on the second machine and its finishing time on the first machine. Jackson and Mitten demonstrated that the optimal solution for FSP with $m=2$ can be obtained using the following transitive rule $\preceq$:
193
194 $$J_i \preceq J_j \Leftrightarrow \min(p_{i,1}+l_i\ ;\ l_j+p_{j,2})
195 \leq \min(l_i+p_{i,2}\ ;\ p_{j,1}+l_j)$$
196
197 Based on this rule, Lenstra {\it et al.}~\cite{ch8:Lenstra_1978} have proposed the following lower bound for a sub-problem associated to a partial schedule where a set {\Large $\jmath$} of jobs have to be scheduled on $m$ machines. $P_{Ja}^*(\jmath,M_k,M_l)$ represents the Jackson-Mitten optimal solution for the sub-problem that consists in scheduling the set {\Large $\jmath$} of jobs on the two machines $M_k$ and~$M_l$. The term  $r_{i,k} = \sum_{l<k} p_{i,l}$ designates the starting time of the job $J_i$ on the machine $M_k$. The other term $q_{j,l} = \sum_{k>l} p_{j,k}$ refers to the latency between the finishing time of $J_j$ on $M_l$ and the finishing time of the schedule.
198
199 $$LB(\jmath)=\max\limits_{1 \leq k < l \leq m}\{P_{Ja}^*(\jmath,M_k,M_l)+\min\limits_{(i,j)\in \jmath^2, i \neq
200 j}(r_{i,k}+q_{j,l}) \}$$
201
202 According to this $LB$ expression, the lower bound for the scheduling of a subset {\Large $\jmath$} of jobs is calculated by applying the Johnson's rule with lags considering all the couples
203 $(k,l)$ for $1 \leq k,l \leq m$ and $k<l$. As illustrated in Figure~\ref{LagKLExample}, the lag $l_j$ of a job $J_j$ for a couple $(k,l)$ of machines is the sum of the processing times of the job on
204 all the machines between~$k$~and~$l$.
205
206 \begin{figure}
207   \begin{center}
208 \includegraphics[width=8cm]{Chapters/chapter8/figures/johnson_with_lags.eps}%
209 \caption{The lag $l_j$ of a job $J_j$ for a couple $(k,l)$ of machines is the sum of the processing times of the job on all the machines between~$k$~and~$l$.}
210 \label{LagKLExample}
211   \end{center}
212 \end{figure}
213
214 \section{GPU-accelerated B\&B based on the parallel tree exploration (GPU-PTE-BB)}
215 \label{ch8:approach1} 
216
217 The first approach we investigate for designing B\&B on GPUs consists in exploring in parallel the generated search tree. The idea is to divide the global search space into disjoint sub-spaces that are explored in parallel by the GPU threads. As explained in Section \ref{ch8:BB}, during the execution of a B\&B, the search space is described by a list of unexplored (pending) nodes and the best solution found so far. In the considered GPU-based scheme, a set of parent nodes is selected from this list according to their depth: deepest pending nodes are the first selected. The selected pool of nodes is off loaded to the GPU where each thread builds its own local search tree by applying the {\it branching}, {\it bounding} and {\it pruning} operators to the assigned node.
218
219 \vspace{0.2cm}
220
221 \begin{figure}[h!]
222 \centering
223 \includegraphics[height=8cm, width=8.1cm]{Chapters/chapter8/figures/Diagram1.eps}
224 \caption{The overall architecture of the parallel tree exploration-based GPU-accelerated Branch-and-Bound algorithm.} 
225 \label{tree_approach}
226 \end{figure}
227
228 \vspace{0.2cm}
229
230 According to the CUDA threading model, each thread has a unique identifier used to determine its assigned role, assigns specific input and output positions and selects work to perform. Therefore, each node (problem) from the pending list is mapped to a thread to ensure that each sub-space of the solution space is evaluated concurrently and is disjoint from others. Figure \ref{tree_approach} illustrates the scheme of the parallel tree exploration-based GPU-accelerated B\&B.
231
232 \section{GPU-accelerated B\&B based on the parallel evaluation of bounds (GPU-PEB-BB) }
233 \label{ch8:approach2} 
234
235 In the GPU-accelerated B\&B based on the parallel evaluation of bounds, illustrated in Figure~\ref{ch8:approach}, the generation of the sub-problems (elimination, selection and branching operations) to be solved is performed on CPU and the evaluation of their lower bounds (bounding operation) is executed on the GPU device. The pool of sub-problems generated on CPU is off-loaded to the GPU device to be evaluated by a pool of threads partitioned into blocks. Each thread applies the lower bound function to one sub-problem. Once the evaluation is completed, the lower bound values corresponding to the different sub-problems is returned to the CPU to be used by the elimination operator to decide either to be pruned or to be decomposed. The process is iterated until the exploration is completed and the optimal solution is found. 
236
237 \vspace{0.2cm}
238
239 \begin{figure}[h!]
240   \begin{center}
241 \includegraphics[scale=0.3]{Chapters/chapter8/figures/approach.eps}%
242 \caption{The overall architecture of the GPU-accelerated Branch-and-Bound algorithm based on the parallel evaluation of bounds.}
243 \label{ch8:approach}
244   \end{center}
245 \end{figure}
246
247 \vspace{0.2cm}
248
249 In both considered approaches, GPU-PEB-BB and GPU-PTE-BB, the GPU-based lower bound's implementation raises mainly two challenges. The first one is related to the ``single instruction multiple data'' (SIMD) model of the GPU and to the implementation of the LB. Indeed, although typically every GPU thread will run the identical lower bound function, the body of the lower bound can contains conditions on thread identifiers and data. This implies that different instructions are executed in some threads. In SIMD architectures like GPUs this behavior leads to the thread or branch divergence issue. This problem arises when threads of a same warp execute different data-dependent instructions. It might causes serious performance declining since computation occurs in parallel only when the same instructions are being performed. The second challenge consists in adjusting the pattern of accesses to the GPU device memory. Good placement of data over the different memory hierarchy grants programmers to further improve the throughput of many high-performance CUDA applications. For B\&B applied to FSP, threads of the same block perform concurrent accesses to the six data structures of the problem when they execute the lower bound function. These data structures have different sizes and access frequencies and should be wisely placed on the different memories of the GPUs that also have different sizes and latencies. 
250
251 \vspace{0.2cm}
252
253 In the following, we present how we dealt with the thread/branch divergence issue and maps the different data structures on the memory hierarchy of the GPU device taking into account the characteristics of the data structures and those of the different GPU memories.
254
255 \vspace{-0.4cm}
256
257 \section{Thread divergence \index{Thread divergence}}
258
259 \subsection{The thread divergence issue}
260
261 During the execution of an application on GPU, to each GPU multiprocessor is assigned one or more thread block(s) to execute. Those threads are partitioned into warps that get scheduled for execution. For each  instruction of the flow, the multiprocessor selects a warp that is ready to be run. A warp executes one common instruction at a time, so full efficiency is realized when all threads of a warp agree on their execution path. In this chapter, the G80 model, in which a warp is a pool of 32 threads, is used. If threads of a warp diverge via a data-dependent conditional branch, the warp serially executes each branch path taken. Threads that are not on the taken path are disabled, and when all paths complete, the threads converge back to the same execution path. This phenomenon is called thread/branch divergence and often causes serious performance degradations. Branch divergence occurs only within a warp; different warps execute independently regardless of whether they are executing common or disjointed code paths.
262
263 \vspace{0.2cm}
264
265 This section discusses thread divergence issue encountered when computing the bounds by GPU. The thread divergence occurs for two main reasons, namely the locations of nodes in the search tree and the control flow instructions within the bounding operator. 
266
267 \vspace{0.3cm}
268
269 \textbf{Divergence related to the location of nodes}
270
271 \vspace{0.3cm}
272
273 This divergence is related to the positions of the nodes in the B\&B search tree. Below is given an example from the source code of the used LB showing that the execution flow depends on the position of the node in the search tree. In the following piece of code, three methods are used {\it is\_leaf()}, {\it makespan()} and {\it lower\_bound()}. {\it is\_leaf()} tests if the node {\it \_node} is a leaf or an internal node. If {\it \_node} is a leaf, {\it makespan()} computes the cost of its makespan. Otherwise, {\it \_node} is an internal node and {\it lower\_bound()} computes the value of its lower bound. 
274
275 \begin{verbatim}
276         if (_node.is_leaf())
277            return _node.makespan();
278         else
279            return _node.lower_bound();
280 \end{verbatim}
281
282 \vspace{0.3cm}
283
284 \textbf{Divergence related to the control flow instructions}
285
286 \vspace{0.2cm}
287
288 Control flow refers to the order in which the instructions, statements or function calls are executed in a program. This flow is determined by instructions such as {\it if-then-else}, {\it for}, {\it while-do}, {\it switch-case}, etc. There are a dozen of such instructions in the implementation of our bounding operator. The source code examples given below show two scenarios in which this kind of instructions is used.
289
290 \begin{itemize}
291 \item Example 1:\\ \vspace{-0.4cm}
292 \begin{verbatim}
293    if( pool[thread_idx].begin != 0 ) 
294       time = TimeMachines[1] ;
295    else
296       time = TimeArrival[1] ;
297 \end{verbatim}
298
299 \item Example 2:\\ \vspace{-0.4cm} 
300 \begin{verbatim}
301    for(int k = 0 ; k < pool[thread_idx].begin; k++)
302       jobTime = jobEnd[k] ;  
303 \end{verbatim}
304
305 \end{itemize}
306
307 In these two examples, {\it thread\_idx} is the index associated to the current thread. Let suppose that the code of Example 1 is executed by $32$ threads, {\it pool[thread\_idx].begin} is equal to $0$ for the first thread, and {\it pool[thread\_idx].begin} is not equal to $0$ for the other $31$ threads. When the first thread executes the statement {\it ``time = TimeArrival[1];''}, 
308 all the other $31$ threads remain idle. Therefore, the GPU cores on which these $31$ threads are executed remain idle and can not be used during the execution of the statement {\it ``time = TimeArrival[1];``}. 
309
310 \vspace{0.2cm}
311
312 The same scenario occurs during the execution of Example 2. Let us suppose that the instruction is executed by $32$ threads, {\it pool[thread\_idx].begin} is equal to $100$ for the first thread, and {\it pool[thread\_idx].begin} is equal to $0$ for the other $31$ threads. When the first thread executes the loop $for$, all the other $31$ threads remain idle. 
313
314 \vspace{0.2cm}
315
316 Existing techniques for handling branch divergence either demand hardware support \cite{ch8:Fung} or require host-GPU interaction \cite{ch8:Zhang}, which incurs overhead. Some other works such as \cite{ch8:Han} intervene at the code level. They expose a branch distribution method that aims to reduce the divergent portion of a branch by factoring out structurally similar code from the branch paths. In our work, we have also opted for software-based optimizations like \cite{ch8:Han}. In fact, we figure out how to literally rewrite the branching instructions into basic ones in order to make thread execution paths uniform. We also demonstrate that we could ameliorate performances only by judiciously reordering data being assigned to each thread.
317
318 \subsection{Mechanisms for reducing branch divergence}
319
320 \vspace{0.3cm}
321
322  \textbf{Thread-data reordering}
323
324 \vspace{0.2cm}
325
326 At each iteration of our GPU-accelerated B\&B approach, several thousands of sub-problems are sent to the GPU. The GPU groups the received sub-problems into several warps according to their reception order. The first 32 sub-problems belong to the first warp, the following 32 sub-problems belong to the second warp, etc. Therefore, thread-data reordering technique sorts sub-problems before sending them to the GPU. These sub-problems are sorted according to their position in the B\&B tree. This sort of sub-problems allows to have warps containing more homogeneous sub-problems, and reduces the number of thread divergences. 
327
328 \vspace{0.2cm}
329
330  \textbf{Branch refactoring}
331
332 \vspace{0.2cm}
333
334 As quoted above, thread or branch divergence occurs when the kernel includes conditional instructions and loops that make the threads performing different control flows leading to their serial execution. In this chapter, we investigate the branch refactoring approach to deal with thread divergence. Branch refactoring consists in rewriting the conditional instructions so that threads of the same warp execute an uniform code avoiding their divergence. To do that, two major ``if" scenarios are studied and some optimizations are proposed accordingly. These two scenarios correspond to the conditional instructions contained in the $LB$ kernel code. In the first scenario, the conditional expression is a comparison of the content of a variable to 0. For instance, the following example extracted from the pseudo-code of the lower bound $LB$ illustrates such scenario.
335
336 \vspace{0.3cm}
337
338 \begin{tabular}{l}
339 \\
340 \small
341 \textsf{ if ( pool[thread\_idx].limit1 $\neq$ 0 ) tmp = MM[1];  }\\
342 \small
343 \textsf{ else  tmp = RM[1] ; }\\  \\
344 \end{tabular}
345
346 \vspace{0.2cm}
347
348 The refactoring idea is to replace the conditional expression by two functions namely $f$ and $g$ as shown in Equation~\ref{ch8:Eq1}.
349
350 \vspace{0.2cm}
351
352 The behavior of $f$ and $g$ fits the cosine trigonometric function. These functions return values between $0$ and $1$. An integer variable is used to store the result of the cosine function. Its value is $0$ or $1$ since it is rounded to $0$ if it is not equal to~$1$. In order to increase the performance the CUDA runtime math operations are used: $sinf(x)$, $expf(x)$ and so forth. Those functions are mapped directly to the hardware level~\cite{ch8:cuda}. They are faster but provide lower accuracy which does not matter in our case because the results are rounded to $int$. 
353
354 \begin{equation}
355 \begin{array}{lllllllll}
356 \small
357     &\multicolumn{8}{l}{ if (x \neq 0) ~ a = b[1]; ~~~~~~ if (x \neq 0) ~ a = b[1] + 0 \times c[1];} \\
358     & \multicolumn{2}{l}{} & ~~~~~~\Rightarrow & \multicolumn{2}{l}{} \\
359     &\multicolumn{2}{l}{\emph{else}}    $a = c[1];$ &     &\multicolumn{2}{l}{\emph{else}} $a = 0 \times b[1] + c[1];$ \\\\
360     & \multicolumn{6}{l}{\Rightarrow a = f(x) \times b[1] + g(x) \times c[1];}\\ \\
361     &\multicolumn{6}{l}{\emph{where:}}\\
362     &&\multicolumn{6}{l}{ f(x)=\left\{
363                     \begin{array}{lll}
364                         f(x) = 0    & if &x = 0\\
365                         1           &else\\
366                     \end{array}
367                 \right.}\\
368      &\multicolumn{6}{l}{\emph{and}}\\
369     &&\multicolumn{6}{l}{g(x)=\left\{
370                     \begin{array}{lll}
371                         g(x) = 1    & if &x = 0\\
372                         0           &else & \\
373                     \end{array}
374                   \right.}
375 \end{array}
376 \label{ch8:Eq1}
377 \end{equation}
378
379 \vspace{0.3cm}
380
381 The throughput of $sinf(x)$, $cosf(x)$, $expf(x)$ is one operation per clock cycle~\cite{ch8:cuda}. The refactoring result for the ``if" pseudo-code given above is the following:
382
383 \vspace{0.3cm}
384
385 \begin{tabular}{l}
386 \\
387 \small
388 \textsf{int coeff = \_\_cosf (pool[thread\_idx].limit1);}\\
389 \small
390 \textsf{tmp = (1 - coeff) $\times$ MM[1] +  coeff $\times$ RM[1];}\\ \\
391 \end{tabular}
392
393 \vspace{0.3cm}
394
395 The second "if" scenario considered in our study compares two values between themselves as shown in Equation~\ref{ch8:Eq2}.
396
397 \vspace{0.2cm}
398
399 \begin{equation}
400 \begin{array}{lllllllll}
401 \small
402     &\multicolumn{8}{l}{if (x > y)  a = b[1];~~~ \Rightarrow if (x - y \geq 1)   a = b[1];}\\\\
403     \Rightarrow &\multicolumn{6}{l}{if (x - y - 1 \geq 0)}& a = b[1];~~~~~ (x, y) \in N\\\\
404     \Rightarrow &\multicolumn{4}{l}{a = f(x, y) \times b[1] + g(x,y) \times a;}\\\\
405     &\multicolumn{6}{l}{\emph{where:}}\\
406     &&\multicolumn{6}{l}{ f(x,y)=\left\{
407                     \begin{array}{lll}
408                         1    & if &x - y - 1 \geq 0\\
409                         0    &if&x - y - 1 < 0\\
410                     \end{array}
411                 \right.}\\
412     &\multicolumn{6}{l}{\emph{and}}\\
413     &&\multicolumn{6}{l}{g(x,y)=\left\{
414                     \begin{array}{llll}
415                         0    & if &x - y - 1 \geq 0\\
416                         1    &if&x - y - 1 < 0 & \\
417                     \end{array}
418                 \right.}\\\\
419 \end{array}
420 \label{ch8:Eq2}
421 \end{equation}
422
423 \vspace{0.3cm}
424
425 For instance, the following example extracted from the pseudo-code of the lower bound $LB$ illustrates such scenario.
426
427 \vspace{0.3cm}
428
429 \footnotesize
430 \begin{tabular}{ll}
431 \\
432 \multicolumn{2}{l}{\textsf{if(RM[1]] $>$ MIN )}\{}  \textsf{Best\_idx = Current\_idx;}  \textsf{\}}\\\\
433 \end{tabular}
434 \normalsize
435
436 \vspace{0.3cm}
437
438 The same transformations as those applied for the first scenario are applied here using the exponential function. Recall that the exponential is a positive function which is equal to $1$ when applied to $0$. Thus, if $x$ is greater than $y$ then $expf(x-y-1)$ returns a value between $0$ and $1$. If the result is rounded to an integer value $0$ will be obtained. Now, if $x$ is less than $y$ then $expf(x-y-1)$ returns a value greater than $1$ and since the minimum between $1$ and the exponential is get, the returned result would be $1$. Such behavior satisfies exactly our prerequisites. The above ``if" instruction pseudo-code is now equivalent to:
439
440 \vspace{0.3cm}
441
442 \small
443 \begin{tabular}{l}
444 \\
445 \textsf{int coeff = min(1, \_\_expf(RM[1] - MIN - 1)); }\\
446 \textsf{Best\_idx = coeff $\times$ Current\_idx + ( 1 - coeff ) $\times$ Best\_idx ;}\\
447 \end{tabular}
448 \normalsize
449
450 \section{Memory access optimization}
451 \label{ch8:DataAccessOpt}
452
453 Memory access optimizations \index{Memory access optimizations} are by far the most studied area for improving GPU-based application performances. Indeed, adjusting the pattern of accesses to the GPU device memory grants programmers to further improve the throughput of many high-performance CUDA applications. The goal of memory access optimizations is generally to use as much fast memory and as little slow-access memory as possible. This section discusses how best to set up data LB items on the various kinds of memory on the device.   
454
455 \vspace{0.2cm} 
456
457 CUDA enabled devices use several memory spaces, which have different characteristics in term of sizes and access latencies. These memory spaces include global memory, local memory , shared memory, texture memory , and registers. Devices of compute capability 2.0 have also an L1 $/$ L2 cache hierarchy that is used to cache local and global memory accesses.
458
459 \begin{itemize}
460 \item At the thread-level, each thread has its own allocated registers and a private local memory. CUDA uses this local memory for thread-private variables that do not fit in the threads registers, as well as for stack frames and register spilling. \item At the thread block-level, each thread block has a shared memory visible to all its associated threads. \item At the grid-level, all threads have access to the same global memory. Texture and constant cached memories are two other memories accessible by all threads. 
461 \end{itemize}
462
463 The data access optimization challenge is to find the best mapping of the data structures of the application at hand (different sizes and access frequencies) and the GPU hierarchy of memories (different sizes and access latencies). For instance, of these different memory spaces, global memory is the most plentiful but the one with the highest access latency. On the contrary, shared memory is smaller in size but has much higher bandwidth and lower latency than the global memory.
464
465 \subsection{Complexity analysis of the memory usage of the Lower Bound }
466 \label{ch8:MemComplex}
467
468 In this section, the characteristics of the data structures used by the lower bound function are studied in terms of sizes and access frequencies. For an efficient implementation of the LB, six data structures are required: the  matrix $PTM$ of the processing times of the jobs, the matrix of lags $LM$, the Johnson's matrix $JM$, the matrix $RM$ of the earliest starting times of jobs, the matrix $QM$ of their lowest latency times and the matrix $MM$ containing the couples of machines. The complexities of the different data structures are summarized in Table~\ref{ch8:tabMemComplex} where the columns represent respectively the name of the data structure, its size and the number of times it is accessed.
469
470 \vspace{0.2cm} 
471
472 In the $LB$ expression, the computation of the term $P_{Ja}^*(\jmath,M_k,M_l)$ requires the calculation of the lag of each remaining job to be scheduled on the couple $(M_k,M_l)$ of machines using its processing times on these machines (Johnson's rule with lags). Such computation is repeated for each couple $(M_k,M_l)$ of machines with $1 \leq k,l \leq m$ and $k<l$. To avoid the repetitive computation of the lags, they are computed once at the beginning of the algorithm and stored in the matrix $LM$. The dimension of $LM$ is $n \times \frac{m\times (m-1)}{2}$, where $n$ and $m$ are respectively the number of jobs to be scheduled and $m$ the number of machines. $LM$ is accessed $n' \times \frac{m \times (m-1)}{2}$ times, $n'$ being the number of remaining jobs to be scheduled in the sub-problem for which the lower bound is being calculated. The processing times of all the jobs on all the machines are stored in the matrix $PTM$. This matrix has a dimension of $n \times m$ and is accessed $n' \times m \times (m-1)$ times.
473
474 \vspace{0.2cm} 
475
476 In addition, in order to avoid relaunching the Johnson's algorithm for each couple of machines and each subset of jobs, the Johnson's algorithm is computed once to find the optimal solutions on the couples of machines. These optimal solutions are then stored in the Johnson's matrix $JM$. This matrix has the same dimension as $LM$ and is accessed $n \times \frac{m \times (m-1)}{2}$ times during the computation of the lower bound. Finally, the $MM$ matrix that contains all the couples of machines has a dimension and access frequency of $m \times (m-1)$.  
477
478 \vspace{0.2cm} 
479
480 To reduce the computation time cost of the term $\min\limits_{(i,j)\in \jmath^2, i \neq j}(r_{i,k}+q_{j,l})$ in the $LB$ expression, two matrices are defined, namely $RM$ and $QM$. They are used to store respectively the lowest starting and latency times of all the jobs on each machine. Their dimension is $m$ and are accessed $ m \times (m-1)$ times and $ \frac{m \times (m-1)}{2}$ times respectively.
481
482 \begin{table}
483   \centering
484 \begin{tabular}{|c|c|c|}
485 \hline
486   \textbf{Matrix} & \textbf{Size} & \textbf{Number of accesses} \\
487  \hline
488  \hline
489    PTM &  $n \times m$ & $n' \times m \times (m-1)$ \\
490  \hline
491    LM & $n \times \frac{m \times (m-1)}{2}$ & $n' \times \frac{m \times (m-1)}{2}$ \\
492  \hline
493    JM & $n \times \frac{m \times (m-1)}{2}$ & $n \times \frac{m \times (m-1)}{2}$ \\
494  \hline
495    RM &  $m$ & $m \times (m-1)$ \\
496  \hline
497    QM &  $m$ & $\frac{m \times (m-1)}{2}$ \\
498  \hline
499    MM &  $m \times (m-1)$ & $m \times (m-1)$ \\
500  \hline
501 \end{tabular}
502 \vspace{0.5cm}
503  \caption{The different data structures of the $LB$ algorithm and their associated complexities in memory size and numbers of accesses. The parameters $n$, $m$ and $n'$ designate respectively the total number of jobs, the total number of machines and the number of remaining jobs to be scheduled for the sub-problems the lower bound is being computed.}
504 \label{ch8:tabMemComplex}
505 \end{table}
506
507 \subsection{Data placement pattern of the Lower Bound on GPU}
508 \label{ch8:MemComplex}
509
510 This section discusses how best to map the six data structures identified above on the various kinds of memories of the GPU device.
511
512 \vspace{0.2cm} 
513
514 The focus is put on the shared memory which is a key enabler for many high-performance CUDA applications. Indeed, because it is on-chip, shared memory has much higher bandwidth and lower latency than local and global memory. However, for large problem instances (large $n$ and $m$) the data structures especially JM and LM (see Table \ref{ch8:tabMemSizes}), do not fit in the shared memory for some GPU configurations. 
515
516 \vspace{0.2cm} 
517
518 In order to achieve further performances, we also take care of adequately use the global memory by judiciously configuring the L1 cache which greatly enables improving performance over direct access to global memory. Indeed, the GPU device we are using in our experiments is based on the NVIDIA Fermi architecture which introduced two new hierarchies of memories (L1 $/$ L2 cache)
519 compared to older architectures. 
520
521 \begin{table*}
522   \centering
523   \footnotesize
524   \begin{tabular}{|r|r|r|r|r|r|}
525     \hline
526 Prob. instance & JM & LM & PTM & RM, QM & MM \\
527     \hline
528     \hline
529 $200 \times 20$ & 38.000 (38KB) & 38.000 (76KB) & 4.000 (4KB) & 20 (0.04KB) & 380 (0.76KB) \\
530     \hline
531 $100 \times 20$ & 19.000 (19KB) & 19.000 (38KB) & 2.000 (2KB) & 20 (0.04KB) & 380 (0.76KB) \\
532     \hline
533 $50 \times 20$ & 9.500 (9.5KB) & 9.500 (19KB) & 1.000 (1KB) & 20 (0.04KB) & 380 (0.76KB) \\
534     \hline
535 $20 \times 20$ & 3.800 (3.8KB) & 3.800 (7.6KB) & 400 (0.4KB) & 20 (0.04KB) & 380 (0.76KB) \\
536    \hline
537   \end{tabular}
538 \vspace{0.5cm}
539 \caption{The sizes of each data structure for the different experimented problem instances. The sizes are given in number of elements and in bytes (between brackets).}
540 \label{ch8:tabMemSizes}
541 \end{table*}
542
543 \vspace{0.2cm} 
544
545 Taking into consideration the sizes of each data structure presented in Table \ref{ch8:tabMemSizes}, our challenge is to find which data structure has to be mapped on which memory and in some cases how to split the data  structures on different memories and efficiently manage their accesses. The sizes in bytes reported in Table \ref{ch8:tabMemSizes}, are computed knowing that in our implementation the elements of $JM$ and $PTM$ are unsigned chars (one byte) and that the elements of $LM$, $RM$, $QM$ and $MM$ are unsigned short ints (2 bytes). It is important here to highlight that the types of the data of the used matrices impact the size of each matrix. For instance, a matrix of $100$ integers has a size of $400$ octets while the same matrix with $100$ unsigned chars has a size of $100$ octets. In order to minimize the size of each of the used matrices, we analyzed the ranges of their values and defined their data types accordingly. For instance, in PTM all the processing times have positive values varying between $0$ and $100$. Therefore, we defined PTM as a matrix of \verb|unsigned char| having values in the range $[0, 255]$. Using the \verb|unsigned char| type instead of the integer type allows us to reduce by $4$ times the memory space occupied by PTM.
546
547 \vspace{0.2cm} 
548
549 According to the Table \ref{ch8:tabMemSizes} :
550
551 \begin{itemize}
552  \item The data structures $RM$, $QM$ and $MM$ are small sized matrices. Therefore, their impact on the performances is not significant whatever is the memory to which they are off-loaded. In particular, preliminary experiments proves that putting them on the shared memory would allows a very poor performance improvement. 
553 \item The $LM$ data structure is the double of the $JM$ in memory size but with a much lower access frequency. It is thus better to map $JM$ on the shared memory.
554 \item The $PTM$ has almost the same access frequency than $JM$ but requires less memory space.
555 \end{itemize}
556   
557 \vspace{0.2cm} 
558
559 Consequently, the focus is put on the study of the performance impact of the placement of $JM$ and $PTM$ on the shared memory. Three placement scenarios of $JM$ and $PTM$ are experimented and studied: (1) Only $PTM$ is stored in shared memory and all others are placed in global memory~; (2) Only $JM$ is stored in shared memory and all others are placed on global memory~; (3) $PTM$ and $JM$ are stored together in shared memory and all others are placed on global memory. 
560
561 \vspace{0.2cm} 
562
563 Taking profit from the configurable storage space provided in the new Fermi-based devices, the $64$ KB of local storage was spitted between the shared memory and the L1 cache according to the experimented scenario.
564
565 \begin{itemize}
566 \item For the scenario were the data structures are put on the shared memory the $64$ KB of available storage are split on $48$ KB for shared memory and $16$ KB for L1 cache.
567 \item For the scenario where the data sets are put on global memory we used $16$ KB for shared memory and $48$ KB for L1 cache.
568 \end{itemize}
569
570 \section{Experiments}
571 \label{ch8:Experiments}
572
573 In the following, we present the experimental study  we have performed with the aim to evaluate the performance impact of the GPU-accelerated bounding, the techniques for reducing the thread divergence and the proposed approach for data placement on the GPU memories. 
574
575 \subsection{Parameters settings} 
576
577 In our experiments, we used the flow-shop instances defined by Taillard \cite{ch8:Taillard_1993}. These standard instances are often used in the literature to evaluate the performance of methods that minimize the makespan. Optimal solutions of some of these instances are still not known. These instances are divided into groups of $10$ instances. In each group, the $10$ instances  are defined by the same number of jobs and the same number of machines. The groups of 10 instances have different numbers of jobs, namely $20$, $50$, $10$, $200$ and $500$, and different numbers of machines, namely $5$, $10$ and $20$. For example, there are $10$ instances with $200$ jobs and $20$ machines belonging to the same group of instances.
578
579 \vspace{0.2cm} 
580
581 In this work, we used only the instances where the number of machines is equal to $20$. Indeed, instances where the number of machines is equal to $5$ or $10$ are easy to solve. For these instances, the used bounding operator gives so good lower bounds that it is possible to solve them in few minutes using a sequential B\&B. Therefore, these instances do not require the use of a GPU.
582
583 \vspace{0.2cm} 
584
585 Our approach has been implemented using C-CUDA 4.0. The experiments have been carried out using a an Intel Xeon E5520 bi-processor coupled with a GPU device. The bi-processor is 64-bit, quad-core and has a clock speed of 2.27GHz. The GPU device is an Nvidia Tesla C2050 with 448 CUDA cores (14 multiprocessors with 32 cores each), a clock speed of 1.15GHz, a 2.8GB global memory, a 49.15KB shared memory, and a warp size of 32.
586
587 \subsection{Experimental protocol: computing the speed up}
588 \label{ch8:Protocol}
589
590 We need to compute the speed up of our approach to evaluate its performances. This speed up is obtained by comparing our GPU B\&B version to a sequential B\&B version deployed on one CPU core. However, all the instances used in our experiments are extremely hard to solve. Indeed, the resolution of each of these instances requires several months of computation on one CPU core. For example, the optimal solution of one of these instances defined by $50$ jobs and $20$ machines is obtained after $25$ days of computation using an average of $328$ CPU cores \cite{ch8:Mezmaz_2007}. 
591
592 \vspace{0.2cm} 
593
594 Using the approach defined in \cite{ch8:Mezmaz_2007}, it is possible to obtain a random list $L$ of sub-problems such as the resolution of $L$ lasts $T$ minutes with a sequential B\&B. So by initializing the pool of our sequential B\&B with the sub-problems of this list $L$, we are sure that the resolution of the sequential B\&B will last $T{cpu}$ minutes such as $T{cpu}$ will be approximately equal to $T$. Therefore, it will be possible to initialize the pool of our GPU B\&B with the same list $L$ of sub-problems in order to compute the speed up. Let suppose that the resolution of the GPU B\&B will last $T{gpu}$ minutes. So the speed up of our GPU algorithm will be equal to $Tcpu/Tgpu$. With this experimental protocol, the sub-problems explored by the GPU and CPU B\&B versions will be exactly the same. So to find the speed up associated to an instance, we:
595
596 \begin{itemize}
597 \item compute, using the approach defined in \cite{ch8:Mezmaz_2007}, a list $L$ of sub-problems such as the resolution of $L$ lasts $T$ minutes with a sequential B\&B, 
598 \item initialize the pool of our sequential B\&B with the sub-problems of this list $L$, 
599 \item solve the sub-problems of this pool with our sequential B\&B ,
600 \item get the sequential resolution time $T{cpu}$ and the number of explored sub-problems $N{cpu}$, 
601 \item check that $T{cpu}$ is approximately equal to $T$, 
602 \item initialize the pool of our GPU B\&B with the sub-problems of the list $L$, 
603 \item solve the sub-problems of this pool with our GPU B\&B,
604 \item get the GPU resolution time $T{gpu}$ and the number of explored sub-problems $N{gpu}$, 
605 \item check that $N{gpu}$ is exactly equal to $N{cpu}$, 
606 \item and finally compute the speed up associated to this instance by dividing $T{cpu}$ on $T{gpu}$ (i.e. $Tcpu/Tgpu$).
607 \end{itemize}
608
609 \vspace{0.2cm} 
610
611 Table \ref{ch8:instance_time} gives, for each instance according to its number of jobs and its number of machines, the used resolution time with a sequential B\&B. For example, the sequential resolution time of each instance defined with $20$ jobs and $20$ machines is approximately 10 minutes. Of course, the computation time of the lower bound of a sub-problem defined with $20$ jobs and $20$ machines is on average greater than the computation time of the lower bound of a sub-problem defined with $50$ jobs and $20$ machines. Therefore, as shown in this table, the sequential resolution time increases with the size of the instance in order to be sure that the number of sub-problems explored is significant for all instances.  
612
613 \begin{table*}
614 \setlength{\tabcolsep}{0.2cm}
615 \renewcommand{\arraystretch}{1.2}
616 \centering
617 \footnotesize
618 \begin{tabular}{|r|r|r|r|r|}
619 \hline
620 Instance (No. of jobs x No. of machines) & 20$\times$20 & 50$\times$20  & 100$\times$20 & 200$\times$20 \\
621 \hline
622 Sequential resolution time (minutes) & 10 & 50  & 150 & 300 \\
623 \hline
624 \end{tabular}
625 \vspace{0.3cm}
626 \caption{The sequential resolution time of each instance according to its number of jobs and machines}
627 \label{ch8:instance_time}
628 \end{table*}
629
630 \subsection{Performance impact of GPU-based parallelism}
631
632 The objective of the experimental study presented in this section is to compared the performances of both proposed approaches for designing B\&B on top of GPUs. 
633
634 Table \ref{ch8:ParaGPU1} and Table~\ref{ch8:ParaGPU2} report respectively the speedups obtained with the GPU-PTE-BB and GPU-PEB-BB approaches for different problem instances. The first part of both tables gives the size of the pool generated and evaluated on the GPU. The second part of the tables gives the average speedup for each group of instances and for each pool size. Each line corresponds to a group of $10$ instances defined by the same number of jobs and the same number of machines. 
635
636 The results obtained with the GPU-PTE-BB approach (see Table \ref{ch8:ParaGPU1}) show that exploring in parallel the tree search allows to speedup the execution of the B\&B compared to a CPU-based execution. Indeed, an acceleration factor up to 40.50 is obtained for the 20 $\times$ 20 problem instances using a pool of 262144 sub-problems. 
637
638 The results show also that the parallel efficiency decreases with the size of the problem instance. For a fixed number of machines (here 20 machines) and a fixed pool size, the obtained speedup decline accordingly with the number of jobs. For instance for a pool size of 262144, the acceleration factor obtained with 200 jobs (13.4) while it is (40.50) for the instances with 20 jobs. This behavior is mainly due to the overhead induced by the transfer of the pool of resulting sub-problems between the CPU and the GPU. For example, for the instances with 200 jobs the size of the pool to exchange between the CPU and the GPU is ten times bigger than the size of the pool for the instances with 20 jobs.
639
640 \begin{table*}
641 \setlength{\tabcolsep}{0.2cm}
642 \renewcommand{\arraystretch}{1.2}
643   \centering
644   \footnotesize
645  \begin{tabular}{|r|r|r|r|r|r|r|r|}
646     \hline
647 Pool size & 4096 & 8192 & 16384 & 32768 & 65536 & 131072 & 262144\\
648     \hline
649     \hline
650 (NJobs $\times$ NMachines) & \multicolumn{7}{|c|}{Average speedup for each group of 10 instances}\\
651     \hline
652 $200 \times $20 & 1.12 & 2.89 & 3.57 & 4.23 & 6.442 & 8.32 & 13.4\\
653     \hline
654 $100 \times $20 & 1.33 & 1.88 & 3.45 & 6.45 & 12.38 & 20.40 & 28.76 \\
655     \hline
656 $50 \times $20 & 2.70 & 3.80 & 6.82 & 13.04 & 23.53 & 30.94 & 37.66\\
657     \hline
658 $20 \times $20 & 6.43 & 11.43 & 20.14 & 27.78 & 30.12 & 35.74 & 40.50\\
659     \hline
660     \hline
661 % Total average speedup & 2.895 & 5.0 & 8.495 & 14.625 & 22.61 & 30.6 & 41.65\\   
662 %     \hline
663 %     \hline
664   \end{tabular}
665 \vspace{0.3cm}
666   \caption{Speedups for different problem instances and pool sizes with the GPU-PTE-BB approach.}
667 \label{ch8:ParaGPU1}
668 \end{table*}
669
670 The results obtained with the GPU-PEB-BB approach (see Table \ref{ch8:ParaGPU2}) show that evaluating in parallel the bounds of a selected pool, allow to significantly speedup the execution of the B\&B. Indeed, an acceleration factor up to 71.69 is obtained for the 200 $\times$ 20 problem instances using a pool of 262144 sub-problems. The results show also that the parallel efficiency grows with the size of the problem instance. For a fixed number of machines (here 20 machines) and a fixed pool size, the obtained speedup grows accordingly with the number of jobs. For instance for a pool size of 262144, the acceleration factor obtained with 200 jobs (71.69) is almost the double of the one obtained with 20 jobs (38.40). 
671
672 As far the pool size tuning is considered, we could notice that this parameter depends strongly on the problem instance being solved. Indeed, while the best acceleration is obtained with a pool size of 8192 sub-problems for the instances 50 $\times$ 20 and 20 $\times$ 20, the best speedups are obtained with a pool size of 262144 sub-problems with the instances 200 $\times$ 20 and 100 $\times$ 20.\\
673
674 \begin{table*}
675 \setlength{\tabcolsep}{0.2cm}
676 \renewcommand{\arraystretch}{1.2}
677   \centering
678   \footnotesize
679  \begin{tabular}{|r|r|r|r|r|r|r|r|}
680     \hline
681 Pool size & 4096 & 8192 & 16384 & 32768 & 65536 & 131072 & 262144\\
682     \hline
683     \hline
684 (NJobs $\times$ NMachines) & \multicolumn{7}{|c|}{Average speedup for each group of 10 instances}\\
685     \hline
686 $200 \times $20 & 42.83 & 56.23 & 57.68 & 61.21 & 66.75 & 68.30 & \textbf{71.69}\\
687     \hline
688 $100 \times $20 & 42.59 & 56.18 & 57.53 & 60.95 & 65.52 & 65.70 & \textbf{65.97}\\
689     \hline
690 $50 \times $20 & 42.57 & \textbf{56.15} & 55.69 & 55.49 & 55.39 & 55.27 & 55.14\\
691     \hline
692 $20 \times $20 & 38.74 & \textbf{46.47} & 45.37 & 41.92 & 39.55 & 38.90 & 38.40\\
693     \hline
694     \hline
695 % Total average speedup & 41.68 & 53.76 & 54.07 & 54.89 & 56.80 & 57.04 & 57.80\\
696 %     \hline
697 %     \hline
698   \end{tabular}
699 \vspace{0.3cm}
700   \caption{Speedups for different problem instances and pool sizes with the GPU-PEB-BB approach.}
701 \label{ch8:ParaGPU2}
702 \end{table*}
703
704 Compared to the parallel tree exploration-based GPU-accelerated B\&B approach, the parallel evaluation of bounds approach is by far much more efficient wherever the instance is. For example, while the GPU-PEB-BB approach reaches speedup of $\times$71.69 for the instance with 200 jobs on 20 machines, a speedup of a $\times$13.4 is measured with the parallel tree exploration-based approach which corresponds to an acceleration of $\times$5.56 . Moreover, on the contrary to the GPU-PEB-BB approach, in the GPU-PTE-BB the speedups decrease when the problem instance becomes higher. Remember here that while in the GPU-PEB-BB approach all threads evaluate only one node each whatever the permutation size is. In the GPU-PTE-BB, each thread branches all the children of its assigned parent node. Therefore, the bigger the size of the permutation is, the bigger the amount of work performed by each thread is and the bigger the difference between the workload is. Indeed, let us suppose that for the instance with $200$ jobs, the thread $0$ handles a node from the level $2$ of the tree and the thread $100$ handles a node from the level $170$ of the tree. In this case, the thread $0$ generates and evaluates $198$ nodes while the thread $100$ decomposes and bounds only $30$ nodes. The problem in this example is that the kernel execution would last until the thread $0$ finishes its work while the other threads might have ended their works and stayed idle.
705
706 \subsection{Thread divergence reduction}
707
708 The objective of this section is to demonstrate that the thread divergence reduction mechanisms we propose has an impact on the performance of the GPU accelerated B\&B and to evaluate how this impact is significant. 
709 In the following, the reported results are obtained with the GPU-accelerated B\&B based on the parallel evaluation of bounds.
710
711 \begin{table*}[!h]
712 \setlength{\tabcolsep}{0.2cm}
713 \renewcommand{\arraystretch}{1.2}
714   \centering
715   \footnotesize
716  \begin{tabular}{|r|r|r|r|r|r|r|r|}
717     \hline
718 Pool size & 4096 & 8192 & 16384 & 32768 & 65536 & 131072 & 262144\\
719     \hline
720     \hline
721 (NJobs $\times$ NMachines)  & \multicolumn{7}{|c|}{Average speedup for each group of 10 instances}\\
722     \hline
723     \hline
724 $200 \times $20 & 46.63 & 60.88 & 63.80 & 67.51 & 73.47 & 75.94 & \textbf{77.46}\\
725     \hline
726 $100 \times $20 & 45.35 & 58.49 & 60.15 & 62.75 & 66.49 & 66.64 & \textbf{67.01}\\
727     \hline
728 $50 \times $20 & 44.39 & \textbf{58.30} & 57.72 & 57.68 & 57.37 & 57.01 & 56.42\\
729     \hline
730 $20 \times $20 & 41.71 & \textbf{50.28} & 49.19 & 45.90 & 42.03 & 41.80 & 41.65\\
731     \hline
732     \hline
733 % Total average speedup & 44.52 & 56.99 & 57.72 & 58.46 & 59.84 & 60.35 & 60.64\\
734 %     \hline
735 %     \hline
736   \end{tabular}
737 \vspace{0.3cm}
738   \caption{Speedups for different instances and pool sizes using thread divergence management.}
739 \label{ch8:ParaDivergence}
740 \end{table*}
741
742 Table~\ref{ch8:ParaDivergence} shows the experimental results obtained using the sorting process and the refactoring approach presented in Section \ref{ch8:ThreadDivergence}. Results show that the proposed optimizations emphasize the GPU acceleration reported in Table~\ref{ch8:ParaGPU2} and obtained without thread divergence reduction. For example, for the instances of 200 jobs over 20 machines and a pool size of 262144, the average reported speedup is 77.46 while the average acceleration factor obtained without thread divergence management for the same instances and the same pool size is 71.69 which corresponds to an improvement of 7.68\%. Such considerable but not outstanding improvement is predictable, as claimed in \cite{ch8:Han}, since the factorized part of the branches in the FSP lower bound is very small. 
743
744 \subsection{Data access optimization}
745
746 The objective of the experimental study presented in this section is to find the best mapping of the six data structures of the lower bound LB kernel on the memories of the GPU device. In the following, the reported results are obtained with the GPU-accelerated B\&B based on the parallel evaluation of bounds.
747
748 Table~\ref{ch8:PTM-on-SM} reports the speedups obtained for the first experimented scenario where only the matrix $PTM$ is put on the shared memory. Results show that the speedup grows on average with the growing of the pool size in the same way as in Table~\ref{ch8:ParaDivergence}. For the largest problem instance and pool size, putting the PTM matrix on the shared memory improves the speedups up to ($14\%$) compared to those obtained when $PTM$ is on global memory reaching an acceleration of $\times 90.51$ for the problem instances $200 \times 20$ and a pool size of $262144$ sub-problems .  
749
750 \begin{table*}
751   \centering
752   \footnotesize
753   \begin{tabular}{|r|r|r|r|r|r|r|r|}
754     \hline
755 Pool size & 4096 & 8192 & 16384 & 32768 & 65536 & 131072 & 262144\\
756     \hline
757     \hline
758 (NJobs $\times$ NMachines)  & \multicolumn{7}{|c|}{Average speedup for each group of 10 instances}\\
759     \hline
760     \hline
761 $200 \times $20 & 54.03 & 67.75 & 68.43 & 72.17 & 82.01 & 88.35 & \textbf{90.51}\\
762     \hline
763 $100 \times $20 & 52.92 & 66.57 & 66.25 & 71.21 & 76.63 & 79.76 & \textbf{83.01}\\
764     \hline
765 $50 \times $20 & 49.85 & \textbf{65.68} & 64.40 & 59.91 & 58.57 & 57.36 & 55.09\\
766     \hline
767 $20 \times $20 & 41.94 & \textbf{60.10} & 48.28 & 39.86 & 39.61 & 38.93 & 37.79 \\
768      \hline
769     \hline
770 % Average Speedup& 49.69 & 65.03 & 61.84 & 60.79 & 64.21 & 66.10 & 66.60 \\                                             
771 %     \hline
772 %     \hline
773   \end{tabular}
774 \vspace{0.3cm}
775  \caption{Speedup for different FSP instances and pool sizes obtained with data access optimization. $PTM$ is placed in shared memory and all others are placed in global memory.}
776 \label{ch8:PTM-on-SM}
777 \end{table*}
778
779 Table~\ref{ch8:JM-on-SM} reports the behavior of the speedup averaged on the different problem instances (sizes) as a function of the pool size for the scenario where the Johnson's matrix is put on the shared memory. Results show that putting the $JM$ matrix on the shared matrix improves more the performances comparing to the first scenario where $PTM$ is put on the shared memory. Indeed, according to Table~\ref{ch8:tabMemComplex}, matrix $JM$ is accessed more frequently than matrix $PTM$. Putting $JM$ matrix on the shared memory allows accelerations up to $\times 97.83$ for the problem instances $200 \times 20$.
780
781 \begin{table*}
782   \centering
783   \footnotesize
784   \begin{tabular}{|r|r|r|r|r|r|r|r|}
785     \hline
786 Pool size & 4096 & 8192 & 16384 & 32768 & 65536 & 131072 & 262144\\
787     \hline
788     \hline
789 (NJobs $\times$ NMachines) & \multicolumn{7}{|c|}{Average speedup for each group of 10 instances}\\
790     \hline
791     \hline
792 $200 \times $20 & 63.01 & 79.40 & 81.40 & 84.02 & 93.61 & 96.56 & \textbf{97.83}\\
793     \hline
794 $100 \times $20 & 61.70 & 77.79 & 79.32 & 81.25 & 86.73 & 87.81 & \textbf{88.69}\\
795     \hline
796 $50 \times $20 & 59.79 & \textbf{75.32} & 72.20 & 71.04 & 70.12 & 68.74 & 68.07 \\
797     \hline
798 $20 \times $20 & 49.00 & \textbf{60.25} & 55.50 & 45.88 & 44.47 & 43.11 & 42.82 \\
799      \hline
800     \hline
801 % Average Speedup& 58.37 & 73.19 & 72.11 & 70.55 & 73.73 & 74.06 & 74.35 \\ 
802 %     \hline
803 %     \hline
804   \end{tabular}
805 \vspace{0.3cm}
806  \caption{Speedup for different FSP instances and pool sizes obtained with data access optimization. 
807 $JM$ is placed in shared memory and all others are placed in global memory.}
808 \label{ch8:JM-on-SM}
809 \end{table*}            
810
811 Table~\ref{ch8:JM-PTM-on-SM} reports the behavior of the average speedup for the different problem instances (sizes) with $20$ machines for the data placement scenario where both $PTM$ and $JM$ are put on shared memory. According to the underlying Table, the scenarios~(3) ($JM$ together or without $PTM$ in shared memory) is clearly better than the scenarii~(1)and~(2) (respectively $PTM$ in shared memory and $JM$ in shared memory) whatever is the problem instance (size). 
812
813 \begin{table*}
814   \centering
815   \footnotesize
816   \begin{tabular}{|r|r|r|r|r|r|r|r|}
817     \hline
818 Pool size & 4096 & 8192 & 16384 & 32768 & 65536 & 131072 & 262144\\
819     \hline
820     \hline
821 (NJobs $\times$ NMachines)  & \multicolumn{7}{|c|}{Average speedup for each group of 10 instances}\\
822     \hline
823     \hline
824 $200 \times $20 & 66.13 & 87.34 & 88.861 & 95.23 & 98.83 & 99.89 & \textbf{100.48}\\
825     \hline
826 $100 \times $20 & 65.85 & 86.33 & 87.60 & 89.18 & 91.41 & 92.02 & \textbf{92.39}\\
827     \hline
828 $50 \times $20 & 64.91 & \textbf{81.50} & 78.02 & 74.16 & 73.83 & 73.25 & 72.71\\
829     \hline
830 $20 \times $20 & 53.64 & \textbf{61.47} & 59.55 & 51.39 & 47.40 & 46.53 & 46.37\\
831      \hline
832     \hline
833 % Average Speedup & 62.63 & 79.16 & 78.51 & 77.49 & 77.87 & 77.92 & 77.99\\
834 %     \hline
835 %     \hline
836   \end{tabular}
837 \vspace{0.3cm}
838  \caption{Speedup for different FSP instances and pool sizes obtained with data access optimization. 
839 $PTM$ and $JM$ are placed together in shared memory and all others are placed in global memory.}
840 \label{ch8:JM-PTM-on-SM}
841 \end{table*}    
842
843 By carefully analyzing each of the scenarii of data placement on the memory hierarchies of the GPU, the recommendation is to put in the shared memory the Johnson's and the processing time matrices ($JM$ and $PTM$) if they fit in together. Otherwise, the whole or a part of the Johnson's matrix has to be put in priority in the shared memory. The other data structures are mapped to the global memory.
844
845 \section{Conclusion and Future Work}
846 \label{ch8:Conclusion}
847
848 In this chapter, we have revisited the design of parallel B\&B algorithms on GPU accelerators to allow highly efficient solving of permutation-based COPs. To do so, our contributions consist in: (1) rethinking two approaches for parallel B\&B on top of GPUs, discussing the performances of each and identifying which best suits the GPU accelerators. (2) proposing a new approach for thread/branch divergence reduction through a thorough analysis of the different loops and conditional instructions of the bounding function. (3) defining an optimal mapping of the data structures of the bounding function on the hierarchy of memories provided in the GPU device through a careful analysis of both the data structures (size and access frequency) and the GPU memories (size and access latency). 
849
850 In the first parallel tree-exploration-based B\&B, a set of pending nodes is selected from this list according to their depth and off-loaded to the GPU where each thread builds its own local search tree by applying
851 the branching, bounding and pruning operators to the assigned node. In the GPU-accelerated B\&B based on the parallel evaluation of bounds, the generation of the sub-problems (branching, selection and pruning operations) is performed on CPU and the evaluation of their lower bounds (bounding operation) is executed on the GPU device. Pools of sub-problems are off-loaded from CPU to GPU to be evaluated by blocks of threads. After evaluation, the lower bounds are returned to the CPU.
852
853 In both considered approaches, our focus is on the GPU-based lower bound's implementation and the associated thread divergence and data placement challenges. The proposed mechanisms for reducing the thread divergence issue are based on a thorough analysis of the different loops and conditional instructions of the lower bound function. On the one hand, the sorting process aims to homogenize the data of the sub-problems off-loaded to the GPU to minimize the number of threads that diverge on loop instructions. On the other hand, the technique of branch refactoring rewrite the conditional instructions into uniform instructions so that threads of the same warp execute a same code. The proposed data access optimization is based on a preliminary analysis of the lower bound function. Such analysis allowed us to identify six data structures for which we have proposed a complexity analysis in terms of memory size and access frequency. Due to the limited size of the shared memory the matrices do not fit in all together. According to the complexity study, the recommendation is to put in the shared memory the Johnson's and the processing time matrices ($JM$ and $PTM$) if they fit in together. Otherwise, the whole or a part of the Johnson's matrix has to be put in priority in the shared memory. The other data structures are mapped to the global memory. Such recommendation has been confirmed through extensive experiments using a recent C2050 Tesla GPU card. 
854
855 The Flowshop Scheduling Problem has been considered as a case study. The proposed approaches have been experimented using a Tesla C2050 GPU card on different classes of FSP instances. The experimental results show that the parallel evaluation of bounds is the parallelization paradigm that performs better on top of GPU accelerators. Compared to the parallel tree-exploration model, accelerations up to $\times$5.56 are achieved.
856
857 Experiments show also that the proposed refactoring approach improves the parallel efficiency whatever the FSP instance and the pool size are. However, the improvement was not significant because the factorized part of the branches in the FSP lower bound is very small. The optimizations obtained with the proposed thread reduction mechanisms allowed us to achieve accelerations up to $\times$77.46 compared to a sequential B\&B. The data access optimizations grant accelerations up to $\times 100$ compared to a single CPU-based B\&B.
858
859 In the near future, we plan to extend this work to a cluster of GPU-accelerated multi-core processors. From the application point of view, the objective is to optimally solve challenging and unsolved Flow-Shop instances as we did it for one 50$\times$20 problem instance with grid computing \cite{ch8:Mezmaz_2007}. Finally, we plan to investigate other lower bound functions to deal with other combinatorial optimization problems. 
860
861 \putbib[Chapters/chapter8/biblio8]