Issue | Fix Version | Assignee | Priority | Status | Resolution | Resolved In Build |
---|---|---|---|---|---|---|
JDK-8084374 | emb-9 | Brent Christian | P4 | Resolved | Fixed | team |
As Martin Buchholz pointed out way back in JDK-6898220, java.util.Formatter is allocation intense and can be further optimized. Here are a few pretty straightforward things:
- avoid converting data back and forth between String, StringBuilder and char[] (especially using String.toCharArray()), instead try to operate on StringBuilders as much as possible. I consider getting rid of System.arraycopy a nice cleanup, too.
- regex: avoid using m.group on single-char groups, instead use m.start(idx)/m.end(idx) and charAt().
- parse: return the ArrayList<FormatSpecifier> directly instead of converting it to FormatSpecifier[]; since the recipient simply iterates over the specifiers once, converting to array is wasteful
- FixedString: constant string expressions were substrings, which since 7u6 copies the data. By instead saving the offset and rely on range-based append methods of StringBuilder a small speedup is achieved.
The code can be further optimized usingJDK-8041972 (parsing index, width, precision without intermediate substrings gains a few percent for complex cases) and perhaps JDK-8050114, but I will file separate RFEs for those.
Internal microbenchmarks show speedups ranging from 10-30%; some examples (Intel(R) Core(TM) i5-3320M CPU @ 2.60GHz, Linux 3.13):
Benchmark Mode Samples Score Score error Units
old.FormatBench.formatDecimal thrpt 20 2120314.923 48250.799 ops/s
new.FormatBench.formatDecimal thrpt 20 2739464.530 70673.303 ops/s # 1.29x
old.FormatBench.formatHex thrpt 20 1741011.847 28234.553 ops/s
new.FormatBench.formatHex thrpt 20 2172309.104 34826.348 ops/s # 1.25x
old.FormatBench.formatDouble thrpt 20 1199099.261 40828.750 ops/s
new.FormatBench.formatDouble thrpt 20 1334265.418 20479.704 ops/s # 1.11x
old.FormatBench.formatString thrpt 20 2437233.293 45434.999 ops/s
new.FormatBench.formatString thrpt 20 3183689.809 52892.969 ops/s # 1.30x
old.FormatBench.formatComplex thrpt 20 489896.437 9006.345 ops/s
new.FormatBench.formatComplex thrpt 20 578696.625 8039.759 ops/s # 1.18x
old.FormatBench.formatScience thrpt 20 938680.776 11932.883 ops/s
new.FormatBench.formatScience thrpt 20 1119664.074 14700.473 ops/s # 1.19x
package org.sample;
import org.openjdk.jmh.annotations.*;
import java.math.BigInteger;
@State(Scope.Thread)
public class FormatBench {
public int value = 4711;
public int width = 6;
public float pi = (float)Math.PI;
public String str = "foo";
@Benchmark
public String formatDecimal() {
return String.format("%d", value);
}
@Benchmark
public String formatHex() {
return String.format("%06x", value);
}
@Benchmark
public String formatDouble() {
return String.format("%.4f", pi);
}
@Benchmark
public String formatScience() {
return String.format("%5.5e", pi);
}
@Benchmark
public String formatComplex() {
return String.format(" %04x %05x %06x %05x", value, value, value, value);
}
@Benchmark
public String formatString() {
return String.format("%s", str);
}
- avoid converting data back and forth between String, StringBuilder and char[] (especially using String.toCharArray()), instead try to operate on StringBuilders as much as possible. I consider getting rid of System.arraycopy a nice cleanup, too.
- regex: avoid using m.group on single-char groups, instead use m.start(idx)/m.end(idx) and charAt().
- parse: return the ArrayList<FormatSpecifier> directly instead of converting it to FormatSpecifier[]; since the recipient simply iterates over the specifiers once, converting to array is wasteful
- FixedString: constant string expressions were substrings, which since 7u6 copies the data. By instead saving the offset and rely on range-based append methods of StringBuilder a small speedup is achieved.
The code can be further optimized using
Internal microbenchmarks show speedups ranging from 10-30%; some examples (Intel(R) Core(TM) i5-3320M CPU @ 2.60GHz, Linux 3.13):
Benchmark Mode Samples Score Score error Units
old.FormatBench.formatDecimal thrpt 20 2120314.923 48250.799 ops/s
new.FormatBench.formatDecimal thrpt 20 2739464.530 70673.303 ops/s # 1.29x
old.FormatBench.formatHex thrpt 20 1741011.847 28234.553 ops/s
new.FormatBench.formatHex thrpt 20 2172309.104 34826.348 ops/s # 1.25x
old.FormatBench.formatDouble thrpt 20 1199099.261 40828.750 ops/s
new.FormatBench.formatDouble thrpt 20 1334265.418 20479.704 ops/s # 1.11x
old.FormatBench.formatString thrpt 20 2437233.293 45434.999 ops/s
new.FormatBench.formatString thrpt 20 3183689.809 52892.969 ops/s # 1.30x
old.FormatBench.formatComplex thrpt 20 489896.437 9006.345 ops/s
new.FormatBench.formatComplex thrpt 20 578696.625 8039.759 ops/s # 1.18x
old.FormatBench.formatScience thrpt 20 938680.776 11932.883 ops/s
new.FormatBench.formatScience thrpt 20 1119664.074 14700.473 ops/s # 1.19x
package org.sample;
import org.openjdk.jmh.annotations.*;
import java.math.BigInteger;
@State(Scope.Thread)
public class FormatBench {
public int value = 4711;
public int width = 6;
public float pi = (float)Math.PI;
public String str = "foo";
@Benchmark
public String formatDecimal() {
return String.format("%d", value);
}
@Benchmark
public String formatHex() {
return String.format("%06x", value);
}
@Benchmark
public String formatDouble() {
return String.format("%.4f", pi);
}
@Benchmark
public String formatScience() {
return String.format("%5.5e", pi);
}
@Benchmark
public String formatComplex() {
return String.format(" %04x %05x %06x %05x", value, value, value, value);
}
@Benchmark
public String formatString() {
return String.format("%s", str);
}
- backported by
-
JDK-8084374 Optimize java.util.Formatter
- Resolved
- relates to
-
JDK-6898220 (fmt) Optimize Formatter.parse (including String.printf)
- Resolved