Hello Tagir,

I've reworked the benchmark [1] to test also the case when non-latin characters 
are appended (e.g. Russian).

Measurements for both average time and memory allocation per benchmark method 
invocation are done for C2 and C1. [2]

As for other instances of CharSequence in JDK (excluding StringBu***er and 
String) we have those: 

- jdk.internal.jline.extra.EditingHistory$PersistentLine
- jdk.internal.jline.extra.EditingHistory$NarrowingHistoryLine
- jdk.nashorn.internal.runtime.ConsString
- org.graalvm.compiler.phases.ClassTypeSequence
- org.graalvm.compiler.phases.LazyName and MethodDebugValueName
- com.sun.tools.javac.util.Name

All of them delegate the method, affected in my patch, either to String or 
another CharSequence which I'm sure is an instance of java.lang.String in 99 
cases out of a hundred.

As for other questions of yours:

- indeed, there can be a case when a single char of a long String is appended 
to SB using SB::append(CharSequence, int, int).
Proposed patch will create a new String consisted of that character, so no 
memory would leak.
In this case I agree that performance effect of the patch would be negligible, 
I guess this is what you wanted to point out.
This case however seems to be less probable than the opposite case when we have 
more than 1 character.

- there also can be a case when many String instances are appended to the same 
SB.
Proposed patch won't affect other append calls, just SB::append(CharSequence, 
int, int).

I think this is useful optimisation as it provides better performance without 
changing lots of code and seems to be not breaking anything.

P. S. I've tried to modify the patch to make it call SB::append(String), see 
[3]. It's results seems to be even more interesting:

First let's look into C2 [4]. Here patched version again loses, but not 
dramatically.
But on C1 [5] patched version wins at least in avgt mode! The reason for memory 
loss is missing scalar replacement of allocated sub-string.

And again on C2 intrinsification doesn't work in patced version event when it 
calls SB::append(String) from StringBuilder itself.

Appendix

1. Benchmark

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(jvmArgsAppend = {"-Xms2g", "-Xmx2g"})
public class StringBuilderAppendBenchmark {

  @Benchmark
  @SuppressWarnings("StringBufferReplaceableByString")
  public String appendSubString(Data data) {
    String englishStr = data.latinStr;
    String nonLatinStr = data.nonLatinStr;
    int beginIndex = data.beginIndex;
    int endIndex = data.endIndex;

    String substring = data.appendNonLatin ?
            nonLatinStr.substring(beginIndex, endIndex) :
            englishStr.substring(beginIndex, endIndex);

    return new 
StringBuilder().append('L').append(substring).append(';').toString();
  }

  @Benchmark
  public String appendBounds(Data data) {
    String englishStr = data.latinStr;
    String nonLatinStr = data.nonLatinStr;
    int beginIndex = data.beginIndex;
    int endIndex = data.endIndex;

    String appended = data.appendNonLatin ? nonLatinStr : englishStr;

    return new StringBuilder().append('L').append(appended, beginIndex, 
endIndex).append(';').toString();
  }

  @State(Scope.Thread)
  public static class Data {
    String latinStr;
    String nonLatinStr;

    @Param({"true", "false"})
    boolean appendNonLatin;

    @Param({"10", "100", "1000"})
    private int length;

    private int beginIndex;
    private int endIndex;

    private ThreadLocalRandom random = ThreadLocalRandom.current();

    @Setup
    public void setup() {
      latinStr = randomString("abcdefghijklmnopqrstuvwxyz");
      nonLatinStr = randomString("абвгдеёжзиклмнопрстуфхцчшщыьъэюя");
      beginIndex = length / 4;
      endIndex = length / 4 * 3;
    }

    private String randomString(String alphabet) {
      char[] chars = alphabet.toCharArray();

      StringBuilder sb = new StringBuilder(length);
      for (int i = 0; i < length; i++) {
        char c = chars[random.nextInt(chars.length)];
        sb.append(c);
      }

      return sb.toString();
    }
  }

}

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2. Results for StringBuilder:

JDK 11.0.1, OpenJDK 64-Bit Server VM, 11.0.1+13-Ubuntu-3ubuntu116.04ppa1

Benchmark                                                          
(appendNonLatin)  (length)  Mode  Cnt     Score     Error   Units
StringBuilderAppendBenchmark.appendBounds                                      
true        10  avgt  100    39,092 ±   0,558   ns/op
StringBuilderAppendBenchmark.appendBounds                                      
true       100  avgt  100    96,113 ±   0,739   ns/op
StringBuilderAppendBenchmark.appendBounds                                      
true      1000  avgt  100   653,716 ±  82,838   ns/op

StringBuilderAppendBenchmark.appendSubString                                   
true        10  avgt  100    27,052 ±   0,513   ns/op
StringBuilderAppendBenchmark.appendSubString                                   
true       100  avgt  100    46,154 ±   6,511   ns/op
StringBuilderAppendBenchmark.appendSubString                                   
true      1000  avgt  100   226,889 ±   5,857   ns/op

StringBuilderAppendBenchmark.appendBounds                                     
false        10  avgt  100    26,044 ±   0,291   ns/op
StringBuilderAppendBenchmark.appendBounds                                     
false       100  avgt  100    64,630 ±   0,565   ns/op
StringBuilderAppendBenchmark.appendBounds                                     
false      1000  avgt  100   314,778 ±  11,625   ns/op

StringBuilderAppendBenchmark.appendSubString                                  
false        10  avgt  100    22,899 ±   0,365   ns/op
StringBuilderAppendBenchmark.appendSubString                                  
false       100  avgt  100    25,075 ±   0,166   ns/op
StringBuilderAppendBenchmark.appendSubString                                  
false      1000  avgt  100    88,880 ±   0,258   ns/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                  
true        10  avgt  100   184,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                  
true       100  avgt  100   688,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                  
true      1000  avgt  100  5192,001 ±   0,001    B/op

StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm               
true        10  avgt  100   128,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm               
true       100  avgt  100   360,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm               
true      1000  avgt  100  2608,000 ±   0,001    B/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                 
false        10  avgt  100   104,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                 
false       100  avgt  100   344,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                 
false      1000  avgt  100  2144,000 ±   0,001    B/op

StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm              
false        10  avgt  100    96,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm              
false       100  avgt  100   192,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm              
false      1000  avgt  100  1088,000 ±   0,001    B/op


-XX:TieredStopAtLevel=1

Benchmark                                                          
(appendNonLatin)  (length)  Mode  Cnt     Score     Error   Units
StringBuilderAppendBenchmark.appendBounds                                      
true        10  avgt  100    99,823 ±   1,956   ns/op
StringBuilderAppendBenchmark.appendBounds                                      
true       100  avgt  100   395,615 ±   4,098   ns/op
StringBuilderAppendBenchmark.appendBounds                                      
true      1000  avgt  100  3198,072 ±  39,497   ns/op

StringBuilderAppendBenchmark.appendSubString                                   
true        10  avgt  100    94,530 ±   4,256   ns/op
StringBuilderAppendBenchmark.appendSubString                                   
true       100  avgt  100   148,147 ±   0,853   ns/op
StringBuilderAppendBenchmark.appendSubString                                   
true      1000  avgt  100   640,169 ±   1,821   ns/op

StringBuilderAppendBenchmark.appendBounds                                     
false        10  avgt  100    79,023 ±  14,429   ns/op
StringBuilderAppendBenchmark.appendBounds                                     
false       100  avgt  100   298,557 ±   9,347   ns/op
StringBuilderAppendBenchmark.appendBounds                                     
false      1000  avgt  100  2433,623 ±  32,966   ns/op

StringBuilderAppendBenchmark.appendSubString                                  
false        10  avgt  100    54,906 ±   0,112   ns/op
StringBuilderAppendBenchmark.appendSubString                                  
false       100  avgt  100    86,471 ±   0,453   ns/op
StringBuilderAppendBenchmark.appendSubString                                  
false      1000  avgt  100   251,550 ±   0,998   ns/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                  
true        10  avgt  100   184,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                  
true       100  avgt  100   688,001 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                  
true      1000  avgt  100  5192,005 ±   0,003    B/op

StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm               
true        10  avgt  100   256,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm               
true       100  avgt  100   904,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm               
true      1000  avgt  100  6752,001 ±   0,001    B/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                 
false        10  avgt  100   104,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                 
false       100  avgt  100   344,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                 
false      1000  avgt  100  2144,004 ±   0,003    B/op

StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm              
false        10  avgt  100   152,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm              
false       100  avgt  100   440,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendSubString:·gc.alloc.rate.norm              
false      1000  avgt  100  2688,000 ±   0,001    B/op

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

3. New patch

diff --git a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java 
b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java
--- a/src/java.base/share/classes/java/lang/AbstractStringBuilder.java
+++ b/src/java.base/share/classes/java/lang/AbstractStringBuilder.java
@@ -626,10 +626,7 @@
             s = "null";
         }
         checkRange(start, end, s.length());
-        int len = end - start;
-        ensureCapacityInternal(count + len);
-        appendChars(s, start, end);
-        return this;
+        return append(s.subSequence(start, end).toString());
     }
 
     /**
@@ -1592,7 +1589,7 @@
      * @param dstBegin  the char index, not offset of byte[]
      * @param coder     the coder of dst[]
      */
-    void getBytes(byte dst[], int dstBegin, byte coder) {
+    void getBytes(byte[] dst, int dstBegin, byte coder) {
         if (this.coder == coder) {
             System.arraycopy(value, 0, dst, dstBegin << coder, count << coder);
         } else {        // this.coder == LATIN && coder == UTF16
@@ -1686,29 +1683,8 @@
         this.count = count + end - off;
     }
 
-    private final void appendChars(CharSequence s, int off, int end) {
-        if (isLatin1()) {
-            byte[] val = this.value;
-            for (int i = off, j = count; i < end; i++) {
-                char c = s.charAt(i);
-                if (StringLatin1.canEncode(c)) {
-                    val[j++] = (byte)c;
-                } else {
-                    count = j;
-                    inflate();
-                    StringUTF16.putCharsSB(this.value, j, s, i, end);
-                    count += end - i;
-                    return;
-                }
-            }
-        } else {
-            StringUTF16.putCharsSB(this.value, count, s, off, end);
-        }
-        count += end - off;
-    }
-
     /* IndexOutOfBoundsException, if out of bounds */
-    private static void checkRange(int start, int end, int len) {
+    protected static void checkRange(int start, int end, int len) {
         if (start < 0 || start > end || end > len) {
             throw new IndexOutOfBoundsException(
                 "start " + start + ", end " + end + ", length " + len);
diff --git a/src/java.base/share/classes/java/lang/StringBuilder.java 
b/src/java.base/share/classes/java/lang/StringBuilder.java
--- a/src/java.base/share/classes/java/lang/StringBuilder.java
+++ b/src/java.base/share/classes/java/lang/StringBuilder.java
@@ -210,8 +210,11 @@
      */
     @Override
     public StringBuilder append(CharSequence s, int start, int end) {
-        super.append(s, start, end);
-        return this;
+        if (s == null) {
+            s = "null";
+        }
+        checkRange(start, end, s.length());
+        return append(s.subSequence(start, end).toString());
     }
 
     @Override

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4. Comparison of original and patched versions for C2

Original JDK 11.0.1, OpenJDK 64-Bit Server VM, 
11.0.1+13-Ubuntu-3ubuntu116.04ppa1

Benchmark                                                               
(appendNonLatin)  (length)  Mode  Cnt     Score     Error   Units
StringBuilderAppendBenchmark.appendBounds                                       
    true        10  avgt   50    48,047 ±   1,123   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true       100  avgt   50   147,212 ±   7,504   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true      1000  avgt   50  1042,751 ±  68,417   ns/op

StringBuilderAppendBenchmark.appendBounds                                       
   false        10  avgt   50    22,323 ±   0,513   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false       100  avgt   50    74,883 ±   3,260   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false      1000  avgt   50   466,711 ±  25,859   ns/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true        10  avgt   50   184,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true       100  avgt   50   688,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true      1000  avgt   50  5192,002 ±   0,002    B/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false        10  avgt   50    80,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false       100  avgt   50   320,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false      1000  avgt   50  2144,001 ±   0,001    B/op


Patched JDK 11-internal, OpenJDK 64-Bit Server VM, 
11-internal+0-adhoc.sergei.jdk11

Benchmark                                                               
(appendNonLatin)  (length)  Mode  Cnt     Score     Error   Units
StringBuilderAppendBenchmark.appendBounds                                       
    true        10  avgt   50    62,194 ±   1,955   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true       100  avgt   50   172,281 ±   1,865   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true      1000  avgt   50  1132,282 ±   8,697   ns/op

StringBuilderAppendBenchmark.appendBounds                                       
   false        10  avgt   50    38,363 ±   0,815   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false       100  avgt   50    85,648 ±   1,087   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false      1000  avgt   50   505,787 ±   6,477   ns/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true        10  avgt   50   256,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true       100  avgt   50   904,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true      1000  avgt   50  6752,002 ±   0,002    B/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false        10  avgt   50   152,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false       100  avgt   50   440,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false      1000  avgt   50  2688,001 ±   0,001    B/op

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

5. Comparison of original and patched versions for C1

Original

Benchmark                                                               
(appendNonLatin)  (length)  Mode  Cnt     Score     Error   Units
StringBuilderAppendBenchmark.appendBounds                                       
    true        10  avgt   50   117,387 ±   1,886   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true       100  avgt   50   506,864 ±  10,523   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true      1000  avgt   50  4172,215 ±  62,144   ns/op

StringBuilderAppendBenchmark.appendBounds                                       
   false        10  avgt   50    65,084 ±   1,547   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false       100  avgt   50   350,656 ±   5,061   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false      1000  avgt   50  2944,915 ±  43,500   ns/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true        10  avgt   50   184,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true       100  avgt   50   688,001 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true      1000  avgt   50  5192,006 ±   0,007    B/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false        10  avgt   50   104,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false       100  avgt   50   344,001 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false      1000  avgt   50  2144,004 ±   0,005    B/op

Patched

Benchmark                                                               
(appendNonLatin)  (length)  Mode  Cnt     Score     Error   Units
StringBuilderAppendBenchmark.appendBounds                                       
    true        10  avgt   50   109,806 ±   1,013   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true       100  avgt   50   212,775 ±   8,061   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
    true      1000  avgt   50  1308,307 ±  17,675   ns/op

StringBuilderAppendBenchmark.appendBounds                                       
   false        10  avgt   50    69,315 ±   1,788   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false       100  avgt   50   122,495 ±   5,311   ns/op
StringBuilderAppendBenchmark.appendBounds                                       
   false      1000  avgt   50   665,867 ±  31,839   ns/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true        10  avgt   50   256,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true       100  avgt   50   904,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
    true      1000  avgt   50  6752,002 ±   0,002    B/op

StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false        10  avgt   50   152,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false       100  avgt   50   440,000 ±   0,001    B/op
StringBuilderAppendBenchmark.appendBounds:·gc.alloc.rate.norm                   
   false      1000  avgt   50  2688,001 ±   0,001    B/op

Reply via email to