RFR: 8254807: Optimize startsWith() for String.substring() [v5]

Xin Liu xliu at openjdk.java.net
Thu Dec 17 04:39:57 UTC 2020


On Wed, 16 Dec 2020 03:54:09 GMT, Xin Liu <xliu at openjdk.org> wrote:

>>> _Mailing list message from [Vladimir Ivanov](mailto:vladimir.x.ivanov at oracle.com) on [hotspot-compiler-dev](mailto:hotspot-compiler-dev at openjdk.java.net):_
>>> 
>>> > > - You introduce a call to a method (String::startsWith(String, int))
>>> > > which is not present in bytecode. It means (unless the method is called
>>> > > from different places) there won't be any profiling info present, and
>>> > > even if there is, it is unrelated to the call site being compiled. If
>>> > > the code is recompiled at some point (e.g., it hits an uncommon trap in
>>> > > startsWith(String,int) overload), there won't be any re-profiling
>>> > > happening. So, it can hit the very same condition on the consequent
>>> > > recompilations.
>>> > 
>>> > 
>>> > Thank you to raise this! I didn't consider the profiling data before.
>>> > it is an issue. Maybe it can explain why I observe only 80% performance of hand-craft code.
>>> > TBH, I haven't had a clear and solid answer for it yet.
>>> > The transformation actually won't alter control-flow. Perhaps, I can find a way to amend methoddata from the old method.
>>> > Or, is that possible I request to reprofile it after I modify some bytecodes?
>>> 
>>> Sorry, I overlooked your question.
>>> 
>>> To be clear: the transformations during C2 compilation don't affect
>>> bytecodes in any way.
>>> 
>>> If a method is inlined, a deoptimization may lead to a surprising
>>> resumption of execution in the middle of unexpected method, but it's a
>>> one time occurrence and consequent executions will follow original path
>>> in the bytecode.
>>> 
>> Hi, Vladmir, 
>> 
>> I manage to inline the rewired method in c5a25bf.  I attempt to address your question "Will the new target
>> method be taken into account during inlining?".  From my benchmark, it is 10% faster than the last revision. 
>> 
>> Sorry, I still don't understand your concern about the inlined methods.  This is my first non-bugfix patch.  please forgive me if I look dumb.
>> If startsWith(String, int) is deoptimized, the control will give back to the interpreter. IMHO, I can resume execution back to the interpreter as long as jvms are all correct. I don't think I mess them up.  
>> 
>>> If inlining doesn't happen, then the JVM should be able to finish
>>> successully finish profiling in the callee if needed (reprofile request
>>> invalidates the nmethod and it forces the new call in C2 to go through
>>> either interpreter or C1 until it is compiled with C2).
>>> 
>>> So, I'm concerned specifically about inlined case. And I consider it a
>>> blocking issue for adapting the proposed approach of rewriting rules in
>>> C2. I don't see a way to reliably fix it without intrinsifying the
>>> operation.
>>>
>> I do mark substring and startsWith as intrinsics here. 
>> The reason I didn't mark "substring" pure because all nodes will be eliminated after late inlining if they are dead.
>> yes, if I mark it pure, it will drop the call node immediately in do_late_inline().
>>   
>>> Best regards,
>>> Vladimir Ivanov
>> 
>> Hi, John, 
>> In my defense, this patch is just 300 lines of change if you exclude test.  You can see that I try to reuse the existing codebase to minimize foosteps. As a beginner, I just deliberately make it reviewable-size. I don't think it's non-maintainable or a scramble patch. 
>> 
>> Yes, I admit that the gain is not so attractive so far. it just opens a possibility. A possibility of interprocedural optimization of java APIs.  I can also apply the same recipe from "StringBuilder.append(substring)" to "StringBuilder.append(base, begin, end)" etc. 
>> 
>> The bottom line is I have to learn in action.  All your feedbacks here are helping me to grow. I really appreciate that and I think I can return it in other patches.  I plan to explore in Vladimir kolzov's direction next.  We can rewire the underlying bytebuffer of a string in substring pattern. Of course, it's only safe to do so if EA tells us that the object never escape.
>
> hi, Vladimir, 
> 
> Now I understand what you mean.  vframeArray::unpack_to_stack() resumes vframe which points to the new overloaded method. It doesn't match the original bytecode and the entry of cpcache!  It might require appalling hack to make it work. I guess that's why you guys all say it's not the right direction.

> @navyxliu during your investigation did you look on other applications in addition to `SpecJVM2008/xml.validation`?
> 
> I would like to know what patterns you see. For example, we would not benefit much if an extracted substring is used after startsWith() call. Also I don't think we should optimize Java code which can be replaced with existing String API as in RFE's example.
> 
> Note, in RFE's example you added length check which is not needed because startsWith(prefix, offset) does it:
> https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/String.java#L1455
> 
> And if you see a lot of cases like `return substring(base, beg, end).startsWith(prefix)` or code where substring is used based on startsWith() check:
> 
> ```
>     String sub=substring(base, beg, end);
>     if (sub.startsWith(prefix)) {
>         // Do something with sub
> ```
> 
> we should consider extending String API with `startsWith(prefix, beg, end)`.
> 
> As always fixing Java code gives a lot more performance improvement than JIT compiler can.

Hi, Vladimir, 
Thank you for taking time to review it.  Besides xml.validation, I searched this pattern in our company-scope codebase.  I did find some cases, but it's not common indeed. Even xml.validation is unlikely to benefit from this PR.  To be honest, substring.startsWith is a special case in the middle of special cases.

My original aim is to eliminate object allocation and array copying of substrings if they are never escaped. I learned some unpopular APIs such as `String.startsWith(prefix, toOffset)`,  `StringBuilder.append(substr, toOffset)` and `String.split(sep, limit)` etc. I just feel I can add an API-level transformation to avoid creation of substrings in those case. It seems a cool trick. Given the fact that we need significant efforts to consolidate correctness, now I admit I am steering in wrong direction.  I closed JDK-8254807. 

I will go back to my original direction. I have extent current EA to support substring just like you did for boxing method. I think I can idealize() the Ideal pattern of String.substring().

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

PR: https://git.openjdk.java.net/jdk/pull/974


More information about the hotspot-compiler-dev mailing list