-
Notifications
You must be signed in to change notification settings - Fork 565
Two sorbet-runtime call-validation optimizations #4158
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
Conversation
Do the api method tests use T::Profile right now in Stripe codebase? Is the plan to delete or change those tests? |
@jez they don't. If you're talking about the no-hot-runtime-checking tests, they use a custom mechanism so they can report more information about which methods are called. T::Profile is only used in a couple of production log-lines and I don't believe it provides real value there: https://livegrep.corp.stripe.com/search/stripe?q=T%3A%3AProfile%20repo%3Apay-server&fold_case=auto®ex=false&context=true (It was maybe a bit more valuable before the introduction of the no-hot-runtime-checking tests, and when recursive checking was the default, so it was easier to introduce a regression and having a number right in the canonical log line could be valuable. I don't think this really applies anymore.) |
@@ -1,30 +1,25 @@ | |||
# typed: true | |||
# frozen_string_literal: true | |||
|
|||
# Deprecated, kept only for partial backwards compatibility |
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.
When do you plan to remove this completely?
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.
Not sure. Could potentially remove it completely now if you want; I'll have to do some advance work in pay-server anyway to keep tests passing.
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.
yeah i don't think we every documented or publicized this much, so i think it's safe to just remove the ~5 usages of it in Stripe's codebase pre-emptively and then land this PR.
Gotcha, so it seems like it's basically just populating some log lines, and since you've made a case that those log lines are not even really accurate, that it's not worth keeping these things around. |
fmt::print(" # This block is called for every `sig`. It's critical to keep it fast and\n" | ||
" # reduce number of allocations that happen here.\n" |
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.
ooc why have these comments been dropped?
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.
It's not literally true - none of these individual methods is called for every sig
anymore - and it didn't seem like it was adding enough value to be worth trying to reword.
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.
wfm
" CallValidation.report_error(\n" | ||
" method_sig,\n" | ||
" message,\n" | ||
" 'Return value',\n" | ||
" nil,\n" | ||
" return_type,\n" | ||
" return_value,\n" | ||
" caller_offset: -1\n" | ||
" )\n" | ||
" end\n" |
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.
The other two (methods, procedures) take almost the same codepath but then branch on validatorKind
in a few places.
It seems like the "medium" path is pretty similar to the fast path, differing only by
- the names of the individual methods
- the condition used to check the types
Do you think it's worth trying to share more code between the different kinds of validators?
Or at least factor out some of the shared code?
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.
The handling of return_type
is different too, but that doesn't affect your point much.
I agree that more code could be shared; I started off with a separate method because I wasn't sure how much would end up being shared, but it ended up being most of it.
I think merging might make things less readable - I already find the switches on ValidatorKind a bit hard to read -but it would make it less likely that some difference could be introduced accidentally, so maybe it's worth it. WDYT?
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 tend to think that in terms of "hard to read," if you're trying to change this code you're going to be looking at the generated code, imagining how you'd like the generated code to change, and back-solving for a diff to the the generator (i.e., the C++ code is not read in a vacuum, it is read in tandem with the generated code).
Given that, I place more value on the second part (introducing an accidental divergence).
So yeah, if you don't feel strongly one way or another i'd love to share more of the repetitive code between these two.
but it ended up being most of it
for what it's worth, this is basically the same process and realization that I had when I wrote it initially (it started out as two, and then I realized that so little was different)
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.
Change looks mostly good to me. I have one question about rollout / deprecation strategy, and one question about how we can share a little more code.
Happy to approve after having a chat about those things.
839173f
to
ede071e
Compare
@jez updated to remove T::Profile fully and to share more call-validation-generator code. The diff does look much cleaner with the latter change. The Stripe codebase is also now prepped for this (PR just merged). |
Two changes:
Also method-vs-procedure is left for a runtime check, for now, to avoid blowing up code size and because one extra branch didn't seem like a big deal relative to the rest.edit: when merging the generator code, it was simpler to just leave this distinction in for both paths.)These are really related only in intent; I actually implemented T::Profile support in the new fast-ish path before ripping it out. So I'm happy to split into separate PRs if folks prefer.
Motivation
I don't believe this timer measures anything useful. It doesn't account for a significant proportion of runtime typechecking overhead, probably the majority of overhead, because (a) it doesn't capture the impact of adding the
define_method
stack frame itself, and (b) it doesn't capture any impact ofT.let
,T.must
,T.unsafe
etc. A tool like StackProf will give a much better picture, and (if configured well) with much lower measurement overhead. Microbenchmarks show >20% speedup on single-argument methods.I don't have hard statistics but my impression is that a large number of relatively-simple methods fail to qualify for the existing fast path because they use something like T::Boolean, T.nilable, or T.untyped, even as just one argument among otherwise simple ones. Microbenchmarks show a >40% speedup on single-nilable-argument methods.
bundle exec rake bench:typecheck
results for 2.7, slightly abridged, on:master:
just the new fast path:
with both changes:
(Only the changes in the
sig
results could be meaningful, but I've left the others in as points of comparison & indicators of the noise level.)Test plan
Relying on existing tests