Qt6 QLabel / QPixmap内存占用过多



我有一个应用程序,我需要使用QPixmap在许多qlabel上放置背景图像。这对我来说似乎使用了过多的内存,这使我怀疑这是由于内存泄漏。从上面的注释中,我了解到图像在内存中可能比在文件中占用更多的空间,但是我很惊讶差异会如此之大。

我在MacOS Ventura上使用PyQt6。

这里是一个简单的片段代码来复制。图像文件大小为1 MB,这个简单的应用程序使用超过4 GB的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()

我发现的唯一相关的问题是这个,但我不能从中得到任何有用的东西:

Qt5 QLabel + QPixmap。内存泄漏?

在我的应用程序的背景是,我正在与Matplotlib,我想保持在许多不同的窗口/选项卡绘图。在使用许多Matplotlib图时,我遇到了内存使用过度的问题,因此我正在考虑使用单个图来生成每个图作为图像,并将其放在单独的QLabel上。

这个问题与图像文件大小无关,而是它的内容:图像,就像视频或音频一样,可以使用压缩(有损或不有损),但它们的内存使用将是相同的。

粗略地说,一个图像通常占用与其原始图像数据一样多的内存;简单的公式是:
memorySizeInBytes = (image.width * image.height * image.depth) / 8

幸运的是,Qt非常聪明,它的图像对象(QImage和QPixmap)被优化为存储实际上绘制内容的数据。

同样,Qt为像素图使用一个内部缓存(参见QPixmapCache),它只根据图像的可能成本设置其数量,使用类似于上面的公式。虽然不是非常理想,但这是可以理解的:即使图像的存储的内存大小可能远远小于其实际使用量,该像素图也可能被修改并增加其内存消耗。

上述缓存的默认大小通常设置为10240kB,这意味着任何图像的面积和深度超过当前缓存大小的10240kB限制将被缓存。

上面提到了另一个重要的问题:当缓存"image"一个对象可能看起来是一个很好的性能"解决方案",但考虑到上面所写的内容,它会产生一个严重的问题:每个将超过当前使用的像素图缓存大小的新图像将被忽略,并且该图像的每个实例将被视为一个新的、唯一的图像数据对象。
对于一个标准的QPixmap,当它的面积和位深度超过10240kB限制时,它将不被缓存,并且不能"重新加载"。即使它之前已经被加载过。虽然令人讨厌,但这种行为对于较小(通常更常用/绘制)的像素图(包括可能更频繁绘制的图标)是一种安全措施。

请注意,创建像素图作为复杂对象的缓存并不会自动使其成为优化:虽然栅格数据读取速度更快(只要数据已经被读取),但它很容易降低性能,特别是与实际上需要更小内存分配的动态绘制相比。

因此,您必须尝试找出渲染图像真实的大小和深度是否超出默认限制,并最终考虑可能的替代方案。

有一些可能性,例如:

  • 只是在paint事件处理程序中渲染像素图(通常是小部件的paintEvent());虽然不如QLabel上显示的基本QPixmap最优,但它肯定会改善内存管理;
  • 使用QPicture作为一种缓存形式,但直接在其上绘制小部件的结果(而不是作为光栅化渲染);这是pyqtgraph通常绘制其项的方式,这可能不是速度性能的最佳选择,但对于内存管理来说相当好;

最后,请注意全局QPixmapCache的默认缓存限制可以更改,并且当您确定不再使用它们时,还可以清除缓存的像素图。因此,您可以将setCacheLimit()使用到合理的kB大小(可能基于系统信息),使用insert()存储每个像素图,同时存储其密钥,并最终在图形要被销毁时使用remove()存储其密钥。

最新更新