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 }