QML应用程序剖析
使用 QML 分析器,您可以找到导致您应用程序中典型性能问题(如速度慢、无响应、用户界面颤抖)的原因。常见原因包括在非常少的帧中执行过多的 JavaScript。所有 JavaScript 都必须在 GUI 线程继续之前返回,如果 GUI 线程没有准备好,帧将会延迟或丢失。
导致类似性能问题的另一个典型原因是创建或更新不可见项,这在 GUI 线程中需要时间。
触发长期运行的 C++ 函数,如绘制方法和信号处理器,也会在 GUI 线程中占用时间,但在 QML 分析器中更难看到,因为它不分析 C++ 代码。要查找 JavaScript 的过度使用,请在动画和场景图事件中检查帧率,寻找间隔,并检查应用程序是否符合预期。JavaScript 类别显示了函数的运行时间,您应尝试将其保持在每帧 16 毫秒以下。
要找到处理不可见项引起的问题,请查找丢失的帧,并检查您是否没有使用过多的短期绑定或每帧更新的信号处理器。您还可以可视化场景图过度绘制以检查场景布局并找到位于屏幕外或其他可见元素下方的用户永远看不到的项目。
如果即使不运行 JavaScript 也丢失了帧,并且在时间线上有大的、无法解释的间隔,请检查您定制的QQuickItem 实现是否正确。您可以使用 Valgrind Callgrind 或其他通用目的的分析器来分析 C++ 代码。
您可以使用 完整的堆栈跟踪 从顶级 QML 或 JavaScript 跟踪到底部的 C++ 以及到内核空间。您可以在Chrome 跟踪格式查看器中查看收集到的数据。
分析收集到的数据
时间线 视图显示 QML 和 JavaScript 执行的图形表示和所有事件记录的简化视图。
时间线上每行(6)描述了一种已记录的QML事件类型。将鼠标悬停在不同行的事件上,可以看到该事件使用的时间和其在源代码中的位置。若只想在事件被选中时显示信息,请关闭查看鼠标悬停事件信息(4)。
大纲(10)总结了数据收集的时段。拖动缩放范围(8),或点击大纲以沿大纲移动画布。您也可以通过选择跳转到前一事件和跳转到下一事件(1)在各事件间切换。
选择显示缩放滑块(2)以打开设置缩放级别的滑块。您还可以拖动缩放手柄(9)。要重置默认缩放级别,右击时间线以打开上下文菜单,然后选择重置缩放。
点击时间尺以在时间线上添加垂直方向线条(5)。
选择事件范围
选择事件范围(7)以查看事件的帧率并与之比较类似事件的帧率。选择选择范围(3)以激活选择工具。然后在时间线上点击以指定事件范围的开始。拖动选择手柄以定义范围的结束。范围的长度表示事件的帧率。
为了测量两个连续事件之间的延迟,请将事件范围放置在第一事件结束和第二事件开始之间。在持续时间字段中显示事件间的延迟(以毫秒为单位)。
要缩放事件范围,请双击它。
要缩小当前范围(在时间线、统计信息和火焰图视图中),在范围内右击并选择分析当前范围。要返回到完整范围,在上下文菜单中选择分析完整范围。
要删除事件范围,请关闭选择对话框。
理解数据
通常,时间线视图中的事件表示了QML或JavaScript执行的时长。将鼠标悬停在其上以查看详细信息。对于大多数事件,它们包括源代码中的位置、持续时间以及一些相关的源代码部分。
选择事件,将光标移动到代码编辑器中与事件相关联的代码部分。
以下类型的事件在一行或多行上显示在时间线视图中。
事件类别 | 描述 |
---|---|
Pixmap 缓存 | 显示缓存的像素形式的总体 pixmap 数据量。此外,还对每个正在加载的图片显示单独的事件,具体包括其文件名和大小。 |
场景图 | 显示渲染场景图帧时的时间以及执行此操作的各个阶段的附加时间信息。 |
内存使用 | 显示 JavaScript 内存管理器的块分配。通常,内存管理器将保留一块较大的内存并在以后以较小的块分给应用程序。如果应用程序请求超过一定大小的单块内存,内存管理器将单独分配这些内存。这两种操作模式分别显示为不同颜色的事件。第二行显示实际分配的内存的实际使用情况。这是应用程序实际请求的 JavaScript 堆的大小。 |
输入事件 | 显示鼠标和键盘事件。 |
绘画 | 未使用。 |
动画 | 显示正在运行的动画数量以及它们的帧率。渲染线程动画在单独的行中显示。 |
编译 | 显示编译 QML 文件花费的时间。 |
创建中 | 显示创建场景元素花费的时间。元素创建分为两个阶段。第一阶段是创建数据结构,包括子元素。第二阶段表示完成回调。虽然不是所有元素都触发生成回调,但阶段在时间轴中作为单独的事件显示。 |
绑定 | 显示绑定的评估时间和评估时长。 |
处理信号 | 显示处理信号的时间和处理时长。 |
JavaScript | 显示执行绑定和信号处理器背后实际的 JavaScript 代码花费的时间。并列出您可能用于评估绑定或处理信号的 JavaScript 函数。 |
Quick3D | 显示渲染 Qt Quick 3D 帧花费的时间,包括帧准备和同步的计时信息,粒子系统更新时间和粒子更新次数,以及纹理和网格内存分配和内存消耗。 此事件类型从 Qt 6.3 开始可用。 |
分析场景图事件
要了解场景图类别,请阅读更多关于 Qt Quick 场景图工作原理的信息,请参阅 Qt Quick Scene Graph 和 Qt Quick Scene Graph Default Renderer。以下事件在 场景图 类别中报告。并非所有事件都是由所有渲染循环生成的。在 Windows 和基本渲染循环中,所有操作都在同一线程中进行,GUI 线程和渲染线程之间的区别没有意义。
设置环境变量 QSG_RENDER_TIMING,以获取相似但略有不同的事件的应用程序的分析文本输出。以下列出差异。
事件类型 | 线程 | 渲染循环类型 | QSG_RENDER_TIMING 输出的标签 | 描述 |
---|---|---|---|---|
Polish | GUI | Threaded, Basic, Windows | polish | 在使用 QQuickItem::updatePolish() 之前对项目进行最终触摸。 |
GUI 线程等待 | GUI | Threaded | lock | 在执行连接到 QQuickWindow::afterAnimating() 信号的槽后,然后在等待 GUI 线程同步 之前锁定渲染线程的互斥锁。如果这比 渲染线程同步 开始早得多,GUI 线程中就有 空闲 时间您可以用于运行额外的 QML 或 JavaScript。 |
GUI 线程同步 | GUI | Threaded | blockedForSync | GUI 线程阻塞等待渲染线程的时间。 |
动画 | GUI | Threaded, Windows | animations | 在 GUI 线程中推进动画。基本渲染循环不会同步渲染驱动动画。这就是为什么在使用基本渲染循环时看不到动画事件。观看 动画 类别以查看此情况下的动画时间。 |
渲染线程同步 | 渲染 | Threaded, Basic, Windows | 渲染帧 ... 同步 | 使用 QQuickItem::updatePaintNode() 将 QML 状态同步到场景图。 |
渲染 | 渲染 | Threaded, Basic, Windows | 渲染帧 ... 渲染 | 渲染帧所花费的总时间,包括将所有必要数据准备和上传到 GPU。这是 粗略 渲染时间。不要将其与下面的 净 渲染渲染 时间混淆。 |
交换 | 渲染 | Threaded, Basic, Windows | 渲染帧 ... 交换 | 渲染后交换帧。 |
渲染预处理 | 渲染 | Threaded, Basic, Windows | 在渲染器中的时间 ... 预处理 | 在所有需要预处理的节点上调用 QSGNode::preprocess()。这是粗略 渲染 步骤的一部分。 |
渲染更新 | 渲染 | Threaded, Basic, Windows | 在渲染器中的时间 ... 更新 | 遍历并处理场景图中所有节点以更新其几何形状、变换、不透明度和其他状态。在渲染线程同步阶段,每个节点分别使用来自GUI线程的状态进行更新。在渲染更新中,所有节点合并以创建最终场景。这是整体渲染步骤的一部分。 |
渲染绑定 | 渲染 | Threaded, Basic, Windows | 渲染器中的时间 ... 绑定 | 绑定正确的帧缓冲区进行OpenGL渲染。这是整体渲染步骤的一部分。 |
渲染渲染 | 渲染 | Threaded, Basic, Windows | 渲染器中的时间 ... 渲染 | 将所有数据发送到GPU的实际过程,通过OpenGL执行。这是整体渲染步骤的一部分。 |
材质编译 | 渲染 | Threaded, Basic, Windows | 着色器已编译 | 编译GLSL着色器程序。 |
字形渲染 | 渲染 | Threaded, Basic, Windows | 字形 ... 渲染 | 将字体字形渲染到纹理中。 |
字形上传 | 渲染 | Threaded, Basic, Windows | 字形 ... 上传 | 将字形纹理上传到GPU。 |
纹理绑定 | 渲染 | Threaded, Basic, Windows | 普通纹理 ... 绑定 | 使用glBindTextures在OpenGL上下文中绑定纹理。 |
纹理转换 | 渲染 | Threaded, Basic, Windows | 普通纹理 ... 转换 | 转换格式和降低图像大小以使其适合作为纹理使用。 |
纹理交换 | 渲染 | Threaded, Basic, Windows | 普通纹理 ... 交换 | 如果需要,在CPU上交换纹理数据。 |
纹理上传 | 渲染 | Threaded, Basic, Windows | 普通纹理 ... 上传 / atlastexture 上传完成 | 将纹理数据上传到GPU。 |
纹理米波 | 渲染 | Threaded, Basic, Windows | 普通纹理 ... 米波 | 在GPU上米波纹理。 |
纹理删除 | 渲染 | Threaded, Basic, Windows | 普通纹理已被删除 | 删除不再需要的GPU上的纹理。 |
分析Qt Quick 3D事件
以下是Qt Quick 3D的事件列表。每个渲染帧由同步、准备和渲染阶段组成,顺序执行。同步发生在场景图同步阶段,而准备和渲染发生在场景图渲染阶段。
设置环境变量QSG_RENDERER_DEBUG=render
以获取关于不同渲染遍历调用计数的附加文本输出。这些调用计数在渲染帧事件中汇总。
事件类型 | 线程 | 描述 |
---|---|---|
渲染帧 | 渲染 | 帧的渲染时间。也显示调用次数。 |
准备帧 | 渲染 | 准备帧所需时间。资源在准备阶段分配和加载。场景加载后的第一帧通常比其他帧长,因为那时大多数资源都加载了。 |
同步帧 | 渲染 | 帧的同步时间。同步负责从前端更新后端值。还管理Qt Quick Scene Graph和Qt Quick 3D之间的共享资源。 |
网格加载 | 渲染 | 网格的加载时间。显示所有网格的总内存使用量。也显示卸载。 |
自定义网格加载 | 渲染 | 自定义网格的加载时间。显示所有网格的总内存使用量。也显示卸载。 |
纹理加载 | 渲染 | 纹理的加载时间。显示所有纹理的总内存使用量。也显示卸载。 |
生成着色器 | 渲染 | 为材质生成着色器的时间。 |
加载着色器 | 渲染 | 加载内构建着色器的时间。 |
粒子更新 | GUI | 粒子系统的更新时间。显示更新的粒子数量。 |
网格内存消耗 | 渲染 | 显示总网格内存消耗的条形图。 |
纹理内存消耗 | 渲染 | 显示总纹理内存消耗的条形图。 |
查看统计信息
《统计》视图显示每种绑定、创建、编译、JavaScript或信号事件被触发的次数以及平均所需时间。通过查看统计信息来了解哪些事件需要优化。发生次数过多可能表明事件不必要的触发。要查看中位数、最长时间和最短时间,请在上下文菜单中选择扩展事件统计。
选择一个事件,在代码编辑器中移动到该事件的位置。
调用方和被调用方显示了事件之间的依赖关系。它们允许您检查应用程序的内部函数。《b translate="no">调用方总结了触发绑定的QML事件。这告诉您什么导致了绑定中的更改。《b translate="no">被调用方总结了绑定触发的事件。如果更改绑定,这将告诉您哪些QML事件受到影响。
选择一个事件,在代码编辑器中移动到该事件的位置。
当您在《b translate="no">时间轴视图中选择一个事件时,有关该事件的信息将在《b translate="no">统计和《b translate="no">火焰图视图中显示。
要将一个视图或行的内容复制到剪贴板,请在上一个上下文菜单中选择《b translate="no">复制表格或《b translate="no">复制行。
将统计信息可视化作为火焰图
《b translate="no">火焰图视图显示了QML和JavaScript执行的更简洁的统计概述。在《b translate="no">总时间视图中,水平条显示所有特定函数调用所需的总时间,相对于所有JavaScript和QML事件的总运行时间。嵌套显示了哪些函数被哪些其他函数调用。
要查看函数分配的总内存量,请在下拉菜单中选择《b translate="no">内存。
要查看函数执行的内存分配次数,请选择《b translate="no">分配。
双击视图中的某个项以放大它。双击视图中的空白区域以再次缩小。
与《b translate="no">时间轴视图不同,《b translate="no">火焰图视图不会显示完全没有QML或JavaScript运行的时段。因此,它不适合分析每帧执行时间。然而,它很容易看到各种QML和JavaScript事件的总影响。
©2024年Qt公司有限公司。本文件中包含的文档贡献是各自所有者的版权。提供的文档是在自由软件基金会的GNU自由文档许可版本1.3的条款下许可的,如自由软件基金会发布的。Qt和相应标志是芬兰及其它国家/地区的Qt公司有限责任公司的商标。所有其他商标都是其各自所有者的财产。