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