-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Sped up chrono.h
formatting for cases without providing locale
#2576
Conversation
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 the PR! Looks good, just minor comments/questions inline.
if (localized) { | ||
out = detail::write<char_type>( | ||
out, time, context.locale().template get<std::locale>(), format, | ||
modifier); | ||
} else { | ||
out = detail::write<char_type>(out, time, get_classic_locale(), format, | ||
modifier); | ||
} |
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.
Here and below: let's keep the conditional expression to avoid repetition.
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.
What do you mean by "conditional expression"? Ternary ?:
?
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.
Yes, ternary.
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.
Currently it looks like this in the trunk:
const auto& loc = localized ? context.locale().template get<std::locale>()
: std::locale::classic();
context.locale().get()
returns locale object by value so this expression results in a temporary object.
Since one of the subexpressions results in a temporary object, the whole conditional operator
localized ? context.locale().template get<std::locale>() : std::locale::classic()
results in a temporary object. The resulting temporary is then bound to a const
reference.
When the condition is false (e.g. localized==false
or locale reference is empty and therefore bool{context.locale()}==false
) the last subexpression is evaluated: std::locale::classic()
in this case.
Since the conditional operator results in a temporary, the locale object classic()
returns reference to is copy constructed into a temporary object, and then destructed at the end of the scope. Copy construction and destruction of locale objects are relatively expensive operations because they need to increment and decrement a bunch of reference counters of locale facets. This is significant compared to work done for simple format specifications like %Y
.
I changed it so these expressions are evaluated in separate scopes. Depending on the condition, context.locale().get()
results in a temporary which is bound to a function parameter, and a reference returned by std::locale::classic()
is passed directly as function parameter without introducing a temporary.
Please let me know if you want me to back all of this up by quotes from and/or links to the C++ standard and/or cppreference.
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.
Makes sense, thanks for the detailed explanation. One thing that concerns me is that this change can be easily undone by accident with a seemingly innocent refactor. I suggest moving this optimized locale handling logic into a separate function:
template <typename FormatContext, typename F>
void with_locale(bool localized, FormatContext& ctx, F f) {
if (localized) f(ctx.locale().template get<std::locale>());
else f(get_classic_locale());
}
and adding a short comment explaining why it is needed.
Then the usage can be something like
with_locale(localized, context, [&](std::locale&) {
out = detail::write<char_type>(out, time, locale, format, modifier);
});
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.
One other variant can be to lazily construct the requested locale within the context object and return const
reference to it from context.locale()
.
Anyway I think such refactoring is out of the scope of this PR.
I can do something like you suggested in the next PR.
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 can do something like you suggested in the next PR.
Please do. Thanks!
@@ -1822,6 +1834,18 @@ template <typename Char> struct formatter<std::tm, Char> { | |||
return end; | |||
} | |||
|
|||
template <typename It> | |||
It do_format(It out, const std::tm& tm, const std::locale& loc) const { |
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.
Why introduce this function?
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.
To avoid repetition in format()
method.
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.
Keeping ternary should avoid the repetition and introducing a new helper funtion.
Sped up
chrono.h
formatting (compared to the current trunk) in cases without providing locale by the amount roughly equal to three quarters of the time ofi.e. that one is sped up roughly four times.
For more elaborate format specifications and for non-compile-time cases the speedup is proportionally smaller, i.e. the speedup is not proportional to spec contents but rather is kinda a fixed amount.