-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tweak generated class methods .lookup and .resolve for enums to prevent StackError when formatting #172
Conversation
47368a4
to
4890b39
Compare
… "nesting too deep" error
4890b39
to
94e2260
Compare
I also filed an issue for |
end | ||
|
||
LOOKUP = ERB.new(<<~LOOKUP, trim_mode: "-") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize this is just improving upon what we already have, but I wonder if we could use Hash
here or something else with constant-time access. As implemented, we're looking at linear-time lookups. I could see this potentially being faster if enum.value
was a frequency list with an infrequently accessed tail, but I think in practice it's just the order in the protobuf definition.
@tenderlove Were the values enumerated as a long list of branches for YJIT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're generating the code, we could do a mixed strategy, too (assuming the current approach is to optimize on YJIT). E.g., if enum.value.size < 10
or whatever, emit the chain of conditionals, otherwise store in a Hash
and do a lookup that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can merge this as-is then followup with that. I created an issue: #176
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tenderlove Were the values enumerated as a long list of branches for YJIT?
Yes, they were. The idea was that the enums were probably short enough that staying in machine code would outperform calling out to a hash. I thought at one point we'd used a sparse array though? I feel you could do this with an array, but the catch is that Protobuf allows negative enums.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I updated the Issue title to "Investigate using a Hash or sparse Array for Enum lookup and resolve instead of a series of conditonals"
Thank you for confirming the instruction sequences were equivalent in both cases. Some of the non-idiomatic code in the code generator is written that way to maximize performance, either in the interpreter or in YJIT. Comparing the instruction sequences helps avoid inadvertent regressions. |
The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me, seems like a necessary work-around
I ran across an issue when working with an
enum
definition with thousands of values.SyntaxTree
ran into a "nesting too deep" error when attempting to format the code:The error appeared to have something to do with the thousands of chained
elsif
s. I changed this to single line statements likereturn :foo if val == 1
, and this seems to have solved the issue. I this also makes the generated code a bit more concise and, in my opinion, a little more idiomatic. First I tried inserting newlines between theelsif
s to see if that would help, but it didn't.I dumped the instruction sequences of both with and without yjit and it looks to not make a difference:
ruby --yjit --dump=insns -e 'def foo(x); if x == 1; return :first; elsif x == 2; return :second; elsif x == 3; return :third; end; end'
ruby --yjit --dump=insns -e 'def foo(x); return :first if x == 1; return :second if x == 2; return :third if x == 3; end'
Interestingly, when I pulled out the
self.lookup
andself.resolve
methods to ERB templates, I started seeing Rubocop failures related to multi-line blocks with curly braces, so I fixed those too. I think some of the Ruby that generates Ruby might have been confusing Rubocop.