Performance Checklist Loops and Switches

- 166 - if filelist == null continue; for i = filelist.length-1; i = 0; i-- { f = new Fileroot, filelist[i]; if f.isDirectory dirs.pushroot+FS+filelist[i]; else iffilelist[i].toUpperCase .endsWithfileEnding System.out.printlnroot+ls+filelist[i]; } } } In fact, the structures of the two methods are almost the same. This second iterative version has the main part of the body wrapped in an extra loop that terminates when the extra variable holding the stack becomes empty. Otherwise, instead of the recursive call, the directory is added to the stack. In the cases of these particular search methods, the time-measurement comparison shows that the iterative method actually takes 5 longer than the recursive method. This is due to the iterative method having the overhead of the extra stack object to manipulate, whereas filesystems are generally not particularly deep the ones I tested on were not, so the recursive algorithm is not particularly inefficient. This illustrates that a recursive method is not always worse than an iterative one. Note that the methods here were chosen for illustration, using an easily understood problem that could be managed iteratively and recursively. Since the IO is actually the limiting factor for these methods, there would not be much point in actually making the optimization shown. For this example, I eliminated the IO overheads, as they would have swamped the times and made it difficult to determine the difference between the two implementations. To do this, I mapped the filesystem into memory using a simple replacement of the java.io.File class. This stored a snapshot of the filesystem in a hash table. Actually, only the full pathname of directories as keys, and their associated string array list of files as values, need be stored. This kind of trick—replacing classes with another implementation to eliminate extraneous overheads—is quite useful when you need to identify exactly where times are going.

7.6 Performance Checklist

Most of these suggestions apply only after a bottleneck has been identified: • Make the loop do as little as possible. o Remove from the loop any execution code that does not need to be executed on each pass. o Move any code that is repeatedly executed with the same result, and assign that code to a temporary variable before the loop code motion. o Avoid method calls in loops when possible, even if this requires rewriting or inlining. o Multiple access or update to the same array element should be done on a temporary variable and assigned back to the array element when the loop is finished. o Avoid using a method call in the loop termination test. o Use int data types preferentially, especially for the loop variable. o Use System.arraycopy for copying arrays. o Try to use the fastest tests in loops. o Convert equality comparisons to identity comparisons when possible. - 167 - o Phrase multiple boolean tests in one expression so that they short circuit as soon as possible. o Eliminate unneeded temporary variables from loops. o Try unrolling the loop to various degrees to see if this improves speed. • Rewrite any switch statements to use a contiguous range of case values. • Identify if a recursive method can be made faster. o Convert recursive methods to use iteration instead. o Convert recursive methods to use tail recursion. o Try caching recursively calculated values to reduce the depth of recursion. o Use temporary variables in place of passed parameters to convert a recursive method using a single search path into an iterative method. o Use temporary stacks in place of passed parameters to convert a recursive method using multiple search paths into an iterative method.

Chapter 8. IO, Logging, and Console Output

IO, IO, its off to work we go. —Ava Shirazi IO to the disk or the network is hundreds to thousands of times slower than IO to computer memory. Disk and network transfers are expensive activities, and are two of the most likely candidates for performance problems. Two standard optimization techniques for reducing IO overhead are buffering and caching. For a given amount of data, IO mechanisms work more efficiently if the data is transferred using a few large chunks of data, rather than many small chunks. Buffering groups data into larger chunks, improving the efficiency of the IO by reducing the number of IO operations that need to be executed. Where some objects or data are accessed repeatedly, caching those objects or data can replace an IO call with a hugely faster memory access or replace a slow network IO call with faster local disk IO. For every IO call that is avoided because an item is accessed from a cache, you save a large chunk of time equivalent to executing hundreds or thousands of simple operations. [1] [1] Caching usually requires intercepting a simple attempt to access an object and replacing that simple access with a more complex routine that accesses the object from the cache. Caching is easier to implement if the application has been designed with caching in mind from the beginning, by grouping external data access. If the application is not so designed, you may still be lucky, as there are normally only a few points of external access from an application that allow you to add caching easily. There are some other general points about IO at the system level that are worth knowing. First, IO buffers throughout the system typically use a read-ahead algorithm for optimization. This normally means that the next few chunks are read from disk into a low-level buffer somewhere. Consequently, reading sequentially forward through a file is usually faster than other orders, such as reading back to front through a file or random access of file elements . The next point is that at the system level, most operating systems support mmap , memcntl , and various shared-memory options. Using these can improve IO performance dramatically, but they also increase complexity. Portability is also compromised, though not as much as you might think. If you need to use these sorts of features and also maintain portability, you may want to start with the latest Perl distribution. Perl has been ported to a large number of systems at the time of