Skip to content

Conversation

@miiizen
Copy link
Contributor

@miiizen miiizen commented Apr 7, 2025

This PR improves the performance of tick2measure, tick2measureMM and tick2measureBase by maintaining an std::multimap of ticks and MeasureBase* alongside the existing linked list. multimap is much faster to query than the existing linked list, but adds some slowdown and overhead on adding and removing measures from the list. Firstly, the measure must be added to the map, then the map must be regenerated in order to update each measure's tick value. (One possible improvement would be to regenerate the map only from the start tick of layout)

Screenshot 2025-04-03 at 11 21 52 1
std::multimap is implemented as a red-black tree

This slowdown is acceptable, as we are querying this list far more often than we are adding measures. In the Beethoven 9 example, when opening the project MeasureBaseList::add is called 2,662 times, whereas we call tick2measure & tick2measureMM a total of 281,246 times.


Profiling

Screenshot 2025-04-07 at 14 29 33

Any MeasureBaseList or utility measure access function not in the table had a small enough impact to not be picked up by the profiler before or after the PR. %change is the change in sum time. A positive %change is a speedup, negative is a slowdown.

@ghost ghost added this to MuseScore Studio 4.6 Apr 8, 2025
@ghost ghost moved this to In Progress in MuseScore Studio 4.6 Apr 8, 2025
@miiizen miiizen force-pushed the measureListImprovements branch from 68b2396 to f965a66 Compare April 9, 2025 08:57
@miiizen miiizen force-pushed the measureListImprovements branch from b78fdc9 to 596d7ce Compare April 10, 2025 07:49
}

auto mbIt = findMeasureBaseIterator(m);
if (mbIt != m_tickIndex.end()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like it should be an assertion? Or is there a legitimate case where it may happen? (Same in a bunch of other places below)

}
m_first = e;
m_first = m;
m_tickIndex.emplace(std::make_pair(m->tick().ticks(), m));
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks a bit weird to me. With push_back, that's fine. But with push_front, this means that all measures in this score will potentially change tick, so I would have expected that we need to reconstruct the multimap here? Instead, by just emplacing the new measure at the start (at least as far as I understand) we are putting the multimap into a state that we know to be wrong.

I think that the reason why we don't update the multimap here is that at this stage the tick of the measure may not yet be known? But then why inserting it?


void MeasureBaseList::updateTickIndex()
{
std::multimap<int, MeasureBase*> indexCopy = m_tickIndex;
Copy link
Contributor

@mike-spa mike-spa Apr 10, 2025

Choose a reason for hiding this comment

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

This pairs with my comment above. At this point this structure is assumed have wrong ticks. But if it has wrong ticks, then it's not functioning as a map, it's just holding the list of measures... but you already have the list of measures, so why bother working on this at all (and spend the time to make a copy)? Unless there's something that I'm not understanding, I think you could entirely avoid updating m_tickIndex during all the MeasureList operations, and when you get here you simply clear it and update it by looping on MeasureList.

This comment was marked as outdated.

return nullptr;
}

std::vector<MeasureBase*> MeasureBaseList::measureBasesAtTick(int tick) const
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we have already something for this? Like muse::values or something similar that returns all the values for a particular key in a multimap?

miiizen added 2 commits April 11, 2025 10:38
This function is primarily to be used when reading or importing scores. It updates the map as we can guarantee the ticks of the other measures won't change.
@miiizen miiizen force-pushed the measureListImprovements branch from efa559a to 315968d Compare April 11, 2025 10:35
@miiizen miiizen added the performance Performance issues (e.g. high CPU usage) label Apr 15, 2025
@mike-spa mike-spa merged commit 887caa6 into musescore:master May 6, 2025
11 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in MuseScore Studio 4.6 May 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance issues (e.g. high CPU usage)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants