-
-
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
Refactor/modernize shrink scopedtimer #13236
Refactor/modernize shrink scopedtimer #13236
Conversation
Instead of emulating an optional by use of a pointer and placement-`new` construction, use a `std::optional` directly. Also remove `m_cancel` as the empty optional is sufficient for indicating a cancelled timer. Removing these members shrunk the memory footprint from 72 to 56 bytes, reducing the memory overhead of ScopedTimer compared to the underyling Timer to 8 bytes (Timer currently being 48 bytes large). While these improvements are certainly nice, the purpose of this PR is the improved underlying code. However, there is still lots that could be improved in the future.
ScopedTimer t("WaveformWidgetFactory::render() %1waveforms", | ||
static_cast<int>(m_waveformWidgetHolders.size())); | ||
ScopedTimer t("WaveformWidgetFactory::render() %1 waveforms", | ||
QString::number(m_waveformWidgetHolders.size())); |
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 idea was that all ScopeTimer statements have almost zero CPU overhead, when developer mode is disabled.
QString does allocate memory even if the scoped timer is disabled so this is not the best idea.
We may look for a solution that is equal fast in normal mode and faster in developer mode. This can be archived with the constexpr QLatin1String constructor. I have proposed here a Qt < 6.4 solution:
mixxx/src/track/taglib/trackmetadata_file.cpp
Lines 48 to 54 in 751a104
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) | |
using namespace Qt::Literals::StringLiterals; | |
#else | |
constexpr inline QLatin1String operator""_L1(const char* str, size_t size) noexcept { | |
return QLatin1String{str, static_cast<int>(size)}; | |
} | |
#endif |
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 idea was that all ScopeTimer statements have almost zero CPU overhead, when developer mode is disabled.
Yes. I got that from the design and I tried to achieve the same here. This is the one case where the newer API does not exactly match the old one, but I figured this was a small-enough prize to pay in this case. There is also small-string-optimization, which should lessen the cost of the construction considerably. Nevermind, apparently QString only features CoW, no SSO.
We may look for a solution that is equal fast in normal mode and faster in developer mode. This can be archived with the constexpr QLatin1String constructor.
I'm afraid I can't quite follow you here. How does that help in the QString::number
case?
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.
Just don't use QString::number. It is a heap allocating function. Does passing an int not work?
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.
No because the QString::arg(Args&&...)
version only accepts objects implicitly convertible to int...
I can try creating a wrapper that does also accept numeric types and calls QString::number on them, though I don't know how complex the metaprogramming for that would be.
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.
Implemented in the latest commit, wdyt?
The assembly looks pretty decent in the argument-less case. In the lazy case (no compile time QString construction, it obviously looks a little worse, but I think thats fine. I didn't compare it with the current state.
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.
Thank you for this nice improvement.
c94babc
to
1f27406
Compare
build all green. I think its easier to review if I squash all the fixups first. Or do you already have a review pending? @daschuer |
ca7c9e1
to
e4263ae
Compare
Do we have a use case where we need to pass a QString as
Alternatinve that does not require
(Both untested) |
} else if constexpr (is_number_compatible_v<T>) { | ||
return QString::number(std::forward<T>(arg)); | ||
} else { | ||
static_assert(always_false_v<T>, "Unsupported type for QString::arg"); |
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.
unfortunately this throws an error when picked for main (#12548), dunno if that is to be expected, let alone how to fix it (except using static_assert(false, ...)
)
https://github.com/mixxxdj/mixxx/actions/runs/9103424016/job/25025246943?pr=12548
/src/util/stringformat.h: In function ‘auto convertToQStringConvertible(T&&)’:
Error: /src/util/stringformat.h:36:23: error: reference to ‘always_false_v’ is ambiguous
36 | static_assert(always_false_v<T>, "Unsupported type for QString::arg");
| ^~~~~~~~~~~~~~
/src/util/stringformat.h:24:23: note: candidates are: ‘template<class T> constexpr const bool {anonymous}::always_false_v<T>’
24 | static constexpr bool always_false_v = false;
| ^~~~~~~~~~~~~~
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 think its getting confused because always_false_v
is also already defined in other headers (likely from #12781). So you should be able to fix it by shuffling some code around. Potentially just putting a single definition in a single header and then using that across both files could be enough
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.
Ah, okay, in main
this seems to clash with always_false_v
declared in midimessage.h (where it is inline constexpr
bool)
mixxx/src/controllers/midi/midimessage.h
Line 183 in 8f64790
inline constexpr bool always_false_v = false; |
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.
(comment race condition : )
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.
Potentially just putting a single definition in a single header and then using that across both files could be enough
What place would you pick?
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.
Well it would need to be accessible in both places and should not assume many dependencies, so I'd just put it in a new src/util/
header for now.
Well, currently both "QLatin1String" and |
We need to make sure that the QString creation is always delayed until the developer flag is checked to not malloc memory or issue any other locking system call during the normal run. |
Yes, I'm aware. The only way that could happen is if the caller explicitly constructs a the string the wrong way for example by |
Ah, QStringLiteral. This way we get around the UTF-16 conversion. Maybe we create our own macro that tuns existing calls into a QStringLiteral call? This way we can add a private function that accepts "const QString&" only. |
`always_false_v<T>` already exists in midimessage.h, move to new /util/always_false_v.h
Why would I need to check that? I'd need to do that at compile time for it to be useful.
Something like this? #define BENCH(key, ...) \
ScopedTimer(QStringLiteral(key), __VA_ARGS__) That would allow usage like: ScopedTimer t{BENCH("key")}; |
tbh, I'd prefer to avoid the macro and instead construct the literal directly: // assumes previous `using namespace Qt::StringLiterals`
ScopedTimer t(u"key"_s); I think the ergonomics are decent enough. The stringliteral is also constructed at compile time once you enable |
With the proposed
To avoid that users are starting to compose the QString outside the function. This would be DEBUG_ASSERT at runtime. One option would be to hide the Like
Which has the benefit the we need to change nothing in the calling code.
I don't see a way around it, because QStringLiteral is also a macro. |
Infamous "Close with comment"? |
Yes, just as it did previously. but in the latest version, the
but
Not that I would know. Sure we can assert at runtime, but should that be after or before the devmode check. Before it would reduce in overhead, after it would have a good chance to be missed without compiling with |
My idea was to put it after the developer mode check. If they introduce the timer, I am sure they will also use it. Does the |
yes. but only after the check. It probably wouldn't with
We can achieve the same if not more without a macro. Can you restate your exact requirements? I'm sure I can find a macro-less solution. |
OK here is the list:
|
This is already the case.
I assume with "fix" you mean "avoid"? I don't think thats really feasible. The only API that could do that would either be
Not entirely sure how to do that reliably, but I can add a heuristic static_assert that tries to verify that the char type is 16-bit wide. Wdyt? |
whoops, yeah, forgot |
Yeah, fortunately not only optimized away but guaranteed to not be called because otherwise trying to call |
once this lgty I'll rebase out the unnecessary intermediate commits and squash the fixups |
Now I get this error message. Unfortunately it is like a wall of text not pointing directly to the point of the error.
Do you really think this text is easier to understand than just the one demonstrated above: "use of deleted function" with the faulty line of code shown? |
// Allows the timer to contain a format string which is only assembled | ||
// when we're not in `--developer` mode. | ||
/// @param compute Flags to use for the Stat::ComputeFlags (can be omitted) | ||
/// @param key The format string for the key |
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.
/// @param key The format string for the key | |
/// @param key The format string as QStringLiteral to identify the timer |
// key is explicitly `auto` to allow passing non-QString types such as `const char*` | ||
// its conversion is delayed until after we've checked if we're in developer mode | ||
auto assembledKey = QString(std::forward<T>(key)); |
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.
This is obsolete now, we don't want something else that a QString generated form a QStringLiteral.
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 really because we need a local copy we can assign the result of key.arg(...)
to.
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.
Performance wise, we can rely on CoW here IMO.
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.
Moreover, since we're taking an universal reference T&&
the type will be QString&&
anyways, resulting simply in a move which should be even cheaper.
When one uses the mixxx/src/analyzer/analyzerebur128.cpp Line 52 in 8f64790
|
I'd say so yes because the solution with the deleted overload just says "use of deleted function" with no clear guidance on how to solve the issue. In the static_assert text we can provide an explanation and guidance on how to fix it. Yeah its a little more to read because the error is not on the first line for some reason, but IMO its still easy enough to spot where the error is when you look for the "error: [...]" line.
Ah, I forgot about that PR. the changes in |
I would prefer the deleted function with an explaining source code comment, but I will not insists. |
"string type likely not UTF-16, please pass QStringLiteral by " | ||
"means of u\"key text\"_s"); |
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.
"string type likely not UTF-16, please pass QStringLiteral by " | |
"means of u\"key text\"_s"); | |
"Passing char[] is not supported. Wrap it via QStringLiteral() " | |
"to avoid runtime UTF-16 conversion."); |
…pedtimer-main Refactor/shrink modernize scopedtimer (Alternative to #13236)
Superseeded by #13258 |
first commit just modernizes it a bit, and doesn't touch the API, the second commit goes a step further and leverages
QString::arg(Args&&...)
to provide arbitrary formastring support. let me know what you think.