Skip to content

Instantly share code, notes, and snippets.

@pdeutsch
Created March 19, 2019 18:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pdeutsch/2547e0c83895bfdbbadb1f09f5c93bbe to your computer and use it in GitHub Desktop.
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.
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