RFR: 8315585: Optimization for decimal to string [v2]
Claes Redestad
redestad at openjdk.org
Thu Oct 12 19:32:17 UTC 2023
On Wed, 11 Oct 2023 22:19:29 GMT, Shaojin Wen <duke at openjdk.org> wrote:
> Keep the duplicate code of StringConcatHelper, or use JLA, or move the code of getCharsLatin1 & stringSize to DecimalDigits (PR #15699). Of the three options, using JLA is the smallest change.
Adding methods to JLA also adds a maintenance burden by exposing methods and internal details that we prefer not to expose.
Explicit `String` concat performs poorly mainly since ISC is disabled for the java.base module. We could, however, explicitly invoke the `StringConcatFactory` and pull out the resulting `MethodHandle`:
diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java
index 3163b2ffadb..b4edbb131d9 100644
--- a/src/java.base/share/classes/java/math/BigDecimal.java
+++ b/src/java.base/share/classes/java/math/BigDecimal.java
@@ -30,6 +30,12 @@
package java.math;
import static java.math.BigInteger.LONG_MASK;
+
+import java.lang.invoke.CallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.StringConcatFactory;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
@@ -4146,6 +4152,23 @@ public BigDecimal ulp() {
return BigDecimal.valueOf(1, this.scale(), 1);
}
+ private static class ConcatHelper {
+ private static final MethodHandle INT_DOT_CHAR_CHAR;
+
+ static {
+ try {
+ CallSite site = StringConcatFactory.makeConcatWithConstants(
+ MethodHandles.lookup(),
+ "scale2",
+ MethodType.methodType(String.class, int.class, char.class, char.class),
+ "\u0001.\u0001\u0001");
+ INT_DOT_CHAR_CHAR = site.dynamicInvoker();
+ } catch (Exception e) {
+ throw new Error("Bootstrap error", e);
+ }
+ }
+ }
+
/**
* Lay out this {@code BigDecimal} into a {@code char[]} array.
* The Java 1.2 equivalent to this was called {@code getValueString}.
@@ -4164,18 +4187,12 @@ private String layoutChars(boolean sci) {
intCompact >= 0 && intCompact < Integer.MAX_VALUE) {
// currency fast path
int highInt = (int)intCompact / 100;
- int highIntSize = JLA.stringSize(highInt);
- byte[] buf = new byte[highIntSize + 3];
- JLA.getCharsLatin1(highInt, highIntSize, buf);
- buf[highIntSize] = '.';
- DecimalDigits.writeDigitPairLatin1(
- buf,
- highIntSize + 1,
- (int) intCompact % 100);
+ int lowInt = (int)intCompact - highInt * 100;
+ char pair = (char)DecimalDigits.digitPair(lowInt);
try {
- return JLA.newStringNoRepl(buf, StandardCharsets.ISO_8859_1);
- } catch (CharacterCodingException e) {
- throw new AssertionError(e);
+ return (String)ConcatHelper.INT_DOT_CHAR_CHAR.invokeExact(highInt, (char)(pair & 0xff), (char)(pair >> 8));
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
}
}
This is equivalent to `return "" + highInt + '.' + (char)(pair & 0xff) + (char)(pair >> 8);` (if `BigDecimal.java` would have been compiled without `-XDstringConcat=inline`) and produces code that is as fast or even slightly faster in my tests:
Name Cnt Base Error Test Error Unit Change
BigDecimals.testSmallToEngineeringString 15 11,703 ± 0,017 10,667 ± 0,058 ns/op 1,10x (p = 0,000*)
:gc.alloc.rate 4530,180 ± 6,688 4970,132 ± 26,973 MB/sec 1,10x (p = 0,000*)
:gc.alloc.rate.norm 55,600 ± 0,000 55,600 ± 0,000 B/op 1,00x (p = 0,000 )
:gc.count 228,000 226,000 counts
:gc.time 131,000 130,000 ms
* = significant
-------------
PR Comment: https://git.openjdk.org/jdk/pull/16006#issuecomment-1760247214
More information about the core-libs-dev
mailing list