View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  /****************************************************************
19   * Explanation of Use and Rationale For Procedures
20   * 
21   * This class is intended to serve as a "test stand" for loading
22   * images using the Apache Imaging (nee "Sanselan") package.
23   * It performs a loop that loads a specified image multiple times
24   * recording both memory and time required for the loading process.
25   * 
26   * The notes given below explain some of the operations of this
27   * test class and the reasons they were designed as they are.
28   * This test is by no means to be considered the "last word" in how
29   * to write a test application. The techniques described below have
30   * demonstrated themselves useful and relevant in developing speed
31   * enhancements for some of the Apache Imaging operations. But I know
32   * I haven't thought of everything and am actually hoping that
33   * someone will have suggestions for improvements.
34   * 
35   * 
36   * Prerequisites to Testing  --------------------------------
37   * 
38   * Whenever testing software performance, particularly timing, 
39   * there are a few important considerations that should be observed:
40   * 
41   *   a) Get a clean testing environment. In a modern computer 
42   *      system, there are dozens of processes running.  To whatever
43   *      degree possible, make sure you are not running competing
44   *      processes that will consume computer resources and contaminate
45   *      your timing results.
46   * 
47   *   b) Make sure you are testing what you think you are testing.
48   *      This guideline is especially true when comparing two different
49   *      approaches.  Eliminate as many variables from the analysis
50   *      as you possible can.
51   * 
52   *   c) When writing or modifying code, remember that no matter how
53   *      obvious and self-evidentially superior a particular approach
54   *      may seem, you don't really know if it's an improvement until you
55   *      test it. If nothing else, the experience of computer programming
56   *      teaches us to not to take anything for granted.
57   * 
58   *   d) Make sure the JVM is allowed a sufficiently large maximum
59   *      memory size.   Putting aside the fact that the default size
60   *      for the maximum memory use of the JVM could be too small for
61   *      handling test images, we also want to allocate a sufficiently
62   *      large memory size to ensure that it doesn't get too close to
63   *      the maximum size when performing timing tests.   When the JVM
64   *      detects that it is running up against the limits of its 
65   *      maximum memory size setting, it triggers garbage collection
66   *      operations that can contaminate timing values.
67   *        I usually try to set the maximum memory size to be at least
68   *      twice what I think I will need.  Traditionally, the memory 
69   *      size for the JVM is quite modest, perhaps 256 megabytes.  You
70   *      can alter that value by using something like the following
71   *      specification (check your own JVM version for alternate values):
72   *            -Xmx768M   (maximum of 768 megabytes)
73   *      
74   *  
75   * 
76   *  
77   * What the Test Application Does and Why ----------------------
78   * 
79   * 0.  Functions  ------------------
80   * This class reads the path to a graphics file from the command
81   * line and attempts to read it several times, measuring the time
82   * required to read it.   If you prefer, you may hardwire the code
83   * to use a specific file.  Take whatever approach is easiest... it
84   * shouldn't affect the accuracy of the results.
85   * 
86   * 1)  Specific Instances of Classes to Be Tested  -----------------
87   * The Apache Imagine package includes a set of "parsers" for
88   * reading different graphics file formats.  The package also includes 
89   * a general-purpose class called "Imaging" that determines
90   * the format an arbitrarily specified input
91   * file and internally processes it using the appropriate parser.
92   *    However, unless you wish to test the performance of the Imaging
93   * class itself, it is better to instantiate the proper subject-matter
94   * parser explicitly in your code.  In ordinary applications, it is often
95   * more convenient to use the Imaging class and let it take care
96   * of the details for you.  But in that "taking care of details" 
97   * operation, the Imaging class loads and instantiates a large
98   * number of different subject-matter parsers.  These operations take
99   * time, consume memory, and will color the results of any timing
100  * and memory-use measurements you perform.
101  * 
102  * 2) Repetition -----------------------------------------
103  * The example output from this program included below, shows that
104  * it performs multiple image-loading operations, recording both
105  * the time required for each individual load time and the overall
106  * average time required for most of the load operations (times are
107  * in milliseconds).  
108  * 
109  *  image size: 10000 by 10000
110  *  time to load image               memory
111  *  time ms      avg ms         used mb   total mb
112  * 15559.150     0.000    --    384.845   397.035 
113  *  8544.926     0.000    --    410.981   568.723 
114  *  8471.012  8471.012    --    411.563   695.723 
115  *  8626.015  8548.513    --    384.791   397.039 
116  * 
117  * Note that in the example output, the times for the first two load 
118  * operations are not included in the average.  The reason for this is
119  * that the first time a Java application performs some operation,
120  * it is likely to take a little longer than for subsequent
121  * operations due to the overhead for class loading and the 
122  * just-in-time (JIT) compiler.  Unless you're specifically interested
123  * in measuring the cost of start-up operations, the time they take
124  * will contaminate any timing values for the functions of interest.
125  *    My experience under Windows is that the overhead only affects the
126  * first time I load an image.  In Linux, I've noticed that it can sometimes
127  * carry over into the second.  In either case, two loop iterations
128  * has proven to be enough to isolate the start costs... but keep an eye
129  * on the individual times to make sure nothing unwanted is happening.
130  * 
131  * 3) Clean Up Memory Between Load Operations --------------------
132  * This test application specifically invokes the garbage collection
133  * method provided by the Java Runtime class.  It then executes a one-second
134  * sleep operation.
135  *    Recall that in Java, the JVM performs garbage collection in a 
136  * separate thread that runs whenever Java thinks it important to do so.
137  * We want to do what we can to ensure that the garbage collection operation,
138  * which consumes processor resources, doesn't do so while the application
139  * is loading an image.  To that end, the application invokes 
140  * Runtime.gc() and then allows the JVM one second to initiate and 
141  * complete the garbage collection.  However, the .gc() method is, at best,
142  * a "suggestion" that the JVM should run garbage collection.
143  * It does not guarantee that the garbage collection will be executed and
144  * completed immediately.  Thus the relatively long one-second delay
145  * between loop iterations.
146  * 
147  * ---------------------------------------------------------------------
148  * Good luck in using the class for testing.
149  * Feel free to modify it to suit your own requirements... Nothing
150  * I've done in this code is beyond improvement.   I hope it works
151  * well for you.
152  *                Gary Lucas --  May 2012.
153  * ---------------------------------------------------------------
154  * 
155  */
156 package org.apache.commons.imaging.examples;
157 
158 import java.awt.image.BufferedImage;
159 import java.io.File;
160 import java.io.IOException;
161 import java.util.Formatter;
162 import java.util.HashMap;
163 
164 import org.apache.commons.imaging.ImageReadException;
165 import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
166 import org.apache.commons.imaging.formats.tiff.TiffImageParser;
167 
168 /**
169  * A "test stand" for evaluating the speed an memory use of different Apache
170  * Imaging operations
171  */
172 public class ApacheImagingSpeedAndMemoryTest {
173 
174     /**
175      * Create an instance of the speed and memory test class and execute a test
176      * loop for the specified file.
177      * 
178      * @param args
179      *            the path to the file to be processed
180      */
181     public static void main(final String[] args) {
182         final String name = args[0];
183 
184         final ApacheImagingSpeedAndMemoryTest testStand = new ApacheImagingSpeedAndMemoryTest();
185 
186         testStand.performTest(name);
187     }
188 
189     /**
190      * Loads the input file multiple times, measuring the time and memory use
191      * for each iteration.
192      * 
193      * @param name
194      *            the path for the input image file to be tested
195      */
196     private void performTest(final String name) {
197         final File target = new File(name);
198         final Formatter fmt = new Formatter(System.out);
199 
200         double sumTime = 0;
201         int n = 1;
202         for (int i = 0; i < 10; i++) {
203             try {
204                 ByteSourceFile byteSource = new ByteSourceFile(target);
205                 // This test code allows you to test cases where the
206                 // input is processed using Apache Imaging's
207                 // ByteSourceInputStream rather than the ByteSourceFile.
208                 // You might also want to experiment with ByteSourceArray.
209                 // FileInputStream fins = new FileInputStream(target);
210                 // BufferedInputStream bins = new BufferedInputStream(fins);
211                 // ByteSourceInputStream byteSource =
212                 // new ByteSourceInputStream(bins, target.getName());
213 
214                 // ready the parser (you may modify this code block
215                 // to use your parser of choice)
216                 HashMap<String, Object> params = new HashMap<>();
217                 TiffImageParser tiffImageParser = new TiffImageParser();
218 
219                 // load the file and record time needed to do so
220                 final long time0 = System.nanoTime();
221                 BufferedImage bImage = tiffImageParser.getBufferedImage(
222                         byteSource, params);
223                 final long time1 = System.nanoTime();
224 
225                 // tabulate results
226                 final double testTime = (time1 - time0) / 1000000.0;
227                 if (i > 1) {
228                     n = i - 1;
229                     sumTime += testTime;
230                 }
231                 final double avgTime = sumTime / n;
232 
233                 // tabulate the memory results. Note that the
234                 // buffered image, the byte source, and the parser
235                 // are all still in scope. This approach is taken
236                 // to get some sense of peak memory use, but Java
237                 // may have already started collecting garbage,
238                 // so there are limits to the reliability of these
239                 // statistics
240                 final Runtime r = Runtime.getRuntime();
241                 final long freeMemory = r.freeMemory();
242                 final long totalMemory = r.totalMemory();
243                 final long usedMemory = totalMemory - freeMemory;
244 
245                 if (i == 0) {
246                     // print header info
247                     fmt.format("%n");
248                     fmt.format("Processing file: %s%n", target.getName());
249                     fmt.format(" image size: %d by %d%n%n", bImage.getWidth(),
250                             bImage.getHeight());
251                     fmt.format(" time to load image    --         memory%n");
252                     fmt.format(" time ms      avg ms   --    used mb   total mb%n");
253                 }
254                 fmt.format("%9.3f %9.3f    --  %9.3f %9.3f %n", testTime,
255                         avgTime, usedMemory / (1024.0 * 1024.0), totalMemory
256                                 / (1024.0 * 1024.0));
257                 bImage = null;
258                 byteSource = null;
259                 params = null;
260                 tiffImageParser = null;
261             } catch (final ImageReadException ire) {
262                 ire.printStackTrace();
263                 System.exit(-1);
264             } catch (final IOException ioex) {
265                 ioex.printStackTrace();
266                 System.exit(-1);
267             } finally {
268                 fmt.close();
269             }
270 
271             try {
272                 // sleep between loop iterations allows time
273                 // for the JVM to clean up memory. The Netbeans IDE
274                 // doesn't "get" the fact that we're doing this operation
275                 // deliberately and is apt offer hints
276                 // suggesting that the code should be modified
277                 Runtime.getRuntime().gc();
278                 Thread.sleep(1000);
279             } catch (final InterruptedException iex) {
280                 // this isn't fatal, but shouldn't happen
281                 iex.printStackTrace();
282             }
283 
284         }
285     }
286 }