On Wed, 11 Oct 2023 22:19:29 GMT, Shaojin Wen <d...@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