Created
March 19, 2019 18:01
-
-
Save pdeutsch/2547e0c83895bfdbbadb1f09f5c93bbe to your computer and use it in GitHub Desktop.
A test to determine the fastest way to format strings and doubles for logging. See the JavaDoc below for more details.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.google.common.base.Stopwatch; | |
import java.io.*; | |
import java.text.DecimalFormat; | |
import java.util.concurrent.TimeUnit; | |
/** | |
* This code attempts several different methods of creating and printing a string similar to: | |
* test1: 4379747.584; test2: 4379747.684; test3: 4379747.784 | |
* | |
* There were several things this was looking to address, but mainly what is the fastest way | |
* to format doubles when creating logs. Key findings: | |
* - String.format() is the slowest method | |
* - Decimal.format() with StringBuilder isn't much faster | |
* - A custom, fixed formatter is almost 5 times faster. | |
* - Using string concatenation with '+' signs is nearly as fast, but harder to read. | |
* | |
* The final result shows: | |
* testAll called, cnt=300000, test1=1.091, test2=1.034, test3=0.221, test4=0.388 | |
* Test1: uses String.format() for formatting. | |
* Test2: uses DecimalFormat.format() for formatting, StringBuilder for appending. | |
* Test3: uses custom number formatter, StringBuilder for appending. | |
* Test4: uses no formatting, string concatenation '+' for appending. | |
*/ | |
class DoubleFormatting { | |
public static void main(String[] args) { | |
double start = -10_000.123; | |
double step = 7.12345678; | |
String msg = testAll(start, 1463249.23562, 20, System.out); | |
System.out.println(msg); | |
System.out.println("------"); | |
try (FileOutputStream out = new FileOutputStream("/Users/petedeutsch/test/junk/fmt-times")) { | |
msg = testAll(start, step, 300000, out); | |
System.out.println(msg); | |
} catch (Exception e) { | |
System.out.println("ERROR: " + e.getMessage()); | |
} | |
} | |
static String testAll(double start, double step, int cnt, OutputStream out) { | |
System.out.printf("testAll start: start=%.3f, step=%.3f, cnt=%d\n", start, step, cnt); | |
try (BufferedWriter wrt = new BufferedWriter(new OutputStreamWriter(out))) { | |
Stopwatch stopwatch = Stopwatch.createStarted(); | |
long t0 = stopwatch.elapsed(TimeUnit.MICROSECONDS); | |
wrt.write("test1: "); | |
test1(wrt, cnt, start, step); | |
wrt.write('\n'); | |
long t1 = stopwatch.elapsed(TimeUnit.MICROSECONDS); | |
wrt.write("test2: "); | |
test2(wrt, cnt, start, step); | |
wrt.write('\n'); | |
long t2 = stopwatch.elapsed(TimeUnit.MICROSECONDS); | |
wrt.write("test3: "); | |
test3(wrt, cnt, start, step); | |
wrt.write('\n'); | |
long t3 = stopwatch.elapsed(TimeUnit.MICROSECONDS); | |
wrt.write("test4: "); | |
test4(wrt, cnt, start, step); | |
wrt.write('\n'); | |
long t4 = stopwatch.elapsed(TimeUnit.MICROSECONDS); | |
wrt.flush(); | |
String msg = String.format("testAll called, cnt=%d, test1=%.6f, test2=%.6f, test3=%.6f, test4=%.6f\n", cnt, | |
(t1 - t0) / 1_000_000.0, | |
(t2 - t1) / 1_000_000.0, | |
(t3 - t2) / 1_000_000.0, | |
(t4 - t3) / 1_000_000.0); | |
wrt.write(msg); | |
wrt.write("Test1: uses String.format() for formatting.\n"); | |
wrt.write("Test2: uses DecimalFormat.format() for formatting, StringBuilder for appending.\n"); | |
wrt.write("Test3: uses custom number formatter, StringBuilder for appending.\n"); | |
wrt.write("Test4: uses no formatting, string concatenation '+' for appending.\n"); | |
return msg; | |
} catch (Throwable t) { | |
System.out.println("ERROR: " + t.getMessage()); | |
t.printStackTrace(); | |
return "ERROR: " + t.getMessage(); | |
} | |
} | |
/** | |
* Use String.format() to format and concatenate the doubles and strings. | |
*/ | |
static void test1(BufferedWriter writer, int cnt, double start, double step) throws IOException { | |
double d = start; | |
double d2, d3; | |
for (int i = 0; i < cnt; i++) { | |
d2 = d + 0.1; d3 = d + 0.2; | |
writer.write(String.format("test1: %.3f; test2: %.3f; test3: %.3f\n", d, d2, d3)); | |
d += step; | |
} | |
} | |
final static DecimalFormat fmt = new DecimalFormat("0.000"); | |
/** | |
* Use DecimalFormat to format the double and StringBuilder to concatenate | |
* everything. In theory this should be relatively fast since DecimalFormat | |
* can pre-compile the format string, however it does not appear to be that fast. | |
*/ | |
static void test2(BufferedWriter writer, int cnt, double start, double step) throws IOException { | |
double d = start; | |
StringBuilder bldr = new StringBuilder(200); | |
double d2, d3; | |
for (int i = 0; i < cnt; i++) { | |
d2 = d + 0.1; d3 = d + 0.2; | |
bldr.setLength(0); | |
bldr.append("test1: ").append(fmt.format(d)) | |
.append("; test2: ").append(fmt.format(d2)) | |
.append("; test3: ").append(fmt.format(d3)) | |
.append("\n"); | |
writer.write(bldr.toString()); | |
d += step; | |
} | |
} | |
/** | |
* Test out the test3Format() method with should be one of the faster ways to format | |
* a double. Using StringBuilder to concatenate the strings and formatted doubles. | |
* In testing this is normally the fastest method. | |
*/ | |
static void test3(BufferedWriter writer, int cnt, double start, double step) throws IOException { | |
StringBuilder bldr = new StringBuilder(200); | |
double d = start; | |
double d2, d3; | |
for (int i = 0; i < cnt; i++) { | |
d2 = d + 0.1; d3 = d + 0.2; | |
bldr.setLength(0); | |
bldr.append("test1: ").append(test3Format(d)) | |
.append("; test2: ").append(test3Format(d2)) | |
.append("; test3: ").append(test3Format(d3)) | |
.append("\n"); | |
writer.write(bldr.toString()); | |
d += step; | |
} | |
} | |
/** | |
* This formatter works by using math to find each part of the number. | |
* | |
* If d = 12.01591 | |
* First get convert to long to get the left side of the number. | |
* n = (long)(12.01591 + 0.0005) = (long)(12.01641) = 12 | |
* buffer = "12." | |
* | |
* Then trim off the left side digits (integer part) and multiply by 1000.0 | |
* to just look at the decimal digits. | |
* decimalDigits = (0.01591 * 1000.0) + 0.5 = (long)(15.91 + 0.5) = (long)(16.41) = 16 | |
* | |
* Add 1000 to create leading zeros and convert to string | |
* str = Long.toString(16 + 1000) = "1016" | |
* | |
* Append the last three characters of this string to the buffer and return it. | |
* buffer.append(str.substring(1)) = "12.016" | |
*/ | |
static String test3Format(double d) { | |
// create a buffer to store the intermediate string | |
StringBuffer buffer = new StringBuffer(); | |
// round the double to the 3rd digit and convert to long (to get the left side of the decimal) | |
long n = (long)(d + 0.0005); | |
// add this to the string buffer with the decimal place | |
buffer.append(n).append('.'); | |
// get just the digits to the right of the decimal place | |
double doubleDecimalDigits = Math.abs(d) - Math.abs(n); | |
// shift the decimal over 3 places by multiplying by 1000.0 and round it, convert to long | |
long decimalDigits = (long)(doubleDecimalDigits * 1000.0 + 0.5); | |
// add 1000 to the number so that we can get leading zeros | |
String str = Long.toString(1000 + decimalDigits); | |
// now just add the last three digits to the buffer (ignore the leading '1') | |
buffer.append(str.substring(1)); | |
return buffer.toString(); | |
} | |
/** | |
* No formatting or string building, just raw strings being concatenated with doubles. | |
*/ | |
static void test4(BufferedWriter writer, int cnt, double start, double step) throws IOException { | |
double d = start; | |
double d2, d3; | |
for (int i = 0; i < cnt; i++) { | |
d2 = d + 0.1; d3 = d + 0.2; | |
writer.write("test1: " + d + "; test2: " + d2 + "; test3: " + d3 + "\n"); | |
d += step; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment