-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Use closure for init constraints #2939
Conversation
@CanardMandarin is attempting to deploy a commit to the coral-xyz Team on Vercel. A member of the Team first needs to authorize it. |
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.
Using closures for init constraints helps decrease the stack usage and helps mitigate the issue.
That's definitely interesting because using map_err
and creating a closure uses more stack space compared to regular match
statements. I'm also not sure if this behavior will always be the same with all Rust versions.
Is it possible to add a test that demonstrates the improvement in stack usage?
@acheroncrypto @CanardMandarin , are closures always guranteed to be |
so i tested it out with the following struct , reduced stack usage from 15k to 4k.
Got good results , |
hm? yeah you can. |
Hey
Seems i misread that pr and thought its still not avbl on stable-wrapping in curly braces compiles fine- thanks for the headsup @workingjubilee . |
You can always add the attribute fn main () {
let demo = {
#[inline(never)] || println!("hello")
};
demo();
} |
This is exactly what i am doing, wrapped it in a inline closure,the only issue which is preventing me from making a pr is, how do we add bench test for this, is there a way to deterministically measure stack frame size usage, need this since i need to add before and after differences as part of the pr. |
You can check the generated assembly code for the contract and analyze the largest offset from |
Not really a good one, no, and Rust does not promise that we do not make the stack size bigger for essentially no reason. ( I mean, we'll probably have a reason, but you might think of it as a silly one, so you would see it as No Reason At All. ) |
Hey ser @workingjubilee could you elaborate on your point, did not exactly understand, also the approach for inline(never) closure is to force rust to call the providing body of the closure in a seperate function as the ebpf vm provides a fresh stack frame for each function so that is what we are trying to achieve via inline(never). |
|
| Program | Binary Size | - | | ||
| ------- | ----------- | --------------------- | | ||
| bench | 787,968 | 🟢 **-3,040 (0.38%)** | | ||
| Program | Binary Size | - | | ||
| ------- | ----------- | ------------------------ | | ||
| bench | 1,096,096 | 🔴 **+305,088 (38.57%)** | |
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 was planning to merge this today, but the bench results show ~38% increase in binary size with this change. Always using closures for init
constraints might not be what we want. Decreasing stack usage is certainly more important than binary size, but the difference is still massive.
Thoughts?
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 would kinda expect this might happen due to applying inline(never)
: Closures benefit a lot from being able to inline and remove any redundant logic inside them.
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.
Thanks! I removed the inline(never)
s, but it had very little effect on the binary size (+300k rather than +305k).
What we want is to inline them as long as the try_accounts
function doesn't use more than 4096 bytes of stack. Not sure if there is a simple way to make the compiler care about the stack limit when deciding whether to inline or not.
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.
if inline had no major contribution in the 38% increase, what else could have caused such a difference ?
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 bench program is a rather unusual program that has 90 init
s which skews the results. The binary size difference won't be as bad for regular programs.
I think we can get this in as is, since decreasing stack usage of try_accounts
is very valuable currently (at least until we get dynamic stack frames).
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.
Finally able to merge this — thank you for the major improvement!
Hello,
This PR is somewhat related to issues #2915 and #2920.
It seems that with Anchor 0.29, stack usage in
try_accounts
increased and more easily causesStack offset of X exceeded max offset of 4096 by X bytes, please minimize large stack variables
error.Existing projects using Anchor could have trouble migrating, and it looks like this question appears a lot in Discord.
Using closures for init constraints helps decrease the stack usage and helps mitigate the issue.
What do you think?