Skip to content
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 timers #11807

Merged
merged 21 commits into from Aug 20, 2023
Merged

Refactor timers #11807

merged 21 commits into from Aug 20, 2023

Conversation

Swiftb0y
Copy link
Member

Initially wanted to investigate something performance-related but then got distracted by the questionable state of util/performancetimer.h and util/timer.h so I decided to clean it up a little.

src/util/battery/battery.cpp Outdated Show resolved Hide resolved
Comment on lines -45 to -47
// This is a fork of QPerformanceTimer just without the Q prefix
// To fix interface changes issues in different QT versions
// Added restart() function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you checked if this class is still necessary? Maybe we can use plain QPerformanceTimer nowadays.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QPerformanceTimer doesn't exist anymore as far as I can tell. Closest thing would be QElapsedTimer. Didn't investigate whether that satisfies the same constraints. I will check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QPerformanceTimer was forked from QElapsedTimer and later reintegrated:
qt/qt@8896651
I think we can adopt that commit to our code and drop our PerformanceTimer entirely.
I recommend that over the std::chrono::steady_clock solution because it is only weak defined by the C++ standard and may change by any update or different standard library.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the API differs a little bit because qt has no duration type. PerformanceTimer is also spread over more than 40 classes in our codebase. IMO we should keep PerformanceTimer as a facade over QElapsedTimer that makes use of mixxx::Duration (which we should also replace with std::chrono::duration` IMO but thats even harder than replacing PerformanceTimer verbatim).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a task to our Semantic Type Refactoring project https://github.com/mixxxdj/mixxx/projects/5 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. We should at least think about replacing the mixxx::Duration internals with std::chrono::duration to allow for gradual transition. But mixxx::Duration is already a semantic type, so I don't see a pressing need. The only major advantage std::chrono::duration has is its duration_cast as far as I can tell.

src/util/timer.h Outdated Show resolved Hide resolved
src/util/timer.h Outdated
Comment on lines 80 to 82
if (m_state) {
m_state->cancelled = true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancelled state can be replaced by m_state == std::nullopt;

Suggested change
if (m_state) {
m_state->cancelled = true;
}
m_state.reset();

Because the timers is no longer required and can be destructed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super familiar with C++ destructors but I thought this would still happen automatically for RAII types even when using a user-provided constructor. Or is there a way to call a default-generated destructor within the user-provided destructor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cancelled state can be replaced by m_state == std::nullopt;

That implies the removal of the cancel method, but its only used in one location and thats before a return, so basically useless anyway.

src/util/timer.h Outdated
initialize(QString(key), arg ? QString(arg) : QString(), compute);
}
ScopedTimer(QStringView key, int i, Stat::ComputeFlags compute = kDefaultComputeFlags)
: ScopedTimer(key, QString::number(i), compute) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QString::number(i) constructs a QString without --developer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, now results in a duplicate getDeveloper() call, but I'm pretty sure that gets optimized away.

Comment on lines -45 to -47
// This is a fork of QPerformanceTimer just without the Q prefix
// To fix interface changes issues in different QT versions
// Added restart() function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QPerformanceTimer was forked from QElapsedTimer and later reintegrated:
qt/qt@8896651
I think we can adopt that commit to our code and drop our PerformanceTimer entirely.
I recommend that over the std::chrono::steady_clock solution because it is only weak defined by the C++ standard and may change by any update or different standard library.

src/util/timer.h Outdated
Comment on lines 72 to 77
~ScopedTimer() noexcept {
if (m_state) {
if (!m_state->cancelled) {
m_state->timer.elapsed(true);
}
if (m_maybeTimer) {
m_maybeTimer->elapsed(true);
}
m_maybeTimer.reset();
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reset triggers a -Wmaybe-uninitialized warning locally for me and I'm not sure why...

In destructor ‘QString::~QString()’,
    inlined from ‘Timer::~Timer()’ at /home/swiftb0y/Sourcerepositories/mixxx/src/util/timer.h:20:7,
    inlined from ‘constexpr void std::_Optional_payload_base<_Tp>::_M_destroy() [with _Tp = Timer]’ at /usr/include/c++/13/optional:287:35,
    inlined from ‘constexpr void std::_Optional_payload_base<_Tp>::_M_reset() [with _Tp = Timer]’ at /usr/include/c++/13/optional:318:14,
    inlined from ‘constexpr std::_Optional_payload<_Tp, false, _Copy, _Move>::~_Optional_payload() [with _Tp = Timer; bool _Copy = false; bool _Move = false]’ at /usr/include/c++/13/optional:439:65,
    inlined from ‘constexpr std::_Optional_base<Timer, false, false>::~_Optional_base()’ at /usr/include/c++/13/optional:510:12,
    inlined from ‘constexpr std::optional<Timer>::~optional()’ at /usr/include/c++/13/optional:705:11,
    inlined from ‘ScopedTimer::~ScopedTimer()’ at /home/swiftb0y/Sourcerepositories/mixxx/src/util/timer.h:71:5,
    inlined from ‘virtual void VinylControlXwax::analyzeSamples(CSAMPLE*, size_t)’ at /home/swiftb0y/Sourcerepositories/mixxx/src/vinylcontrol/vinylcontrolxwax.cpp:648:1:
/usr/include/qt5/QtCore/qstring.h:1311:35: warning: ‘*(QString*)((char*)&t + offsetof(ScopedTimer, ScopedTimer::m_maybeTimer.std::optional<Timer>::<unnamed>.std::_Optional_base<Timer, false, false>::<unnamed>)).QString::d’ may be used uninitialized [-Wmaybe-uninitialized]
 1311 | inline QString::~QString() { if (!d->ref.deref()) Data::deallocate(d); }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Uninitialized QString seems to be Timer::m_key. The warning happens after the user destructor in the auto generated code.
It is uninitialized if m_maybeTimer is std::nullopt. But in that case the ~Timer() not be run.

@Swiftb0y
Copy link
Member Author

The QElapsedTimer::clockType() == PerformanceTimer debug assert fails on some platforms... How do we go about that? relax the restrictions and just insist its monotonic?

@JoergAtGithub
Copy link
Member

According to the documentation QElapsedTimer::PerformanceCounter is a Windows implementation:
https://doc.qt.io/qt-5/qelapsedtimer.html#ClockType-enum

src/util/timer.h Outdated
}
m_maybeTimer.reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will happen automatically.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? Thats what I was asking in this comment: #11807 (comment)

Copy link
Member

@daschuer daschuer Aug 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the back trace with the warning #11807 (comment) proves it.
But there seems to be a related issue causing the warnig.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot confirm that warning with valgrind, It seems to be a false positive.

@daschuer
Copy link
Member

QElapsedTimer::clockType() seems to be not a nop() in some cases. Can we move the check to an extra function and call it only once?

In my case "QElapsedTimer::ClockType::MonotonicClock" is returned. QElapsedTimer::ClockType::PerformanceCounter" is nerver returned.

@JoergAtGithub
Copy link
Member

Checking clock time make not much sense. The documentation says: "QElapsedTimer will always use the same clock type in a particular machine, so this value will not change". It just tells you which implementation is used on the particular system (result will differ on operating system).
What makes sense to check in an assertion is https://doc.qt.io/qt-5/qelapsedtimer.html#isMonotonic Also this needs to be checked only once on a particular machine.

@daschuer
Copy link
Member

I agree. Since Qt uses the timer internternally for the same propose we, may rely on Qt that they will use the optimal timer.

Since system calls are involve, a subject to change with OS versions, we may even consider to check the monotonic nature it in release builds. So the check is in place for every "particular machine".

@JoergAtGithub
Copy link
Member

On any modern desktop computer with any OS officially supported by Mixxx, you will get a monotonic clock.
I guess thiss is more relevant for the embedded and mobile platforms, which Qt also supports.

Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a final nit pick and than LGTM

Comment on lines 14 to 15
// TODO: turn this into a static_assert and inline this method
// once Qt enables it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will probably never be a static assertion, because it depends on the OS executing Mixxx.

Suggested change
// TODO: turn this into a static_assert and inline this method
// once Qt enables it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but the OS / target is usually known at compile time anyways. That's why the stdlib can get a away with a constexpr static is_steady. https://en.cppreference.com/w/cpp/chrono/system_clock

@Swiftb0y
Copy link
Member Author

I'm unsure whether the clock is monotonic in all cases. I experienced some test failures. https://github.com/Swiftb0y/mixxx/actions/runs/5913234283/job/16037422961

Copy link
Member

@daschuer daschuer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you.

@daschuer daschuer merged commit 1c4096e into mixxxdj:main Aug 20, 2023
8 of 10 checks passed
@Swiftb0y
Copy link
Member Author

you just merged dozens of fixup commits.... how do we proceed?

@uklotzde
Copy link
Contributor

you just merged dozens of fixup commits.... how do we proceed?

@Swiftb0y Switch the repo to "Allow squash merging" exclusively for preventing this in the future? Avoids tons of low-value commits in the repo and ensures that git bisect works for every commit.

I have seen this strategy in a customer project recently and adopted it immediately after experiencing the benefits. Saves you from a lot of headaches.

JoergAtGithub added a commit that referenced this pull request Sep 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants