Skip to content
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

YJIT: Optimize attr_writer #9986

Merged
merged 2 commits into from Feb 22, 2024
Merged

YJIT: Optimize attr_writer #9986

merged 2 commits into from Feb 22, 2024

Conversation

k0kubun
Copy link
Member

@k0kubun k0kubun commented Feb 16, 2024

This PR optimizes attr_writer by reusing setinstancevariable's shape-based implementation gen_set_ivar.

This seems to speed up railsbench by 2%.

before: ruby 3.4.0dev (2024-02-21T23:09:17Z master 35f68e7dff) +YJIT [x86_64-linux]
after: ruby 3.4.0dev (2024-02-22T01:26:29Z yjit-attrset f27198fac3) +YJIT [x86_64-linux]

----------  -----------  ----------  ----------  ----------  -------------  ------------
bench       before (ms)  stddev (%)  after (ms)  stddev (%)  after 1st itr  before/after
railsbench  2439.5       1.0         2382.9      0.8         1.01           1.02
----------  -----------  ----------  ----------  ----------  -------------  ------------

@k0kubun k0kubun force-pushed the yjit-attrset branch 13 times, most recently from e427b28 to 7e9af54 Compare February 21, 2024 01:39
@k0kubun k0kubun marked this pull request as ready for review February 22, 2024 01:52
@matzbot matzbot requested a review from a team February 22, 2024 01:52
Comment on lines +2747 to +2770
if let StackOpnd(index) = recv_opnd { // attr_writer
let recv = asm.stack_opnd(index as i32);
asm_comment!(asm, "call rb_vm_set_ivar_id()");
asm.ccall(
rb_vm_set_ivar_id as *const u8,
vec![
recv,
Opnd::UImm(ivar_name),
val_opnd,
],
);
} else { // setinstancevariable
asm_comment!(asm, "call rb_vm_setinstancevariable()");
asm.ccall(
rb_vm_setinstancevariable as *const u8,
vec![
Opnd::const_ptr(jit.iseq as *const u8),
Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF),
ivar_name.into(),
val_opnd,
Opnd::const_ptr(ic.unwrap() as *const u8),
],
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like a good idea to reuse code and it's nice to see a 2% speedup! 👍

However, this PR introduces multiple if statements to use different operands and change the behavior, which seems a bit weird.

Just to make sure I understand, attr_writers do not have ICs? That's why you have to call a different function here?

Copy link
Member Author

@k0kubun k0kubun Feb 22, 2024

Choose a reason for hiding this comment

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

You're right. attr_writers don't have ICs we need here, so attr_writers can't use rb_vm_setinstancevariable. An attr_writer is a method call, so it has an cd->cc (method call cache), but it doesn't have an instance-variable cache ic (IVC), which is used by setinstancevariable.

Similarly, setinstancevariable can't use rb_vm_set_ivar_id, which supports only T_OBJECT. attr_writer fallbacks to a dynamic dispatch when the receiver is not T_OBJECT, but setinstancevariable wants to use this general case for non-T_OBJECT cases.

We could use rb_ivar_set for both cases, which supports non-T_OBJECT receivers as well, but I saw a slowdown when I tried that, so I kept this logic. But let me benchmark it again 🤔

Copy link
Member Author

@k0kubun k0kubun Feb 22, 2024

Choose a reason for hiding this comment

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

We could use rb_ivar_set for both cases, which supports non-T_OBJECT receivers as well, but I saw a slowdown when I tried that, so I kept this logic. But let me benchmark it again 🤔

It does seem to give a slowdown (these benchmarks consistently give a 0.99x change). It seems to hurt the performance if you handle both cases using rb_ivar_set. Another thing we could try is to replace rb_vm_setinstancevariable with rb_ivar_set, but it still leaves this if branch, so it doesn't simplify code.

before: ruby 3.4.0dev (2024-02-22T01:26:29Z yjit-attrset f27198fac3) +YJIT [x86_64-linux]
after: ruby 3.4.0dev (2024-02-22T17:12:46Z yjit-attrset 2b73548263) +YJIT [x86_64-linux]

----------  -----------  ----------  ----------  ----------  -------------  ------------
bench       before (ms)  stddev (%)  after (ms)  stddev (%)  after 1st itr  before/after
hexapdf     1862.5       3.0         1874.3      1.9         1.02           0.99
railsbench  2423.5       0.9         2436.3      0.8         0.98           0.99
----------  -----------  ----------  ----------  ----------  -------------  ------------

Copy link
Member Author

@k0kubun k0kubun Feb 22, 2024

Choose a reason for hiding this comment

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

Another thing we could try is to replace rb_vm_setinstancevariable with rb_ivar_set, but it still leaves this if branch, so it doesn't simplify code.

I also tried this, but it also gave a slowdown (0.99x) on railsbench. So it seems more efficient to keep using rb_vm_setinstancevariable for setinstancevariable and rb_vm_set_ivar_id for attr_writer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh yeah, I wasn't suggesting that we use the less efficient function for both. The code is a bit harder to follow though with the special casing, but I understand why it is there.

yjit/src/codegen.rs Outdated Show resolved Hide resolved
@maximecb maximecb merged commit a16fefc into ruby:master Feb 22, 2024
96 checks passed
@k0kubun k0kubun deleted the yjit-attrset branch February 22, 2024 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
2 participants