String.indexOf optimization (original) (raw)

Ivan Gerasimov ivan.gerasimov at oracle.com
Tue Apr 15 17:13:28 UTC 2014


Hi everyone!

On 04.04.2014 21:13, Martin Buchholz wrote:

Summary:

Many people (myself included) have looked at this problem. It's unlikely that String.indexOf will change. It's hard to beat the naive implementation in the typical case. But we can try to speed up this naive implementation a little bit.

We can separate the special case: When the substring's length == 1. This special case can be done fast, and in the general case we can now assume substring's length is at least 2.

Here's the webrev with the implementation of this idea: http://cr.openjdk.java.net/~igerasim/indexof/0/webrev/

I've done some benchmarking with JMH and found no performance degradation on my machine. Of course, more testcases should be created and they should be tried on different machines to be treated as reliable.

Benchmark Mode Thr Cnt Sec Mean
Mean error Units o.b.IndexOfBench.benchIndexOf_1_A avgt 1 20 5
704.739 9.104 nsec/op o.b.IndexOfBench.benchIndexOf_1_B avgt 1 20 5 573.879
9.820 nsec/op o.b.IndexOfBench.benchIndexOf_2_A avgt 1 20 5
668.455 9.882 nsec/op o.b.IndexOfBench.benchIndexOf_2_B avgt 1 20 5 476.062
6.063 nsec/op o.b.IndexOfBench.benchIndexOf_3_A avgt 1 20 5
155.227 1.796 nsec/op o.b.IndexOfBench.benchIndexOf_3_B avgt 1 20 5 *152.850 *
1.512 nsec/op o.b.IndexOfBench.benchIndexOf_4_A avgt 1 20 5
656.183 5.904 nsec/op o.b.IndexOfBench.benchIndexOf_4_B avgt 1 20 5 515.178
9.107 nsec/op o.b.IndexOfBench.benchIndexOf_5_A avgt 1 20 5
140.609 7.305 nsec/op o.b.IndexOfBench.benchIndexOf_5_B avgt 1 20 5 129.603
1.654 nsec/op o.b.IndexOfBench.benchIndexOf_6_A avgt 1 20 5
127.713 1.497 nsec/op o.b.IndexOfBench.benchIndexOf_6_B avgt 1 20 5 122.177
1.186 nsec/op o.b.IndexOfBench.benchIndexOf_7_A avgt 1 20 5
430.148 4.875 nsec/op o.b.IndexOfBench.benchIndexOf_7_B avgt 1 20 5 387.338
10.904 nsec/op o.b.IndexOfBench.benchIndexOf_8_A avgt 1 20 5
2064.563 28.885 nsec/op o.b.IndexOfBench.benchIndexOf_8_B avgt 1 20 5 1858.669
24.343 nsec/op

Benchmarks ending with A use the current indexOf implementation, with B use the modified version. These numbers show from 1.4% up to 28% performance increase.

The full listing is below.

Suggestions about what else to test are welcome!

Sincerely yours, Ivan


/**

import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.logic.BlackHole; import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Benchmark) public class IndexOfBench {

// ||| final static char[] source1 = "abababcabcacabcabcabcabcaccbcabcacbcabcacbcbcabcbcbacbcabcbabacbcbacbcabcabcabcabcabcabcabcacbacbacbacabcabcacbacbcabcbcbcaabdacbacabcabacbacabca".toCharArray(); final static char[] source2 = "acfacafacfacfacfacafcafcacadcacdacaccacacdacacfcafcafcfacdacadcadcadcdacfacfacdacadcacdcfacfacdacdacdcfacdacdacdacgshgshasdabdahghjgwdshacavcavsc".toCharArray(); final static char[] source3 = "tyrtytfytytuytfytuytggfghgdytyftytfdytdshfgjhdfytsfuythgsfhgjhfghtuysdfthgfsdhgystfjhgsfguysthgfgjhgdfjhgsjdghfythgsdfjhggfabduikjhfjhkjhfkjhgkjh".toCharArray(); final static char[] target1 = "abd".toCharArray();

 final static char[] source4 = 

"ahhhahahahahhahahahahhahahahhhahahahahahahahahahahhahahahahahahahahallallalalalalalkakakakakakakakakkakmamamamabammamamamamamaakaklalalaoalalalao".toCharArray(); final static char[] source5 = "hgjkhjhjkdghkjhdfkjhgkjhdkjdhgkjdfhgkjdhfgkjdfhgkjhdfkjghkdjghkdjfhgkjhkdjhgkjdfhjkghkdjfhgkjdfhgkjdfhgkjdfhkgabhfkjghdkfjhgkjdfhgkjdfhgkjdfhgkhh".toCharArray(); final static char[] target2 = "ab".toCharArray();

 final static char[] source6 = 

"lskgjsklfjgskldfjgklsfjdlgkjsdflkgjskldfgjsdklfgjsl;kdfgjskldfjglksdfjglksfjglksdfjgklsfdjgslkdfjglksjdflkgsjfalksjdflkfgjsdklfjglskdfjglksdfjghh".toCharArray(); final static char[] target3 = "a".toCharArray();

 final static char[] source7 = 

"lskgajabfagskldfjgklsabclgkjsdflkabsabcdgjsdklfabclbkdfgjskabfjglksdfjabcdfjglabcfjgklsfabgslkdfjglksjdabcdsjfabcdedflabcjsdklfjglskdfabcksdfjghh".toCharArray(); final static char[] target4 = "abcde".toCharArray();

 final static char[] source8 = 

"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".toCharArray(); final static char[] target5 = "aaaab".toCharArray();

 @GenerateMicroBenchmark
 public void benchIndexOf_1_A(BlackHole bh) {
     bh.consume(indexOfA(source1, 0, source1.length, target1, 0, 

target1.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_1_B(BlackHole bh) {
     bh.consume(indexOfB(source1, 0, source1.length, target1, 0, 

target1.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_2_A(BlackHole bh) {
     bh.consume(indexOfA(source2, 0, source2.length, target1, 0, 

target1.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_2_B(BlackHole bh) {
     bh.consume(indexOfB(source2, 0, source2.length, target1, 0, 

target1.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_3_A(BlackHole bh) {
     bh.consume(indexOfA(source3, 0, source3.length, target1, 0, 

target1.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_3_B(BlackHole bh) {
     bh.consume(indexOfB(source3, 0, source3.length, target1, 0, 

target1.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_4_A(BlackHole bh) {
     bh.consume(indexOfA(source4, 0, source4.length, target2, 0, 

target2.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_4_B(BlackHole bh) {
     bh.consume(indexOfB(source4, 0, source4.length, target2, 0, 

target2.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_5_A(BlackHole bh) {
     bh.consume(indexOfA(source5, 0, source5.length, target2, 0, 

target2.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_5_B(BlackHole bh) {
     bh.consume(indexOfB(source5, 0, source5.length, target2, 0, 

target2.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_6_A(BlackHole bh) {
     bh.consume(indexOfA(source6, 0, source6.length, target3, 0, 

target3.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_6_B(BlackHole bh) {
     bh.consume(indexOfB(source6, 0, source6.length, target3, 0, 

target3.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_7_A(BlackHole bh) {
     bh.consume(indexOfA(source7, 0, source7.length, target4, 0, 

target4.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_7_B(BlackHole bh) {
     bh.consume(indexOfB(source7, 0, source7.length, target4, 0, 

target4.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_8_A(BlackHole bh) {
     bh.consume(indexOfA(source8, 0, source8.length, target5, 0, 

target5.length, 0)); }

 @GenerateMicroBenchmark
 public void benchIndexOf_8_B(BlackHole bh) {
     bh.consume(indexOfB(source8, 0, source8.length, target5, 0, 

target5.length, 0)); }

 // implementations

 static int indexOfA(char[] source, int sourceOffset, int sourceCount,
         char[] target, int targetOffset, int targetCount,
         int fromIndex) {
     if (fromIndex >= sourceCount) {
         return (targetCount == 0 ? sourceCount : -1);
     }
     if (fromIndex < 0) {
         fromIndex = 0;
     }
     if (targetCount == 0) {
         return fromIndex;
     }

     char first = target[targetOffset];
     int max = sourceOffset + (sourceCount - targetCount);

     for (int i = sourceOffset + fromIndex; i <= max; i++) {
         /* Look for first character. */
         if (source[i] != first) {
             while (++i <= max && source[i] != first);
         }

         /* Found first character, now look at the rest of v2 */
         if (i <= max) {
             int j = i + 1;
             int end = j + targetCount - 1;
             for (int k = targetOffset + 1; j < end && source[j]
                     == target[k]; j++, k++);

             if (j == end) {
                 /* Found whole string. */
                 return i - sourceOffset;
             }
         }
     }
     return -1;
 }

 static int indexOfB(char[] source, int sourceOffset, int sourceCount,
         char[] target, int targetOffset, int targetCount,
         int fromIndex) {
     if (fromIndex >= sourceCount) {
         return (targetCount == 0 ? sourceCount : -1);
     }
     if (fromIndex < 0) {
         fromIndex = 0;
     }
     if (targetCount == 0) {
         return fromIndex;
     }
     char first = target[targetOffset];
     char second = 0;
     if (targetCount != 1) {
         second = target[targetOffset + 1];
     }
     int max = sourceOffset + (sourceCount - targetCount);
     for (int i = sourceOffset + fromIndex; i <= max; i++) {
         /* Look for first character. */
         if (source[i] != first) {
             while (++i <= max && source[i] != first);
         }
         if (targetCount == 1) {
             return (i <= max) ? i - sourceOffset : -1;
         }
         /* Found first character, now look at the rest of v2 */
         if (i <= max && source[i + 1] == second) {
             int j = i + 2;
             int end = i + targetCount;
             for (int k = targetOffset + 2; j < end && source[j]
                     == target[k]; j++, k++);

             if (j == end) {
                 /* Found whole string. */
                 return i - sourceOffset;
             }
         }
     }
     return -1;
 }

}



More information about the core-libs-dev mailing list