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

Periodically free memory from unused images #3915

Merged
merged 18 commits into from Sep 4, 2022

Conversation

dnsge
Copy link
Contributor

@dnsge dnsge commented Aug 10, 2022

  • CHANGELOG.md was updated, if applicable

Description

Attempts to reduce memory usage due to images.

  1. Reduces copying of frame data (though this doesn't actually increase memory usage in the long term)
  2. Periodically discards frame data of Images that haven't recently been painted

Images store the last time they were accessed via Image::pixmapOrLoad. Every 1 minute, Chatterino will check every Image instance and check if the last accessed time was more than 10 minutes ago. If so, it deletes the frame data and marks the image as needing to be loaded again.

The intention is that Chatterino instances with many open splits but with only a few being used at any moment will no longer hold Image data for emotes that aren't used in the active splits. This has no effect on emotes shared between splits.

These duration values are specified here: https://github.com/dnsge/chatterino2/blob/b35071aee8511aa87155e70184254aec51b14013/src/messages/Image.cpp#L28-L31

Addresses #3062

Memory Usage Graph

The following graph's lines indicate:

  1. Opening the emote popup for a single channel
  2. Closing the emote popup
  3. The automatic expired image frame data cleanup occurring (at shorter expiration for testing)

Screen Shot 2022-08-09 at 11 27 11 PM

Utilized memory goes from 154.74 MB before cleanup to 88.57 MB after.

Periodically, we check for images with frame data that has not been recently
painted. Upon finding an "expired" image that can be reloaded later (e.g. from
a URL), we delete the frame data and mark it as unloaded.
@dnsge dnsge changed the title Reduce frame copying and preallocate vectors Attempt to reduce memory usage from Images Aug 10, 2022
@pajlada
Copy link
Member

pajlada commented Aug 13, 2022

No big feedback yet, but:
The allImages field contains every single emote and emoji, which means the iteration we do happens for all ~6k images. Could they be added only when they are actually loaded (i.e. from the Image::load function)? Could they also then be removed from the list again since they would be re-added whenever Image::load is called next?

Images also seem to load as soon as they are created, even if the split isn't visible. Probably not relevant for this PR, but this PR has made that fact very clear.

Overall, this approach seems like a sane approach to take 👍

@dnsge dnsge marked this pull request as ready for review August 14, 2022 00:10
@pajlada pajlada changed the title Attempt to reduce memory usage from Images Periodically free memory from unused images Aug 17, 2022
@pajlada
Copy link
Member

pajlada commented Aug 17, 2022

I want to take another look at the code before I merge this in, but I've been running this branch for 2 days without any crashes/freezes

fourtf
fourtf previously requested changes Aug 27, 2022
Copy link
Member

@fourtf fourtf left a comment

Choose a reason for hiding this comment

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

Looks good to me apart from some small things

src/messages/Image.cpp Outdated Show resolved Hide resolved
src/messages/Image.hpp Outdated Show resolved Hide resolved
src/messages/Image.cpp Show resolved Hide resolved
src/messages/Image.cpp Outdated Show resolved Hide resolved
src/messages/Image.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

@@ -346,8 +373,10 @@ void Image::load() const

if (this->shouldLoad_)
{
const_cast<Image *>(this)->shouldLoad_ = false;
const_cast<Image *>(this)->actuallyLoad();
Image *this2 = const_cast<Image *>(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: use auto when initializing with a cast to avoid duplicating the type name [modernize-use-auto]

Suggested change
Image *this2 = const_cast<Image *>(this);
auto *this2 = const_cast<Image *>(this);

@@ -346,8 +373,10 @@ void Image::load() const

if (this->shouldLoad_)
{
const_cast<Image *>(this)->shouldLoad_ = false;
const_cast<Image *>(this)->actuallyLoad();
Image *this2 = const_cast<Image *>(this);
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: do not use const_cast [cppcoreguidelines-pro-type-const-cast]

        Image *this2 = const_cast<Image *>(this);
                       ^

return instance;
}

void ImageExpirationPool::addImagePtr(ImagePtr imgPtr)
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: parameter 'imgPtr' is unused [misc-unused-parameters]

Suggested change
void ImageExpirationPool::addImagePtr(ImagePtr imgPtr)
void ImageExpirationPool::addImagePtr(ImagePtr /*imgPtr*/)

@@ -3,11 +3,14 @@
#include <QPixmap>
#include <QString>
#include <QThread>
#include <QTimer>
#include <QVector>
#include <atomic>
#include <boost/noncopyable.hpp>
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: 'boost/noncopyable.hpp' file not found [clang-diagnostic-error]

#include <boost/noncopyable.hpp>
         ^

*/
void freeOld();

private:
Copy link
Contributor

Choose a reason for hiding this comment

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

warning: redundant access specifier has the same accessibility as the previous access specifier [readability-redundant-access-specifiers]

Suggested change
private:

src/messages/Image.hpp:105: previously declared here

private:
^

@pajlada
Copy link
Member

pajlada commented Sep 3, 2022

Ignore clang-tidy for now 🗡️

There seems to be a "leak" in the images debugcount - you can reproduce this by opening Chatterino, press F10 to open the Debug popup, then type an emote in a split. It will increase the images counter by 1.
Type the same emote again, it will increase the images counter by 1 again. Every time the emote is written, the count is increased.

@dnsge
Copy link
Contributor Author

dnsge commented Sep 3, 2022

There seems to be a "leak" in the images debugcount

I found the issue, it wasn't actually due to incorrectly keeping track of the number of images. Instead, every message would have the reply button image pixmap created and appended, regardless of whether the setting is enabled (this is correct behavior).

https://github.com/dnsge/chatterino2/blob/20c974fdab215c54297cc5a47ea7d1e03c6065be/src/providers/twitch/TwitchMessageBuilder.cpp#L346-L350

I added caching to creating Image instances from resource pixmaps so that we reuse the same Image for the same pixmap and scale.

@dnsge
Copy link
Contributor Author

dnsge commented Sep 3, 2022

By the way, commit 2dd96d2 adds the usage of <boost/functional/hash.hpp>. I have no clue about vcpkg, but I remember that bringing in circular_buffer required adding some vcpkg entry (#3941). It would be good if someone could check if this new include requires an entry too.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

clang-tidy made some suggestions

src/messages/Image.cpp Show resolved Hide resolved
@pajlada pajlada dismissed fourtf’s stale review September 4, 2022 10:06

Suggestions taken care of

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.

None yet

4 participants