8000 YJIT: Add RubyVM::YJIT.enable by k0kubun · Pull Request #8705 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

YJIT: Add RubyVM::YJIT.enable #8705

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

Merged
merged 1 commit into from
Oct 19, 2023
Merged

YJIT: Add RubyVM::YJIT.enable #8705

merged 1 commit into from
Oct 19, 2023

Conversation

k0kubun
Copy link
Member
@k0kubun k0kubun commented Oct 19, 2023

Background

  • Some Rails Core members are interested in letting Rails enable YJIT by default with an initializer/hook.
    • Originally, DHH posted he wants YJIT to be enabled by default for Rails performance.

Changes

  • Add RubyVM::YJIT.enable that can enable YJIT even if it wasn't enabled at boot
    • To safely achieve this, continuations will have a rb_jit_cont even if JIT is disabled.
    • RUBY_DESCRIPTION gets +YJIT when this method is called.
  • Remove RubyVM::YJIT.resume and rename --yjit-pause to --yjit-disable
    • We got a "bug report" that RubyVM::YJIT.enabled? returns true when it's paused, even though it was the intended behavior for us. To avoid such confusion, this PR gets rid of "resume"/"pause" terminology and only leaves "enable"/"disable".
    • For backward compatibility (and because I think it's useful), using YJIT tuning options still enables YJIT even if you don't use --yjit or RUBY_YJIT_ENABLE=1. You may sometimes need to specify YJIT tuning options while disabling YJIT at boot, and --yjit-disable can be used for that purpose (e.g. --yjit-call-threshold=1 --yjit-disable).

Benchmark

No significant performance impact on the interpreter.

before: ruby 3.3.0dev (2023-10-19T03:11:23Z master 3f5ec5c866) [x86_64-linux]
after: ruby 3.3.0dev (2023-10-19T06:18:53Z yjit-enable 073f04b516) [x86_64-linux]

--------------  -----------  ----------  ----------  ----------  -------------  ------------
bench           before (ms)  stddev (%)  after (ms)  stddev (%)  after 1st itr  before/after
activerecord    106.9        2.2         106.2       2.1         1.01           1.01
chunky-png      964.3        0.3         973.9       0.4         0.99           0.99
erubi-rails     28.9         12.9        29.6        14.4        0.83           0.98
hexapdf         3875.0       0.6         3847.1      0.7         1.03           1.01
liquid-c        106.3        0.7         105.7       0.7         1.01           1.01
liquid-compile  107.5        0.2         106.9       0.2         1.01           1.01
liquid-render   256.9        0.2         256.0       0.3         1.00           1.00
lobsters        1485.0       1.4         1486.5      1.8         0.99           1.00
mail            209.1        0.6         208.7       0.2         1.00           1.00
psych-load      3030.0       0.1         2989.3      0.2         1.02           1.01
railsbench      3363.5       0.4         3348.9      0.4         1.01           1.00
ruby-lsp        91.9         0.5         91.4        0.3         1.00           1.01
sequel          113.9        0.9         113.2       1.0         1.00           1.01
--------------  -----------  ----------  ----------  ----------  -------------  ------------

@k0kubun k0kubun force-pushed the yjit-enable branch 2 times, most recently from 1c7444c to 885eace Compare October 19, 2023 05:13
@k0kubun k0kubun marked this pull request as ready for review October 19, 2023 05:54
@matzbot matzbot requested a review from a team October 19, 2023 05:54
Copy link
Member
@byroot byroot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes perfect sense to me 👍

Comment on lines +174 to +190
void
Init_ruby_description(ruby_cmdline_options_t *opt)
{
const char *const jit_opt =
RJIT_OPTS_ON ? " +RJIT" :
YJIT_OPTS_ON ? YJIT_DESCRIPTION :
"";
define_ruby_description(jit_opt);
}

void
ruby_set_yjit_description(void)
{
rb_const_remove(rb_cObject, rb_intern("RUBY_DESCRIPTION"));
define_ruby_description(YJIT_DESCRIPTION);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR looks good and simplifies a lot of things 👍

This is the only part I'm not sure about. It's not fully clear what the Ruby description is going to print. Does it only print +YJIT if YJIT is enabled, which can happen later during execution?

I feel like ideally, the description should tell us that this is a YJIT-capable build of CRuby, so maybe it should say something like +YJIT (enabled) or +YJIT (disabled) ? Otherwise, as a simpler solution, we could just always have +YJIT, and people would have to query RubyVM::YJIT::enabled? if they want to know if YJIT is enabled or not.

Copy link
Member Author
@k0kubun k0kubun Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it only print +YJIT if YJIT is enabled, which can happen later during execution?

Correct.

This PR doesn't change the format. If disabled, that part is "". If enabled, that part is "+YJIT", "+YJIT dev", "+YJIT dev_nodebug", or "+YJIT stats". The whole point of this PR's design is to avoid confusion, so I wouldn't introduce "+YJIT (disabled)" (also that state is in fact the same as just running the interpreter).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing that may be confusing is that the "+YJIT" is going to appear only when YJIT is enabled, which is going to happen much later if YJIT is enabled in software. You won't see it if you run ruby -v.

That's why I think YJIT enabled and YJIT disabled make sense to report. It lets us know we have a YJIT-capable build of CRuby.

Copy link
Member Author
@k0kubun k0kubun Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have asked this first: what problem do you want to solve with this? Does it really require RUBY_DESCRIPTION to have +YJIT? If you're concerned about possibly breaking RubyVM::YJIT.enable when Ruby is not built with --enable-yjit and troubleshooting it is hard, for example, can we just tell that it's not built with --enable-yjit when RubyVM::YJIT.enable is called? If you want to quickly check if it's built with --enable-yjit, why not just run ruby --yjit -v?

In CRuby, we generally present build configurations in RbConfig::CONFIG instead of RUBY_DESCRIPTION. RbConfig::CONFIG["YJIT_SUPPORT"] is "yes" or "no". Can we use that information in RubyVM::YJIT APIs or warning/error messages to deal with the problem? Would you like to introduce RubyVM::YJIT.supported? to wrap it?

The thing that may be confusing

To me, what seems more confusing to end users is "+YJIT (disabled)". "+YJIT" seems to say YJIT is enabled, but "(disabled)" also says it's disabled. "+YJIT (disabled)" seems just as confusing as "YJIT is enabled but paused".

I think the "+XXX" format is generally used when it's actually enabled, not when it's capable of doing it. For example, JRuby prints +indy and +jit when the flags are passed, and doesn't print +indy (disabled) or +jit (disabled) when built with that support but disabled.

Similarly, today's CRuby prints +MN when RUBY_MN_THREADS=1 is given, and doesn't print +MN (disabled) just by the fact that USE_MN_THREADS macro is 1 when it's disabled. It seems inconsistent in CRuby alone too.

One last note, changing the format of RUBY_DESCRIPTION for the YJIT-disabled mode might need discussions on bugs.ruby-lang.org and Matz's approval unlike RubyVM::YJIT-closed changes like the current diff. I would rather merge this first without breaking compatibility to make sure we land this feature in Ruby 3.3, and then spend time discussing the format of RUBY_DESCRIPTION, which is less important than adding RubyVM::YJIT.enable in 3.3.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be useful to know if we're running a YJIT-capable version of Ruby when doing ruby -v, that would be the use case, but I get that you don't want to break convention with other flags shown in the Ruby description.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've always wanted a quick way to check if --enable-shared because it does slow down the interpreter and macOS and ruby-build unfortunately turn it on by default. I wish ruby -v printed it, so I guess I get your use case.

I would still want something less confusing than +YJIT (disable), though. If there's no concise way to communicate it in ruby -v, maybe we could make ruby -vv more verbose and let it print important build configurations like YJIT_SUPPORT and ENABLE_SHARED. Either way, we'd need a ticket to introduce such changes. Until then, ruby -v --yjit seems like a good enough compromise.

@k0kubun k0kubun merged commit 6beb09c into ruby:master Oct 19, 2023
@k0kubun k0kubun deleted the yjit-enable branch October 19, 2023 17:54
k0kubun added a commit to Shopify/ruby that referenced this pull request Oct 19, 2023
k0kubun added a commit to Shopify/ruby that referenced this pull request Oct 19, 2023
k0kubun added a commit to Shopify/ruby that referenced this pull request Oct 19, 2023
k0kubun added a commit that referenced this pull request Oct 20, 2023
Having this variable actually helps the performance of non-JITed calls.

-----  -----------  ----------  ----------  ----------  -------------  ------------
bench  before (ms)  stddev (%)  after (ms)  stddev (%)  after 1st itr  before/after
fib    241.9        0.5         225.4       1.0         1.06           1.07
-----  -----------  ----------  ----------  ----------  -------------  ------------

(benchmarked with --yjit-cold-threshold=0)
casperisfine pushed a commit to Shopify/rails that referenced this pull request Nov 7, 2023
There was many public reports of 15-25% latency improvements for Rails
apps that did enable Ruby 3.2 YJIT, and in 3.3 it's even better.

Following ruby/ruby#8705, in Ruby 3.3 YJIT
is paused instead of disabled by default, allowing us to enable it
from an initializer.
@k0kubun k0kubun mentioned this pull request Nov 7, 2023
@Edwing123
Copy link
Contributor

Seeing +yjit in RUBY_DESCRIPTION (or when passing the -v flag) could be interpreted as Ruby having support for YJIT. So, it would be good to figure out how to clearly state whether YJIT is disabled or not. In my opinion, RUBY_DESCRIPTION should only state whether it has support for YJIT, and let the process of verifying if it's enabled with RubyVM::YJIT.enabled?.

Regarding RubyVM::YJIT.supported?, I'd rather use what Katashi suggested: RbConfig::CONFIG["YJIT_SUPPORT"]. That's because I'd expect RubyVM:YJIT to not be defined if RbConfig::CONFIG["YJIT_SUPPORT"] resolves to "no".

yoones pushed a commit to yoones/rails that referenced this pull request Mar 6, 2025
There was many public reports of 15-25% latency improvements for Rails
apps that did enable Ruby 3.2 YJIT, and in 3.3 it's even better.

Following ruby/ruby#8705, in Ruby 3.3 YJIT
is paused instead of disabled by default, allowing us to enable it
from an initializer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0