Printf formatting is often used in loops to produce neatly formatted tables. Sharkysoft's Printf for Java was designed with the ability to accelerate printf formatting in loops. To demonstrate the many ways in which Printf for Java can be optimized, we'll examine a simple program that outputs an ASCII table. Each of the code samples shown below produces exactly the same output.
This first example shows the easiest way to use printf in Java.
import com.sharkysoft.printf.Printf; import com.sharkysoft.printf.PrintfData; public class AsciiTable1 { public static void main(String[] args) { for (int i = 0; i < 256; ++ i) Printf.out ( "%#10c%+10d%10.4o%#10.2x\n", new PrintfData() .add((char) i) .add(i) .add(i) .add(i) ); } }
Since Java does not support variable argument lists, a special class, PrintfData
, is used to emulate them.
PrintfData
's add
method is overloaded for all of the primitive Java types.
The completion of the argument list is (optionally) indicated by calling the done()
method.
We're just kidding ourselves.
We all know that no matter how you dress it up, in Java an untyped argument list is nothing more than a vector of Object
s.
In fact, I'll confess now that PrintfData
simply uses a java.util.ArrayList
in its implementation.
Thus, in the above example, each add
call simply appends its arguments to the ArrayList
, and PrintfData
automatically converts the list into an Object array.
Let's shed the overhead of building an Object
array one call at a time.
Let's do it the fast way instead:
import com.sharkysoft.printf.Printf; public class AsciiTable2 { public static void main(String[] args) { for (int i = 0; i < 256; ++ i) Printf.out ( "%#10c%+10d%10.4o%#10.2x\n", new Object[] { new Character((char) i), new Integer(i), new Integer(i), new Integer(i) } ); } }
Pay careful attention to the syntax used here to build the argument list.
Not only does this syntax cause the Java compiler to automatically choose the correct length for the Object
array (in this case, 4), but it also populates the array with the 4 objects.
This approach requires significantly less processing overhead than PrintfData
, because the final array length is known at compile time (eliminating the need for dynamic array growth) and because there is no need to create an ArrayList
, or to convert it to an Object
array when you are done.
Here's another handly trick. Since the last three parameters to the argument list are the same value, why waste time creating three identical objects? Instead, let's optimize the loop further by creating just one Integer object, and then reusing it to populate the array:
import com.sharkysoft.printf.Printf; public class AsciiTable3 { public static void main(String[] args) { for (int n = 0; n < 256; ++ n) { Integer i = new Integer(n); Printf.out ( "%#10c%+10d%10.4o%#10.2x\n", new Object[] { new Character((char) n), i, i, i } ); } } }
This optimization eliminates 2 object creations for each iteration.
Before printf
can produce output, the format string must be parsed.
In the above examples, the format string must be parsed once for each pass through the loop.
What a waste of time!
Why not just parse the format string outside the loop, and then reuse the same parse results every time inside the loop?
This would certainly be an optimization.
In Printf for Java, format strings can be "pre-parsed" by constructing instances of PrintfFormatString
.
When PrintfFormatString
objects are used in place of a regular String
objects for format strings, the resulting printf operation is always faster.
The PrintfFormatString
s are like ready-to-go "formatting engines."
Here's what the optimized code looks like:
import com.sharkysoft.printf.Printf; import com.sharkysoft.printf.PrintfFormatString; public class AsciiTable4 { public static void main(String[] args) { PrintfFormatString fmt = new PrintfFormatString("%#10c%+10d%10.4o%#10.2x\n"); for (int n = 0; n < 256; ++ n) { Integer i = new Integer(n); Printf.out ( fmt, new Object[] { new Character((char) n), i, i, i } ); } } }
This little trick saves us the overhead of parsing the format string with each iteration through the loop.
Whereever printf is used in a loop or in a method that is called repeatedly, the programmer should consider storing the pre-initialized format string in a private static
variable in the class.
This way, it will be initialized just once, when the classes is loaded, and ready for use each time it is needed.
It's too bad the C version of printf
didn't have this feature!
You may think we have completed all of the optimizations possible for this application of Printf for Java.
Would you believe there's more?
Most C programmers are unfamiliar with the fact that printf
has a return value.
The ANSI C specification of printf
declares that in normal circumstances, the value returned by printf
shall be the total number of characters formatted.
What does this have to do with speeding up AsciiTable
?
Well, the relationship is indirect.
You see, Printf.out
doesn't actually do any formatting.
Instead, it delegates the job to another method, which performs the formatting and returns the results in a String
.
Then, Printf.out
simply writes the String
to System.out
and returns the String
's length.
In our case, however, we really don't care about counting characters, so we can bypass the syntactic sugar of Printf.out
and call the formatting routine ourselves.
The main printf formatting workhorse is -- you guessed it -- the format
method.
The format
method does the formatting and returns the formatted string.
In many ways, this is similar to sprintf
.
What you do with the string when you get it is your own business, not Printf
's.
Let's optimize:
import com.sharkysoft.printf.Printf; import com.sharkysoft.printf.PrintfFormatString; public class AsciiTable5 { public static void main(String[] args) { PrintfFormatString fmt = new PrintfFormatString("%#10c%+10d%10.4o%#10.2x\n"); for (int n = 0; n < 256; ++ n) { Integer i = new Integer(n); System.out.print ( Printf.format ( fmt, new Object[] { new Character((char) n), i, i, i } ) ); } } }
Not pretty, but a bit faster.
You see what's happening here?
We're taking the formatted string returned by format and passing it to System.out.print
for printing, bypassing the call to Printf.out
entirely.
Compare AsciiTable1
to AsciiTable5
and see just how much our code has evolved.
The first version was syntactically sweet, but the price for this sugar was unnecessary processing overhead at runtime (burning more calories, to keep the analogy).
The final version is not as pleasant to look at (health food), and requires significantly more labor from the programmer (gardening), but the optimizing techniques employed result in significantly faster output (OK, family-
A simple benchmark I ran, based on these examples, indicates that AsciiTable5
runs approximately 32% faster than AsciiTable1
.
For small formatting jobs, this speedup may not be worth the extra effort.
However, if your program uses Printf for Java to produce large, formatted tables that are thousands of lines longs, this 32% speedup may be worth it.
Copyright ©1998-2004 Sharkysoft. All rights reserved.