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

CoverArtCache refactoring + Fix scrolling lag after updating Mixxx #12009

Merged
merged 14 commits into from Nov 14, 2023

Conversation

daschuer
Copy link
Member

This is a prerequisite to fix #11131

It consolidates a grown interface with a hard to use enum class Loading that was passed in most cases redundant to the different inputs to a function. To use it right you needed to pass the correct loading value with the correct set of parameters.

This has been now replaces with single purpose functions which should be self-explainable.

static void requestCover() 
static void requestTrackCover()
static QPixmap getCachedCover();
static  void requestUncachedCover();

The original universal interface has been mad private.

To make this work, a refactoring of the migration from legacy hash was required.
This is now always done in the worker thread, to get around the hard to understand extra information of requestedCacheKey and coverInfoUpdated in the CoverFound() signal. This should improve the library scrolling after updating from a legacy Mixxx version.

I have tried hard to do this PR in single testable commits, to allow a good review.

I have two follow up in my stash:

Comment on lines 855 to 856
static_assert(sizeof(TrackId) <= sizeof(void*)); // no locking required
return m_record.getId();
Copy link
Member

Choose a reason for hiding this comment

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

why? are you sure the same assumption is true for ARM?

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. There is theoretical the issue for a possible odd aliment we may assert as well, but you need to try hard to trick the compiler to achieve that.

Copy link
Member Author

Choose a reason for hiding this comment

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

In this case, a alignas(int) int m_value; is the best solution without loosing pipelining because of the memory barriers in an a std::atomc_int. I have also remove the generic value_type which gives us the guarantee that we have always int and allows fully in-lining of the DbId class.

Copy link
Member

Choose a reason for hiding this comment

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

I'm still not comfortable with bugprone optimizations like this without evidence that they are needed. I also doubt that a properly used std::atomic would be any slower than this.

Copy link
Member Author

@daschuer daschuer Sep 24, 2023

Choose a reason for hiding this comment

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

I was originally started to implement a std::atomic based solution here. With a 'memory_order_relaxed' you can also achieve that the processor pipeline is not cleared. But than I have noticed that the load a and store calls are (probably intentional) not inline, they are real calls. std::atomic are used for atomic operations like read and increments and memory briers, all this not needed here. We would need a lot of boilerplate code to achieve something with std::atomic that is reliable out of the box.

Here we really just want to read a single integer as a whole that is in addition almost constant. This is already a thing that cannot be interrupted = atomic. The compiler only uses unaligned integers when it is forced to. See also https://stackoverflow.com/a/70967180 and https://learn.microsoft.com/en-us/cpp/cpp/alignment-cpp-declarations?view=msvc-170

I have found a one header implementation of an atomic int that also uses aligneas(int): https://github.com/wingfiring/xirang/blob/eda84647cca476a29e8d785b8fd8ceda78bb952f/trunk/xirang/backward/atomic.h
so we are in a good companion.

But anyway now with alingeas(int) we have sorted out this case as well. So why do you still think this is bugprone. What is the scenario that may fail?

Copy link
Member

Choose a reason for hiding this comment

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

I was originally started to implement a std::atomic based solution here. With a 'memory_order_relaxed' you can also achieve that the processor pipeline is not cleared. But than I have noticed that the load a and store calls are (probably intentional) not inline, they are real calls.

Are you sure, they're fully inlined on -O1 on all compilers. https://compiler-explorer.com/z/n3MbKqr3q
There is no reason why they couldn't be inlined unless atomics where using dynamic dispatch (which they absolutely aren't, considering how performance-sensitive they are).

We would need a lot of boilerplate code to achieve something with std::atomic that is reliable out of the box.

Why would that be? Just make the member atomic and then .load() and .store() to it.

This is already a thing that cannot be interrupted = atomic.

I'm worried the assumption might break, silently resulting in a race-condition. I'd rather have the fallback to slower behavior, than undefined behavior.

I have found a one header implementation of an atomic int that also uses aligneas(int): https://github.com/wingfiring/xirang/blob/eda84647cca476a29e8d785b8fd8ceda78bb952f/trunk/xirang/backward/atomic.h
so we are in a good companion.

That project has been dead for 6 years, I doubt they did any work to validate whether that actually works on every platform they support, which are probably even fewer platform than we support.

But anyway now with alingeas(int) we have sorted out this case as well. So why do you still think this is bugprone. What is the scenario that may fail?

Because on paper alignas just guarantees alignment not atomicity. The conclusion you've drawn that aligned access means atomic access is implicit but not strong enough to not silently break. We either need to be able to assert this (which we can't afaik), or have it documented rigidly for all platforms we support (and might support, I don't want mixxx be unusable on RISC-V because we relied on a bunch of ARM and x86 specific behavior).

Copy link
Member

Choose a reason for hiding this comment

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

If you want to convince me that alignas guarantees atomicity, just link me the section in the standard (or even cppreference would be fine) that supports your assumption. But I don't think that assumption is true in all cases and thus its also not defined in the standard. As such, we can't use it blindly.
If its only defined for some CPU architectures, fine, that works too, but then we need to assert that we only run on those CPU architectures, etc. Do you see the pattern? just linking to some code and stackoverflow articles describing what might happen on some systems doesn't let us draw conclusions over all systems!

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok I have committed the std::atomic_int version.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you. I will try to take a look, soon-ish.

@daschuer
Copy link
Member Author

daschuer commented Sep 22, 2023

Is s class always integer alligned? Probably not. I think we need to assert that.

@github-actions github-actions bot added the build label Sep 23, 2023
@JoergAtGithub
Copy link
Member

This doesn't compile here:
src\util\db\dbid.h(27): error C3615: constexpr function 'DbId::DbId' cannot result in a constant expression

@JoergAtGithub
Copy link
Member

Now I can build locally. Everything works as before, except the Coverart-Tooltip in the library, which does no longer appear. I guess this is intended for this intermediate step.

@daschuer
Copy link
Member Author

It should appear after the second try. The first try fills the cache. This should however the same in 2.4.
Can you confirm?

@JoergAtGithub
Copy link
Member

No, this was the behavior before. With this PR a tooltip for the CoverArt does not appear at all.

@daschuer
Copy link
Member Author

Oh, there was missing ! introduced in the last minute. Now everything should be like before, just faster.

@uklotzde
Copy link
Contributor

uklotzde commented Sep 25, 2023

I need to leave a comment here.

DbId is supposed to be a simple, type-safe newtype wrapper around a native type. It is and should not be aware of multiple theads. It is supposed to be used in single-threaded contexts only. A simple POD. All database access is single-threaded.

Now it has become an opinionated AtomicDbId that fits a single use case. But everyone else using this generic type has to pay the price.

@@ -22,35 +22,36 @@
// not add any additional state (= member variables). Inheritance is
// only needed for type-safety.
class DbId {
protected:
public:
// Alias for the corresponding native type. It keeps the
Copy link
Contributor

@uklotzde uklotzde Sep 25, 2023

Choose a reason for hiding this comment

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

The comment explained this intended purpose. But it has been and deleted.

@JoergAtGithub
Copy link
Member

I tested this, everything works now as before, but the CoverArt loading while scrolling through the library feels to be faster now.

Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

blocking this PR for now as I don't agree with the changes. I'll elaborate asap.

@daschuer
Copy link
Member Author

I wanted to use the TrackId to check if it is a temporary track or not. However at that point the temporary track used for lookup the metadata is out of scope. So it means the original demand of optimize the locking is not there.

This means we can revert the introduction of an atomic here.

.. in favour of the already existing toString() and toVariant() interface. This hides the internal int value entirely.
@daschuer
Copy link
Member Author

@Swiftb0y Ok, the int is gone. Please have a look.

Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

can't comment on coverartcache, coverartdelegate and basetracktablemodel, but my concerns over DbIb have been mostly resolved.

src/util/db/dbid.h Outdated Show resolved Hide resolved
@Swiftb0y Swiftb0y dismissed their stale review November 5, 2023 11:43

can't comment on coverartcache, coverartdelegate and basetracktablemodel as I'm completely unfamiliar with the code in question. Someone else please take over.

@daschuer
Copy link
Member Author

daschuer commented Nov 5, 2023

The clazy failure is already fixed in 2.4.
Shall I rebase it?

@JoergAtGithub
Copy link
Member

@daschuer Please fix the build failures - by rebase, or whatever is needed

@daschuer
Copy link
Member Author

All green again.

@Swiftb0y
Copy link
Member

@JoergAtGithub if you're willing to sign off on the coverartcache, coverartdelegate and basetracktablemodel changes, feel free to merge this PR.

Copy link
Member

@JoergAtGithub JoergAtGithub left a comment

Choose a reason for hiding this comment

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

I reviewed the code and found only some minor nitpicks. I also build it on Windows 11 and it works as expected.

src/library/coverartcache.cpp Outdated Show resolved Hide resolved
src/library/coverartcache.cpp Outdated Show resolved Hide resolved
src/library/coverart.h Show resolved Hide resolved
src/library/coverart.h Show resolved Hide resolved
src/library/basetracktablemodel.cpp Outdated Show resolved Hide resolved
Co-authored-by: JoergAtGithub <64457745+JoergAtGithub@users.noreply.github.com>
Copy link
Member

@JoergAtGithub JoergAtGithub left a comment

Choose a reason for hiding this comment

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

LGTM! Waiting for CI

@daschuer
Copy link
Member Author

All green again

@JoergAtGithub
Copy link
Member

What did you push here?

@daschuer
Copy link
Member Author

I have amended the last commit, because there was a code style issue.

@daschuer
Copy link
Member Author

daschuer commented Nov 13, 2023

Oh no, I see. I have messed with the follow up PTRs.
Need to revert that tonight for a clean history.

@daschuer
Copy link
Member Author

Restored the original state from reflog.

@JoergAtGithub JoergAtGithub merged commit a09236f into mixxxdj:2.4 Nov 14, 2023
23 checks passed
@JoergAtGithub
Copy link
Member

Thank you!

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

4 participants