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 }