Home > Net >  Qt6 QLabel / QPixmap excessive memory usage
Qt6 QLabel / QPixmap excessive memory usage

Time:12-25

I have an application where I need to put background images on many QLabels using QPixmap. This uses what to me seems to be an excessive amount of memory, which made me suspect that it's due to a memory leak. From the comments I understand that an image in memory may occupy a lot more space than on file but I was surprised that the difference would be so big.

I'm using PyQt6 on MacOS Ventura.

Here is a simple piece code to reproduce. The image file size is 1 MB and this simple application uses more than 4 GB of RAM.

import sys
from PyQt6.QtGui import QImage, QPixmap
from PyQt6.QtWidgets import QMainWindow,QLabel,QApplication

app = QApplication(sys.argv)
w = QMainWindow()

for i in range(20):

    label = QLabel(w)

    # The file size of the image is 1 MB
    label.setPixmap(QPixmap('image.png'))

w.show()

app.exec()

The only related issue I have found is this but I could not get anything useful from it:

Qt5 QLabel QPixmap. Memory leak?

The background in my application is that I am making plots with Matplotlib that I want to keep in many different windows / tabs. I have experienced problems with excessive memory usage when using many Matplotlib figures and hence I am looking at using one single figure to produce each plot as an image to be put on a separate QLabel.

CodePudding user response:

The issue is not related to the image file size, but its contents.

Roughly speaking, an image normally occupy as much memory as its raw image data; the simple formula is:

memorySizeInBytes = (image.width * image.height * image.depth) / 8

Luckily, Qt is quite smart, and its image objects (QImage and QPixmap) are optimized to store data that actually paint contents.

Also, Qt uses an internal cache for pixmaps that sets its amount only based on the possible cost of the image (using a formula similar to the above). While not extremely optimal, it's understandable: even if the stored memory size of the image is potentially much less than its actual usage, that pixmap might be modified and increase its memory consumption.

The default size for the above cache is normally set to 10240kB, meaning that any image that has an area and depth that exceeds the current cache size more than the 10240kB limit will not be cached.

The above raises another important issue: while caching the "image" of an object might seem a good performance "workaround", it raises a serious problem considering what written above: each new image that will exceed the current used pixmap cache size will be ignored, and each new instance of that image will be considered as a new, unique image data object.
For a standard QPixmap, whenever its area and bit depth exceeds the 10240kB limit, it will not be cached, and cannot be "reloaded" even if it has already been previously loaded. While annoying, this behavior is a safety measure for smaller (and usually more commonly used/drawn) pixmaps, including icons.

Be aware that creating pixmaps as cache of complex objects does not automatically make it an optimization: while raster data is faster to read (as long as the data has been already read), it can easily decrease performance, especially if compared to dynamic painting that actually requires far more smaller memory allocation.

So, you must try to find out if the rendered image real size and depth goes beyond the default limit, and eventually consider possible alternatives.

There are some possibilities, for instance:

  • just render the pixmap in the paint event handler (usually, paintEvent() of the widget); while not as optimal as a basic QPixmap shown on a QLabel, it will certainly improve memory management;
  • use QPicture as a form of cache, but drawing the result of the widget directly on it (and not as a rasterized render); this is how pyqtgraph normally draws its items, which may not be extremely optimal for speed performance, but rather good for memory management;
  • Related