You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Qt中设置Pixmap时执行时长不符合预期的原因咨询

疑问:直接传入QImage引用调用setPixmap,总耗时为何和先复制再传入几乎相同?

我遇到了一个不符合预期的性能问题,想向大家请教。

先看最初的代码,其中ImageImageViewer类中类型为QImage的成员变量:

void ImageViewer::setImage(const QImage &newImage) {
    Image = newImage; // 耗时108毫秒
    imageLabel->setPixmap(QPixmap::fromImage(Image)); // 耗时58毫秒
}

因为后续不再需要将newImage赋值给类成员Image,我想着直接使用newImage的引用传入,应该能节省复制的时间,于是修改了代码:

void ImageViewer::setImage(const QImage &newImage) {
    imageLabel->setPixmap(QPixmap::fromImage(newImage)); // 耗时158毫秒
}

结果出乎意料,总耗时居然和之前几乎相同(158ms vs 108+58=166ms)。我到底忽略了什么?

补充说明

  • 计时采用QElapsedTimer,每次测试传入的均为同一张2380x3368尺寸的.jpg图片,多次测量结果稳定,注释中为平均耗时。
  • 我认为图片格式或尺寸并非核心问题,核心疑问是:为何直接传入QImage引用调用setPixmap,比先复制QImage再传入的耗时更长/几乎一致?这不符合逻辑。

嘿,这个问题挺有意思的,我来帮你拆解一下背后的原因!

核心关键点:QImage的隐式共享机制

首先你得明白:Qt的QImage是基于**隐式共享(Copy-on-Write,写时复制)**实现的。你最初代码里的Image = newImage;这一步,并没有真正复制图片的像素数据——它只是复制了一个指向数据块的指针和引用计数,这一步的108毫秒绝对不是像素复制的耗时!那这108毫秒到底是什么?

大概率是newImage本身的“懒加载”收尾或者内存预处理:比如你传入的.jpg图片,它的像素数据可能还处于磁盘缓存或者未对齐的内存区域,赋值给Image时,Qt悄悄做了内存对齐、像素格式预转换或者数据加载到更高效的内存空间的操作,这才是那108毫秒的来源。

为什么直接传引用耗时没减少?

当你直接把const QImage& newImage传入QPixmap::fromImage()时,问题来了:
QPixmap::fromImage()需要将QImage的像素数据转换为适合当前显示设备的格式(比如从RGB888转换为ARGB32,或者做内存对齐),但因为newImageconst引用,函数内部不能修改原对象的数据,所以Qt不得不临时复制一份QImage的完整像素数据来进行格式转换。

而你最初的写法里,Image = newImage;之后,Image是非const的,fromImage()处理时可以直接基于已经做过预优化的Image数据进行转换,不需要额外复制——所以“预处理(108ms)+ 转换(58ms)”的总耗时,和“临时复制+转换(158ms)”的总耗时就几乎一致了。

验证思路

你可以做几个小测试来验证这个猜想:

  • 在第一种写法里,Image = newImage;之后加一行Image = Image.copy();——这会强制触发写时复制,真正复制像素数据,看看这一步的耗时。如果这一步耗时远小于108ms,就说明之前的108ms不是复制的时间,而是预处理开销。
  • 打印newImage.format()Image.format(),对比赋值前后的像素格式是否有变化,判断是否存在隐式的格式转换。
  • 尝试给QPixmap::fromImage()传入Qt::NoFormatConversion标志,看看两种写法的耗时差异是否消失——如果消失,就说明核心问题确实是格式转换的额外开销。

内容的提问来源于Stack Exchange,提问作者blah blah

火山引擎 最新活动