Thanks for the article, Stamatis. I learned a lot. 

I believe that upgrading to JavaCC 7.0.5 (or higher) will solve the first 
problem in Aleksey‘s article (stack trace construction) but not the second 
(stack trace unwinding).

JavaCC uses exceptions extensively in its lookahead logic. (There hundreds of 
‘catch (LookaheadSuccess)’ blocks in our code, and I would guess that we invoke 
lookahead once per line in a typical query, but the number of frames to be 
unwound each lookahead is very small.) I have to hope that the JavaCC team have 
evaluated the performance implications. 

Julian

PS By coincidence, Aleksey is a colleague at Google, working on BigQuery 
performance. 

> On Jul 11, 2024, at 05:48, Stamatis Zampetakis <zabe...@gmail.com> wrote:
> 
> This reminds me of a very nice benchmark done by Aleksey Shipilёv
> around the performance implications of Exception [1]. Among other
> things the benchmark analyses the impact of stack depth in the
> performance of exceptions and is very similar to the parser benchmark
> contributed by Cyril.
> 
> The closing meme says everything: "If you didn't want exceptions to
> ruin your performance you shouldn't have used them for the regular
> control flow"
> 
> Best,
> Stamatis
> 
> [1] https://shipilev.net/blog/2014/exceptional-performance/
> 
>> On Thu, Jul 11, 2024 at 10:12 AM Cyril DE CATHEU
>> <cy...@startree.ai.invalid> wrote:
>> 
>> https://github.com/apache/calcite/pull/3853
>> 
>>> On Wed, Jul 10, 2024 at 5:29 PM Julian Hyde <jhyde.apa...@gmail.com> wrote:
>>> 
>>> PS Your method of increasing stack size by making recursive calls seems
>>> valid. Your experiments show an improvement before and after the fix.
>>> That’s good enough for me.
>>> 
>>> I plan to use your benchmark to check that upgrading JavaCC doesn’t
>>> introduce other performance problems.
>>> 
>>> Julian
>>> 
>>>> On Jul 10, 2024, at 08:18, Julian Hyde <jhyde.apa...@gmail.com> wrote:
>>>> 
>>>> A PR with the benchmark would be great. If it is based on jmh I will
>>> merge it to main, so others can run the benchmark in future, and maybe do
>>> more performance optimizations on the parser.
>>>> 
>>>> By the way, it can be on whichever parser variant (core, babel, server)
>>> is most convenient. I’m pretty sure they have the same performance issues.
>>>> 
>>>> I’m continuing to work on upgrading JavaCC to 7.0.13, and should have a
>>> PR ready soon.
>>>> 
>>>> Julian
>>>> 
>>>>> On Jul 10, 2024, at 03:54, Cyril DE CATHEU <cy...@startree.ai.invalid>
>>> wrote:
>>>>> 
>>>>> Hey Mihai and Julian,
>>>>> 
>>>>> Thanks for looking at this.
>>>>> The performance hit depends on the size of the stack.
>>>>> Here are the numbers I have for the moment: (*not run in a good
>>> benchmark
>>>>> setup* but gives an idea)
>>>>> 
>>>>> Benchmark                                                          Mode
>>>>> Cnt   Score   Error  Units
>>>>> BabelParserBenchmark.instantiateBabelParser                        avgt
>>>>> 7  14.128 ± 0.041  us/op
>>>>> BabelParserBenchmark.instantiateBabelParserErrorFixed              avgt
>>>>> 7  12.054 ± 0.039  us/op
>>>>> 
>>>>> BabelParserBenchmark.instantiateBabelParserBigCallStack            avgt
>>>>> 7  20.576 ± 0.069  us/op
>>>>> BabelParserBenchmark.instantiateBabelParserBigCallStackErrorFixed  avgt
>>>>> 7  12.479 ± 0.068  us/op
>>>>> 
>>>>> For the smallest error stack possible with JMH, the current version is
>>> ~15%
>>>>> slower than with the fix.
>>>>> With a bigger stack - instantiation that happens on depth ~100 - the
>>>>> current version gets even slower. With the fix the time is stable.
>>>>> 
>>>>> Note I'm not sure my method to make the stack bigger is realistic. I
>>> just
>>>>> do some recursive calls.
>>>>> Also to fix the issue I just copied the SqlBabelParserImpl and fixed the
>>>>> stack trace call, so the results above may not match exactly the change
>>>>> that will be observed by upgrading to 7.0.13.
>>>>> 
>>>>> I can create a PR that adds the benchmark (without the hotfix
>>> *ErrorFixed
>>>>> one) if it's okay for you.
>>>>> 
>>>>>>> On Tue, Jul 9, 2024 at 4:07 AM Julian Hyde <jh...@apache.org> wrote:
>>>>>> 
>>>>>> I've started work on
>>>>>> https://issues.apache.org/jira/browse/CALCITE-5541, to upgrade to
>>>>>> 7.0.13. There are still a few errors, but the inefficiency in the
>>>>>> subclass of Error seems to have been solved.
>>>>>> 
>>>>>> Can someone work on the microbenchmark? I would like to see numbers
>>>>>> before and after this fix.
>>>>>> 
>>>>>> Julian
>>>>>> 
>>>>>> 
>>>>>>> On Mon, Jul 8, 2024 at 1:51 PM Mihai Budiu <mbu...@gmail.com> wrote:
>>>>>>> 
>>>>>>> There is an issue to upgrade JavaCC:
>>>>>> https://issues.apache.org/jira/browse/CALCITE-5541
>>>>>>> 
>>>>>>> It may be a worthwhile effort to do it.
>>>>>>> 
>>>>>>> Mihai
>>>>>>> ________________________________
>>>>>>> From: Cyril DE CATHEU <cy...@startree.ai.INVALID>
>>>>>>> Sent: Friday, July 5, 2024 8:55 AM
>>>>>>> To: dev@calcite.apache.org <dev@calcite.apache.org>
>>>>>>> Subject: Re: SqlBabelParserImpl instantiation is slow because it
>>>>>> instantiates a java.lang.Error
>>>>>>> 
>>>>>>> Me again,
>>>>>>> 
>>>>>>> So this LookaheadSuccess comes from JavaCC.
>>>>>>> It seems this issue was solved in JavaCC some time ago, from JavaCC
>>>>>> 7.0.5:
>>>>>>> https://github.com/javacc/javacc/pull/99
>>>>>>> 
>>>>>> 
>>> https://github.com/javacc/javacc/blob/46aa917fda0d0726690e384632e5cefae46bab68/docs/release-notes.md?plain=1#L163
>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>>>> On Fri, Jul 5, 2024 at 5:44 PM Cyril DE CATHEU <cy...@startree.ai
>>>>>> <mailto:cy...@startree.ai>> wrote:
>>>>>>> Had a deeper look, it seems the LookaheadSuccess instance is exploited
>>>>>> for conditional branching
>>>>>>> 
>>>>>>> Eg:
>>>>>>> 
>>>>>>> try { return !jj_3_2(); }
>>>>>>> catch(LookaheadSuccess ls) { return true; }
>>>>>>> finally { jj_save(1, xla); }
>>>>>>> 
>>>>>>> So I guess lazy instantiation will not help because the
>>> LookaheadSuccess
>>>>>> will be instantiated anyway.
>>>>>>> If we know this error is always caught internally in the class, could
>>> we
>>>>>> override the constructor to not do a call to
>>> Throwable.fillInStackTrace();
>>>>>> ? which is what makes this slow.
>>>>>>> 
>>>>>>> On Fri, Jul 5, 2024 at 10:53 AM Cyril DE CATHEU <cy...@startree.ai
>>>>>> <mailto:cy...@startree.ai>> wrote:
>>>>>>> Hey,
>>>>>>> 
>>>>>>> I'm using Calcite to parse snippets of SQL.
>>>>>>> My (simplified) function looks like this:
>>>>>>> 
>>>>>>> public static SqlNode expressionToNode(final String sqlExpression,
>>>>>>>  final SqlParser.Config config) {
>>>>>>> SqlParser sqlParser = SqlParser.create(sqlExpression, config);
>>>>>>> try {
>>>>>>>  return sqlParser.parseExpression();
>>>>>>> } catch (SqlParseException e) {
>>>>>>>   //do something
>>>>>>> }
>>>>>>> }
>>>>>>> 
>>>>>>> and it is called many times.
>>>>>>> The parser set in the config is Babel, so the parser instantiated is
>>>>>> SqlBabelParserImpl.
>>>>>>> A SqlBabelParserImpl is instantiated every time this function is
>>> called.
>>>>>> From what I understand this parser cannot be re-used easily to parse
>>>>>> different expressions but let me know if I'm incorrect.
>>>>>>> 
>>>>>>> The issue I'm encountering is the instantiation of this class is
>>> pretty
>>>>>> slow because one of the instance field  jj_ls extends java.lang.Error
>>> and
>>>>>> is instantiated when the SqlBabelParserImpl is instantiated.
>>>>>>> 
>>>>>>> [Screenshot 2024-07-05 at 10.37.17.png]
>>>>>>> 
>>>>>>> here is the extract of the generated code in SqlBabelParserImpl:
>>>>>>> 
>>>>>>> static private final class LookaheadSuccess extends java.lang.Error {
>>> }
>>>>>>> 
>>>>>>> final private LookaheadSuccess jj_ls = new LookaheadSuccess();
>>>>>>> 
>>>>>>> final private boolean jj_scan_token(int kind) {
>>>>>>> if (jj_scanpos == jj_lastpos) {
>>>>>>>  jj_la--;
>>>>>>>  if (jj_scanpos.next == null) {
>>>>>>>    jj_lastpos = jj_scanpos = jj_scanpos.next =
>>>>>> token_source.getNextToken();
>>>>>>>  } else {
>>>>>>>    jj_lastpos = jj_scanpos = jj_scanpos.next;
>>>>>>>  }
>>>>>>> } else {
>>>>>>>  jj_scanpos = jj_scanpos.next;
>>>>>>> }
>>>>>>> if (jj_rescan) {
>>>>>>>  int i = 0; Token tok = token;
>>>>>>>  while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
>>>>>>>  if (tok != null) jj_add_error_token(kind, i);
>>>>>>> }
>>>>>>> if (jj_scanpos.kind != kind) return true;
>>>>>>> if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls;
>>>>>>> return false;
>>>>>>> }
>>>>>>> 
>>>>>>> 
>>>>>>> jj_ls is only used in an error case. (before last line of
>>> jj_scan_token)
>>>>>>> I'm assuming the re-use of a single error instance is done on purpose,
>>>>>>> but could we consider instantiate jj_ls lazily?
>>>>>>> 
>>>>>>> I can try to do this but I'm a bit lost in the code generation
>>> codebase
>>>>>> so I would need some pointers.
>>>>>>> 
>>>>>>> Have a nice day.
>>>>>>> --
>>>>>>> 
>>>>>>> [StarTree] <https://startree.ai>
>>>>>>> Cyril de Catheu
>>>>>>> Software Engineer
>>>>>>> +33 (684) 829-908<tel:+33+(684)+829-908>
>>>>>>> Follow us: [RSS] <https://www.startree.ai/blogs> [LinkedIn] <
>>>>>> https://www.linkedin.com/company/startreedata/> [Twitter] <
>>>>>> https://twitter.com/startreedata> [Slack] <https://stree.ai/slack>
>>>>>> [YouTube] <https://youtube.com/StarTreeData>
>>>>>>> 
>>>>>>> [Try StarTree Cloud Today]<
>>>>>> 
>>> https://get.startree.ai/startree-cloud?utm_campaign=byoc-edition-of-startree-cloud&utm_source=email&utm_content=startree-employee-email-signatures
>>>>>>> 
>>>>>> 
>>> 

Reply via email to