On Wed, 11 Oct 2023 22:19:29 GMT, Shaojin Wen <[email protected]> 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