Skip to content

Conversation

@AW1534
Copy link
Member

@AW1534 AW1534 commented Jun 25, 2024

Before:
Screenshot from 2024-06-25 16-48-37

After:
Screenshot from 2024-06-25 16-59-08

Not much to say here.

Copy link
Contributor

@Rossmaxx Rossmaxx left a comment

Choose a reason for hiding this comment

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

Great.

@Rossmaxx
Copy link
Contributor

#6549 tried to fix the same leak but that got into merge conflicts because we removed our inbuilt memory management in favour of the STL mechanisms.

@Rossmaxx Rossmaxx requested review from Veratil and sakertooth June 25, 2024 16:14
@messmerd
Copy link
Member

I think we should spend a little more time and refactor the NotePlayHandleManager class. Maybe use std::vector for storage.

@sakertooth
Copy link
Contributor

I think we should spend a little more time and refactor the NotePlayHandleManager class. Maybe use std::vector for storage.

I would like for us to move to a better design. NotePlayHandleManager is poorly implemented (IMO), and it would probably be best to move to something better altogether, rather than to try and fix it up.

The idea of having the handles be allocated on the heap as the heart of our audio processing is flawed on a fundamental level. I was thinking of designing this in such a way that the handles are pre allocated and stored in sample clips, a note, instruments, etc, and the audio processing would use those handles directly somehow.

This would require a large refactoring however, so it might be worthwhile to just create a realtime safe memory pool that can be used with allocations on an audio thread. That has its own problems (e.g., what should the optimal size be for this pool?), but since we have some kind of memory pool as of right now, making a better one is the easiest solution.

Regarding the optimal memory pool size, it seems like we have some kind of limit already for the maximum number of play handles (JOB_QUEUE_SIZE is 8192), so we could just take the handle with the largest size in bytes, multiply it by 8192, maybe do some rounding/added bytes, and that I think would be an "okay" size. This is probably not the best way of doing it, but it could work well enough.

@AW1534
Copy link
Member Author

AW1534 commented Jun 30, 2024

I would like for us to move to a better design. NotePlayHandleManager is poorly implemented (IMO), and it would probably be best to move to something better altogether, rather than to try and fix it up.

This is way out of my skill level, I don't even want to attempt it lmfao

@Rossmaxx
Copy link
Contributor

Rossmaxx commented Jul 1, 2024

@sakertooth @messmerd I do agree we must rework NotePlayHandleManager but can we get done with this one leak first. @AW1534 is a new contributor and I don't feel they will be comfortable with a rework. Let's take the work off their shoulders and do the rework at #7338 , that said, I think merging this would be a good idea for till the rework lands in master.

@sakertooth
Copy link
Contributor

sakertooth commented Jul 1, 2024

@sakertooth @messmerd I do agree we must rework NotePlayHandleManager but can we get done with this one leak first. @AW1534 is a new contributor and I don't feel they will be comfortable with a rework. Let's take the work off their shoulders and do the rework at #7338 , that said, I think merging this would be a good idea for till the rework lands in master.

I didn't say anything about @AW1534 needing to do the rework, I just gave general ideas for what we should move towards in the future. As for the PR, we don't need to check if its nullptr or not when deleting.

From cppreference:

In all cases, if ptr is a null pointer, the standard library deallocation functions do nothing.

I think its fine to just add delete s_available[0] (i.e, your std::free(s_available[0]) line without all the if nullptr checks and using delete instead).

Also, this deletion should happen before delete[] s_available since that pointer was assigned into the array (which is already being done, but I just wanted to be clear).

@AW1534
Copy link
Member Author

AW1534 commented Jul 1, 2024

@sakertooth Like this?
image

@sakertooth
Copy link
Contributor

Much better, but I think the comments aren't need here IMO. We should get in the habit of using these judiciously. s_available = nullptr; might not be needed too, but since this isn't a destructor, I think it's fine to keep.

@sakertooth sakertooth merged commit 0bc911e into LMMS:master Jul 1, 2024
sakertooth added a commit that referenced this pull request Jul 1, 2024
sakertooth added a commit that referenced this pull request Jul 1, 2024
@DomClark
Copy link
Member

DomClark commented Jul 2, 2024

The idea of having the handles be allocated on the heap as the heart of our audio processing is flawed on a fundamental level. I was thinking of designing this in such a way that the handles are pre allocated and stored in sample clips, a note, instruments, etc, and the audio processing would use those handles directly somehow.

This sounds more complex than maintaining a pool. What about arpeggios and note stacking? The chord and range of those can be automated, so we'd either need to track the automation to know the maximum value of each, or allocate enough handles per note to cover all possible settings (which would be quite a large number). And once we eventually support third-party MIDI effects, this task becomes effectively impossible.

Maybe use std::vector for storage.

This works if we want a fixed-size pool, but not so well for a dynamic one - pointers to handles will be invalidated when the vector is resized.

@sakertooth
Copy link
Contributor

This sounds more complex than maintaining a pool. What about arpeggios and note stacking? The chord and range of those can be automated, so we'd either need to track the automation to know the maximum value of each, or allocate enough handles per note to cover all possible settings (which would be quite a large number). And once we eventually support third-party MIDI effects, this task becomes effectively impossible.

Fair, but it still raises the question as to if allocating play handles as how audio is being processed is a good design. The allocations of course can be made real-time safe through a memory pool, so it's not much of a problem really, but I thought of other designs such as one based on pre-allocated audio nodes and a dependency graph and wondered if that'll make things easier/better (this would require a huge refactor though, so if it isn't necessarily a better design, then there's no point).

@messmerd
Copy link
Member

messmerd commented Jul 3, 2024

I believe the data structure we'd want would be a "hive" or "colony" like the one proposed for C++26.

It's an unordered container which ensures stable memory addresses for elements in each allocated block. Each element has a boolean to mark whether that element is active or empty/erased. When iterating through the container, empty/erased elements are skipped. When a block is full of active elements, another block is allocated the next time an element is inserted and the old block points to the new block. Since there's no reallocation, no pointers or references are invalidated.

I think a hive could be approximated with this underlying data structure:

std::forward_list<std::vector<std::optional<T>>>

@sakertooth
Copy link
Contributor

I was thinking more along the lines of using std::pmr::* and making it a per-thread memory pool so the pool can be in real-time.

@DomClark
Copy link
Member

DomClark commented Jul 3, 2024

I thought of other designs such as one based on pre-allocated audio nodes and a dependency graph and wondered if that'll make things easier/better

That's certainly a good idea, but it's orthogonal to the issue of how we allocate memory to track per-note data. The number of notes can't be predicted in advance, as I explained above, so while we can pre-allocate memory for effects and per-instrument data, and arrange these in a dependency graph, we still need to handle notes differently. Using entire instruments, rather than notes, as the job unit for the mixer and giving each instrument a maximum polyphony would work, but that's just a fixed memory pool by another name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants