-
Notifications
You must be signed in to change notification settings - Fork 38
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
Optimise atom_manager! implementation #914
Conversation
winit uses atom_manager! with 59 atom names. Due to some sub-optimal code generation, this causes the generated new() function to end up with a size of 111.6 KiB, or almost 5% of the .text size of a winit example. The issue is that each "intern_atom()?" can cause an early exit and needs to call drop() of the already generated Cookies. This leads to some quadratic growth. To work around this, in this commit I change the implementation of new(). It now iterates over the atom names, using .map() to send the InternAtom requests. Finally, collect() is then used to change this Iterator<Item=Result<Cookie, Error>> into Result<Vec<Cookie>, Error>. This even preserves the early-exit behaviour in case of errors. The reply() function is then changed to turn the Vec into an iterator, using next() to pull out the items. This relies on the evaluation of this code to be well-defined (which e.g. wouldn't be guaranteed in C). Luckily, Rust provides the needed guarantee: rust-lang/reference#888 Fixes: #912 Signed-off-by: Uli Schlachter <psychon@znc.in>
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #914 +/- ##
=======================================
Coverage 13.06% 13.07%
=======================================
Files 190 190
Lines 136667 136674 +7
=======================================
+ Hits 17859 17870 +11
+ Misses 118808 118804 -4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
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.
LGTM aside from some macro hygeine
x11rb/src/x11_utils.rs
Outdated
$field_name: $crate::cookie::Cookie<'a, C, $crate::protocol::xproto::InternAtomReply>, | ||
)* | ||
__private_phantom: std::marker::PhantomData<&'a C>, | ||
__private_cookies: Vec<$crate::cookie::Cookie<'a, C, $crate::protocol::xproto::InternAtomReply>>, |
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.
__private_cookies: Vec<$crate::cookie::Cookie<'a, C, $crate::protocol::xproto::InternAtomReply>>, | |
__private_cookies: ::std::vec::Vec<$crate::cookie::Cookie<'a, C, $crate::protocol::xproto::InternAtomReply>>, |
Just in case a potential user defines their own Vec
struct.
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 for catching that! I turned the existing tests/x11_utils.rs
into a test case for that and also found a std::
which should be ::std::
.
x11rb/src/x11_utils.rs
Outdated
let names = [ | ||
$($crate::__atom_manager_atom_value!($field_name$(: $atom_value)?),)* | ||
]; | ||
let cookies: Result<Vec<_>, _> = names.into_iter().map(|name| _conn.intern_atom(false, name)).collect(); |
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.
let cookies: Result<Vec<_>, _> = names.into_iter().map(|name| _conn.intern_atom(false, name)).collect(); | |
let cookies: ::std::result::Result<::std::vec::Vec<_>, _> = names.into_iter().map(|name| _conn.intern_atom(false, name)).collect(); |
x11rb/src/x11_utils.rs
Outdated
let names = [ | ||
$($crate::__atom_manager_atom_value!($field_name$(: $atom_value)?),)* | ||
]; | ||
let cookies: Result<Vec<_>, _> = names.into_iter().map(|name| _conn.intern_atom(false, name)).collect(); |
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.
_conn.intern_atom(
Should we import the x11rb::protocol::xproto::ConnectionExt
trait here, in case the user hasn't already?
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.
Uhm... this doesn't need ConnectionExt
?
I wanted to make x11rb/tests/x11_utils.rs
test for this, but it already does. It does not import ConnectionExt
.
If I had to guess, I would say the bound C: $crate::protocol::xproto::ConnectionExt
brings the trait into scope.
Interesting find! I wonder if rustc is being particularly dumb here or if, say, g++ has the same behaviour 🤔 |
@yshui FYI, I reported this upstream: rust-lang/rust#120604 Here is an attempt.#include <optional>
struct Dropper {
Dropper();
~Dropper();
};
bool choice();
struct Drops {
Dropper a, b;
#ifdef DO_C
Dropper c;
#endif
#ifdef DO_D
Dropper d;
#endif
};
std::optional<Drops> __attribute__((noinline)) do_it() {
Dropper a;
if (choice()) { return std::nullopt; }
Dropper b;
if (choice()) { return std::nullopt; }
#ifdef DO_C
Dropper c;
if (choice()) { return std::nullopt; }
#endif
return Drops {
.a = std::move(a),
.b = std::move(b),
#ifdef DO_C
.c = std::move(c),
#endif
#ifdef DO_D
.d = std::move(d),
#endif
};
} Without That seems linear to me. |
This makes sure that the macro refers to standard types in an unambiguous way that does not use types from the local module. Instead, a full path beginning with "::std" is used. Thanks to @notgull for pointing this out. Signed-off-by: Uli Schlachter <psychon@znc.in>
@psychon slightly tweaked your example so it's a bit clear: https://godbolt.org/z/GqWcPn9bs so g++ indeed does what i expect here, it's essentially: Dropper a;
if (cond1) goto free1;
Dropper b;
if (cond2) goto free2;
Dropper c;
if (cond3) goto free3;
//....
free3:
delete c;
free2:
delete b;
free1:
delete a;
return definitely something worth fixing in Rust. I am not familiar with rustc though.... |
ah but in Rust's case it's more complex because some variables could have been moved thus don't need dropping. |
winit uses atom_manager! with 59 atom names. Due to some sub-optimal code generation, this causes the generated new() function to end up with a size of 111.6 KiB, or almost 5% of the .text size of a winit example.
The issue is that each "intern_atom()?" can cause an early exit and needs to call drop() of the already generated Cookies. This leads to some quadratic growth.
To work around this, in this commit I change the implementation of new(). It now iterates over the atom names, using .map() to send the InternAtom requests. Finally, collect() is then used to change this Iterator<Item=Result<Cookie, Error>> into Result<Vec, Error>. This even preserves the early-exit behaviour in case of errors.
The reply() function is then changed to turn the Vec into an iterator, using next() to pull out the items. This relies on the evaluation of this code to be well-defined (which e.g. wouldn't be guaranteed in C). Luckily, Rust provides the needed guarantee:
rust-lang/reference#888
Fixes: #912
I tried to measure this with latest winit master:
With the latest x11rb release, the result is 16 MB / 2,0 MB. With this change (copy&paste the macro into the winit code), it is 15 MB / 2.0 MB.