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