framework for handbook added
[cacao.git] / doc / handbook / loopopt.tex
1 \section{Loop optimizations}
2
3 \subsection{Introduction}
4
5 In safe programming languages like Java, all array accesses are 
6 checked. It is assured that the used index is greater or equal to 
7 zero and less than the array length, otherwise an 
8 ArrayIndexOutOfBoundsException is thrown. It is obvious that this 
9 gain in saftey causes a run-time overhead that is especially 
10 unpleasant in loops where these checks have to be performed many 
11 times. Often it is possible to remove these checks in loops by 
12 inserting additional tests at the beginning of the loop and 
13 examinig the code structure and loop condition thereby saving run-
14 time speed at each loop execuion. The following algorithm performs 
15 this task for a special set of array accesses in loops. It should 
16 be obvious that it is not always possible to predict the value of 
17 an array index by inserting tests at the beginning of the loop. An 
18 example would be a global variable that is used as an array index. 
19 This variable could be changed by a different thread virtually any 
20 time, so removing a bound check for this array access is not a 
21 good idea. The following algorithm only performs bound check 
22 removal, when the induction variable (the variable used as array 
23 index) is local and is only modified by adding/subtracting a 
24 constant inside the loop body or remains unchanged (is constant). 
25 If a constant is used as index, optimzation can take place as 
26 well. When other variables are used to modify the induction 
27 variable or when different arithmetic operations are used, no 
28 optimization can take place. Nevertheless, the most common use for 
29 index variables in loops is their increment or decrement by a 
30 constant, so most of the array access should be considered for 
31 optimization. 
32
33 \subsection{Initialization}
34
35 Before array bound checks in loops can be eliminated, the loops 
36 have to be detected. The algorithm performs its analysis on 
37 intermediate code level, so we cannot rely on just looking at 
38 while/for statments. A basic block analysis has been completed, so 
39 the algorithm can work on this data structure already. It uses the 
40 Lengauer-Tarjan algorithm to find existing loops, which bases on 
41 determining the dominator tree (a reference with extensive 
42 documentation for this algorithm can be found in \cite{appel98}). It uses a 
43 depth first search on the control flow graph to build a spanning 
44 tree. Then the semidominator and dominator theorem are utilized to 
45 extract the dominator tree and by looking at back edges (a back 
46 edge is an edge where the target node dominates the source node) 
47 all loops can be detected. Before starting to look at each loop, 
48 the case of different loops that share the same header node has to 
49 be considered. The algorithm works on each loop not looking on any 
50 other loop and performs its optimzation. The procedures that 
51 analyze the control flow of the program, build a flow graph and 
52 extract the loops can be found in the files graph.c (for control 
53 flow graph) and loop.c (for the loop extractig algorithm). The 
54 control flow graph is simple built by looking at the last 
55 instruction of each basic block and then deciding, which other 
56 blocks can be reached by that instruction. One problem can occur, 
57 when lookup/table-switch instructions cause multiple jumps to the 
58 same node. It must be prevented that the target nodeĀ“s predecessor 
59 list contains that node more than once. So an additionally array 
60 is needed to prevent double entries in the predecessor array. When 
61 the necessary flow data structure and the list of all loops has 
62 been built, the main algorithm can start.
63
64
65 \subsection{Algorithm}
66
67 The procedures for the main algorithm can be found in analyze.c. 
68 Before each loops is processed on its own, two global 
69 preprocessing steps are necessary.
70
71 The first step deals with loops, that share the same header node. 
72 This special case can happen when a loop body ends with an if-
73 then/else statment. The loop finding algorithm then reports two 
74 different loops, which share the same header node. When additional 
75 tests are inserted at the header node and another loop sharing the 
76 same header is later evaluated, inserting different tests, 
77 problems could arise. To prevent this from happening, loops 
78 sharing the same header node are simply merged into a bigger one 
79 by unioning their nodes. Because the nodes of the loop are already 
80 sorted by increasing basic block numbers, a simple merge of the 
81 nodes can be done (analyze\_double\_headers).
82
83 The second step before the loop by loop analysis commences is the 
84 building of a loop hierarchie. Nested loops cause problems when 
85 variables that are used as array indexes elsewhere get modified. 
86 Because these modifications (eg. an increment by a constant) can 
87 happen an unknown number of times, their results are 
88 unpredictable. These modifications in nested loops have to be 
89 recognized and reacted upon accordingly. The algorithm builds a 
90 tree where each node represents a loop with its parent being the 
91 directly surrounding loop. A dummy root node, representing the 
92 whole function has to be inserted as well. When loops have to be 
93 duplicated because of optimzed array accesses, it is important to 
94 extend or duplicate exception entries as well. Because of that, 
95 exceptions are inserted into the tree as follows. Every exception 
96 is inserted at the node in the hierarchie that represents the 
97 loop, that directly contains this exception. When an exception is 
98 part of a nested loop, it is inserted only at the node, 
99 representing this nested loop, because by following this nodes 
100 parent pointers, we find all other loops, that contain the 
101 exception (analyze\_nested). Finally, the sequentiel order of the 
102 loops is determined by a topological sort of the loops, satisfying 
103 the condition, that all nested loops have to be optimzed before 
104 their parent loop is processed. This can be archieved by a post-
105 order traversal of the hierarchie tree with skipping the root 
106 node.  
107
108 After these global steps have been completed, each loop is 
109 processed. Then the loops are checked for array accesses 
110 (analyze\_for\_array\_access) and the process exits if none 
111 are found. Finally, two special cases must be considered.
112
113 \begin{enumerate}
114
115 \item The algorithm uses the loop condition to guarantee certain 
116 bounds for variables. If the loop condition consists of an or-
117 statement, these guarantees can not be held up. If the loop 
118 statement reads 
119
120 \begin{verbatim}
121 while ((var > 0) or true) 
122 \end{verbatim}
123
124 vars value is not  bound to be greater than zero. When an or-statement
125 is found, loop optimization is stopped.
126
127 \item Variables that are used as indexes in array accesses might be 
128 modified in exception handling code of try-catch statements inside 
129 the loop. Because code in catch blocks is not considered in the 
130 control flow graph and because these modifications happen 
131 unpredictable (and should occur rarely) loops containing these 
132 catch blocks with index variable modifications are not optimized.
133 During the scan for array accesses within the loop, all 
134 interesting variables are recorded. A variable is considered 
135 interesting, when it is used as index in an array access or when 
136 its value is changes (due to a store instruction) 
137 (analyze\_or\_exceptions and scan\_global\_list).
138
139 \end{enumerate}
140
141 The next step is responsible for the analyzation of the loop 
142 condition itself. The loop condition can create a so called 
143 dynamic constraint on a variable. If one side of the loop 
144 condition is constant during the execution of the loop the other 
145 side can be safely assumed to be less than, greater or equal 
146 than or equal to this side (depending on the operator) at the 
147 start of each loop pass. It is important to notice the difference 
148 to so called static constraints on variables. That means that 
149 these variables can be safely assumed to stay below or above a 
150 certain constant during the loop execution by simple testing them 
151 prior to loop entry. This is obviously true for constants but does 
152 also hold for variables that are only decremented or incremented. 
153 For these, a static lower or upper bound can be guaranteed for the 
154 whole loop execution by simply inserting a single test prior to 
155 loop entry. Dynamic constraints, on the contrary, can vary in 
156 different parts of the loop. After the variable is tested in the 
157 loop condition, it might be changed to different values in 
158 different paths of execution. All variables, that are never 
159 changed or that get only decremented/incremented have static and 
160 dynamic constraints, all others can only have dynamic ones 
161 (init\_constraint). 
162
163 Now the core bound check removal procedure is started. Beginning 
164 directly after the loop condition all changes of variables that 
165 are used as index variables somewhere in the loop are recorded. 
166 This is an iterative process since different paths of execution 
167 may yield different changes to these variables. Especially changes 
168 in nested loops cause these changes to become unpredictable and 
169 therefore no upper/lower bound can be held up any further. After 
170 this iterative process caused all changes to become stable, each 
171 array access is examined (e.g. a statement like
172 \begin{verbatim}
173 if (x == true) 
174     i++; 
175 else 
176     i+=2; 
177 \end{verbatim}
178 must result in an increase of 2 for i when control 
179 flow joins after the if-statement). If it is possible, by 
180 inserting additional static/dynamic tests as needed, to assure 
181 that the index variable is greater or equal to zero and less than 
182 the array length, the bound checks are removed 
183 (remove\_bound\_checks). It is possible that more than one static 
184 tests gets inserted for a single variable, when it is used in 
185 different array access (possibly with different constants added or 
186 subtracted). Because all tests have to hold for this variable to 
187 allow optimzed code to be executed, only the strictest tests have 
188 to be done (e.g. if an integer array x[] is accessed by i in the 
189 statements x[i+1] and x[i-1], it has to be guaranteed that i $>$ 1 
190 (for second statement) and that i $<$ arraylength-1 (for the first 
191 statement)). Parallel to the insertion of new tests, the number of 
192 needed instructions for the new loop header node is accordingly 
193 increased. When optimzing the loop, it is important to 
194 differentiate between the loop head and the rest of the basic 
195 blocks, forming the body. The dynamic constraints can only be used 
196 for array accesses in the loop body, as the loop comparison is 
197 done at the end of the header node. Nevertheless, all static 
198 constraints can still be used to optimze array access in the 
199 header block. Because it is possible that an array reference is 
200 null prior to entering the loop, all array references that are 
201 loaded to compute an arraylength have to be checked against null.
202
203 After all new tests have been determined, it is necessary to 
204 reorder the code and insert the new tests (insert\_static\_checks). 
205 The first step is the creation of a new header node. Because all 
206 jumps to the beginning of the old loop now have to get to the new 
207 header first, it is more efficient to just replace the code in the 
208 old header block by the new tests. So only jumps within the loops 
209 need to be patched and the rest of the code can remain untouched. 
210 For each constraint that has been found in the previous step of 
211 the algorithm, two values have to be loaded and are then compared. 
212 Because it is possible during runtime that these tests fail (and 
213 no guarantee can be made) a copy of the loop with the original, 
214 checked array accesses must exist. Depending on the outcome of the 
215 test cascade, a jump to the optimized or original loop is made. To 
216 copy the loop, all basic blocks that are part of the loop are 
217 copied and appended to the end of the global basic block list. 
218 After that, both the original and the copy of the loop need post- 
219 processing to redirect certain jump targets. All jumps to the old 
220 loop head (which now contains the static checks) in the original 
221 loop have to be redirected to the newly inserted block, that holds 
222 the code for the original loop head. In the copied loop, all jumps 
223 inside the loop have to be redirected to jumps to the copied 
224 blocks. When loops are duplicated, these changes must be reflected 
225 in the node list of all parent loops as well. So the hierarchie 
226 tree is climbed and the new nodes are added to all enclosing 
227 loops. Because these node lists are sorted, it is necessary to 
228 deal with the new loop head in a different way. The loop head has 
229 to be inserted into the correct place of the parent loops while 
230 all other copied nodes can simply be appended to the basic block 
231 list. 
232
233 Now all exceptions have to be examined. There are three different 
234 cases, that must be considered. An exception can be part of the 
235 loop body (ie. is inside the loop), an exception can contain the 
236 loop or an exception is in a different part of the code (and does 
237 not have to be further considered). To be able to find all 
238 exceptions that are part of the loop, the algorithm uses the 
239 hierarchie tree and gets all exceptions of all children nodes. The 
240 exception handlers of these loops have to be copied and jumps to 
241 the original loop have to be redirected to the copied loop. The 
242 start and end code pointers of the protected area have to be moved 
243 to the copied blocks. The exception handler code is identified by 
244 looking at the successor nodes starting from the handler block. As 
245 long as control flow does not reach a loop node again, all blocks 
246 are considered as part of the handler code. Exceptions that 
247 surround the loop have to be handled different. It is not 
248 necessary to copy the exception handler, as it is not part of the 
249 loop. Nevertheless it is important to extend the protected area to 
250 the copied blocks. So the first and last block (including copied 
251 exception handler blocks of nested loops) that got copied are 
252 stored. The exceptions are then extended to contain all these new 
253 blocks and jump to the original handler. As no code is duplicated, 
254 nothing needs to be patched. When climbing up the hierarchie tree 
255 by following the parent pointer, not all exceptions of parent 
256 nodes really enclose the loop. It is possible that a parent loop 
257 contains an exception and the loop, that is optimized, right after 
258 each other. Those exceptions must not be handled and are ignored. 
259 Because of the layout of the exception table, where appropriate 
260 exceptions (that could be nested) are found by a linear search 
261 from the start, it is necessary to insert newly created exceptions 
262 right after their original ones. 
263
264 One performance problem still remains after these modifications. 
265 The new loop header that replaces the original one is located 
266 exactly where the old one was. A fall through from the previous 
267 block (that could be part of the loop) to the loop header must be 
268 patched by inserting an additional goto-instruction to the 
269 original head of the loop. Because this would insert an additional 
270 jump into the loop body, performance may deteriorate. To solve 
271 this shortcoming, the new loop head has to be moved before the 
272 first node of the loop. This might cause the problem of moving the 
273 loop head to the beginning of the global basic block list. This 
274 pointer points to the initial basic block array and can not be 
275 easily reassigned. A new pointer is needed that temporary holds 
276 the begin of the basic block list and is assigned to the original 
277 pointer after all optimization step have been finished. Any fall 
278 through from the predecessor of the loop head now reaches the old 
279 loop head, that has been inserted right after the new loop head. 
280 After all loops have been processed, register allocation and code 
281 genration proceed as usual and optimized code is generated.
282
283 \subsection{Helper functions}
284
285 An important helper function is stored in tracing.c. This 
286 functions is needed to determine the variables/constants that 
287 participate in array accesses or comparisons. As all work is done 
288 on java bytecode, it is necessary to find the arguments of an 
289 interesting operation by examining the stack. Unfortunately these 
290 values can just be temporary and origin in variables loaded 
291 earlier and getting modified by arithmetic operations. To find the 
292 variables that participate in an array access, one has to walk 
293 back instructions and looking at the stack, until an appropriate 
294 load is found. The function tracing preforms this task by steping 
295 back instruction for instruction and record the stack changes 
296 these instructions cause until the correct load or push constant 
297 operating has been found or until it becomes clear, that it is 
298 impossible to determine the origin (e.g. when a function return 
299 value is used). The value is then delivered in a special structure 
300 and used by the main algorithm. 
301
302 \subsection{Performance - impact and gain}
303
304 It is obvious that the optimization process causes additionally 
305 compile time overhead but can give performance gains during 
306 runtime, when 3 less instructions are executed per array access 
307 many times in a loop. The additional overhaed is mainly caused by 
308 the needed control flow analysis, which has to be done for every 
309 method and is linear to the number of basic blocks. Then the loop 
310 scaning algorithm trys to find loops in the control flow graph. 
311 This algorithm has to be started every time a method is compiled 
312 and runs in O(n*ld(n)) where n is the number of basic blocks. 
313 After the loops have been scanned for array access, the runtime of 
314 the final bound check removal and loop copying process mainly 
315 depends on the nesting depth of the hierarchie tree. For every 
316 additional nesting depth, the number of basic blocks, that must be 
317 copied and patch is doubled, resulting in exponential overhead 
318 according to the nesting depth. Additionally, the search for 
319 modifications of index variables, which is an iterative process, 
320 becomes slowed down by deeply nested loops. Things get worse when 
321 many exceptions are involved, because not only exceptions in 
322 children nodes have to be considered, but all enclosing exceptions 
323 as well. Nevertheless those cases should rarely occur and in real 
324 environments, the net gain can be significant. Especially in tight 
325 loops, when arrays are initialized or their elements summed up, 
326 three less instructions can gain up to 30 percent speedup, when loops run 
327 many times.
328
329
330 \vspace{4mm}
331
332 \begin{tabular}{|l|c|c|c|}
333
334 \hline
335
336 & Compile time (in ms) & \multicolumn{2}{c|}{Run time (in ms) } \\
337
338 \multicolumn{1}{|c|}{\raisebox{1ex}{Cacao Options}} & javac & perf & sieve 1000 1000\\ \hline 
339
340 \rule{0mm}{5mm}with optimization (-oloop) & 176 & 1510 & 98 \\
341
342 without optimization & 118 & 1720 & 131 \\ 
343
344 \hline
345
346 \end{tabular}
347