Profiling QML 应用程序
通过QML Profiler ,您可以找到应用程序中典型性能问题的原因,如速度慢、用户界面反应迟钝、卡顿等。典型原因包括在太少的帧中执行太多的 JavaScript。在图形用户界面线程继续运行之前,所有 JavaScript 必须返回,如果图形用户界面线程尚未准备就绪,帧就会被延迟或丢弃。
造成类似性能问题的另一个典型原因是创建或更新不可见项,这需要在图形用户界面线程中花费时间。
触发长期运行的 C++ 函数(如绘制方法和信号处理程序)也会在图形用户界面线程中耗费时间,但在QML Profiler 中更难发现,因为它不对 C++ 代码进行剖析。要发现 JavaScript 的过度使用,可检查动画和场景图事件的帧速率,查找间隙,并检查应用程序的行为是否符合预期。JavaScript 类别会显示函数的运行时间,应尽量将其控制在每帧 16 毫秒以下。
要查找因处理不可见项目而导致的问题,请查找掉帧情况,并检查是否使用了过多的短绑定或每帧更新的信号处理器。您还可以通过可视化场景图过度绘制来检查场景布局,并查找用户从未见过的项目,因为它们位于屏幕之外或隐藏在其他可见元素之下。
如果在不运行 JavaScript 的情况下仍出现丢帧现象,并且时间轴上出现大量无法解释的间隙,请检查您的自定义QQuickItem 实现。您可以使用Valgrind Callgrind或其他通用剖析器来分析 C++ 代码。
您可以使用全栈跟踪来跟踪从顶层 QML 或 JavaScript 到 C++ 以及内核空间的所有过程。你可以在Chrome 浏览器跟踪格式查看器中查看收集到的数据。
分析收集的数据
Timeline 视图以图形方式显示 QML 和 JavaScript 的执行情况,以及所有记录事件的浓缩视图。
时间轴 (6) 中的每一行都描述了记录的 QML 事件类型。把光标移到某一行的某个事件上,就能看到它需要多长时间,以及在源代码的哪个位置被调用。要只在事件被选中时显示信息,请关闭View Event Information on Mouseover (4)。
大纲 (10) 概括了收集数据的时间段。拖动缩放范围 (8) 或单击大纲可在大纲上移动。您还可以通过选择Jump to Previous Event 和Jump to Next Event (1) 在事件之间移动。
选择Show Zoom Slider (2),打开一个设置缩放级别的滑块。您还可以拖动缩放手柄 (9)。要重置默认缩放级别,请右键单击时间线打开上下文菜单,然后选择Reset Zoom 。
选择时间标尺,为时间线添加垂直方向线 (5)。
选择事件范围
选择一个事件范围 (7) 来查看事件的帧频,并将其与类似事件的帧频进行比较。选择Select Range (3) 激活选择工具。然后在时间轴上单击,指定事件范围的起点。拖动选择柄来定义范围的终点。范围的长度表示事件的帧频。
要测量两个后续事件之间的延迟,可在第一个事件的结束和第二个事件的开始之间放置一个事件范围。Duration 字段以毫秒为单位显示事件之间的延迟。
要放大事件范围,请双击该事件范围。
要缩小Timeline 、Statistics 和Flame Graph 视图中的当前范围,请右键单击该范围并选择Analyze Current Range 。要返回完整范围,请在上下文菜单中选择Analyze Full Range 。
要删除事件范围,请关闭Selection 对话框。
了解数据
一般来说,时间线视图中的事件表示 QML 或 JavaScript 的执行时间。将鼠标移到它们上面可查看详细信息。对于大多数事件,它们包括源代码中的位置、持续时间和源代码本身的一些相关部分。
选择一个事件,代码编辑器中的光标就会移动到事件相关的代码部分。
在时间线视图中,以下类型的事件会显示在一行或多行中。
事件类别 | 事件描述 |
---|---|
Pixmap Cache | 以像素为单位显示缓存的像素图数据的总体数量。此外,还会为正在加载的每张图片显示一个单独的事件,并详细说明其文件名和大小。 |
Scene Graph | 显示渲染场景图帧的时间,以及为此执行的各阶段的一些附加定时信息。 |
Memory Usage | 显示 JavaScript 内存管理器的块分配情况。一般情况下,内存管理器会一次性预留较大的内存块,然后再将其分成小块分配给应用程序。如果应用程序请求的单个内存块超过了一定大小,内存管理器会单独分配这些内存块。这两种运行模式显示为不同颜色的事件。第二行显示已分配内存的实际使用情况。这是应用程序实际请求的 JavaScript 堆的数量。 |
Input Events | 显示鼠标和键盘事件。 |
Painting | 未使用。 |
Animations | 显示激活的动画数量及其运行的帧速率。渲染线程动画显示在单独一行。 |
Compiling | 显示编译 QML 文件所花费的时间。 |
Creating | 显示在场景中创建元素所花费的时间。元素的创建分为两个阶段。第一阶段是创建数据结构,包括子元素。第二阶段是完成回调。但并非所有元素都会触发完成回调。这两个阶段在时间线中显示为单独的事件。 |
Binding | 显示评估绑定的时间以及评估所需的时间。 |
Handling Signal | 显示处理信号的时间以及处理所需的时间。 |
JavaScript | 显示绑定和信号处理器背后实际 JavaScript 的执行时间。它列出了可能用于评估绑定或处理信号的所有 JavaScript 函数。 |
Quick3D | 显示用于渲染Qt Quick 3D 帧的时间、帧准备和同步的计时信息、粒子系统更新时间和粒子更新计数,以及纹理和网格内存分配和内存消耗。 该事件类型自 Qt 6.3 起可用。 |
分析场景图事件
要了解场景图类别,请阅读Qt Quick Scene Graph(场景图)和Qt Quick Scene Graph Default Renderer(场景图默认渲染器)中有关Qt Quick 场景图工作原理的更多信息。Scene Graph 类别中报告了以下事件。并非所有事件都由所有呈现循环生成。在 Windows 和 Basic 渲染循环中,所有事件都在同一线程中运行,因此 GUI 线程和渲染线程之间的区别毫无意义。
设置环境变量 QSG_RENDER_TIMING,可获得与被剖析应用程序类似但略有不同的计时文本输出。不同之处如下。
事件类型 | 线程 | 渲染循环类型 | QSG_RENDER_TIMING 输出中的标签 | 说明 |
---|---|---|---|---|
Polish | 图形用户界面 | 线程、基本、Windows | polish | 使用QQuickItem::updatePolish() 对项目进行渲染前的最后润饰。 |
GUI Thread Wait | 图形用户界面 | 线程 | lock | 执行连接到QQuickWindow::afterAnimating() 信号的槽,然后锁定渲染线程的互斥体,然后在GUI Thread Sync 等待相同的互斥体。如果在Render Thread Sync 之前很长时间就开始执行,那么 GUI 线程中就会有空闲时间用于运行其他 QML 或 JavaScript。 |
GUI Thread Sync | 图形用户界面 | 线程 | blockedForSync | GUI 线程被阻塞,等待呈现线程的时间。 |
Animations | GUI | 线程,Windows | animations | 在 GUI 线程中推进动画。基本渲染循环不会驱动与渲染同步的动画。这就是使用基本呈现循环时不会显示动画事件的原因。请查看Animations 类别,了解这种情况下的动画时序。 |
Render Thread Sync | 渲染 | 线程, 基本, 窗口 | Frame rendered ... sync | 使用QQuickItem::updatePaintNode() 将 QML 状态同步到场景图中。 |
Render | 渲染 | 线程, 基本, 窗口 | Frame rendered ... render | 渲染帧所花费的总时间,包括准备和上传所有必要数据到 GPU 的时间。这是总渲染时间。请不要与下面的Render Render 净时间混淆。 |
Swap | 渲染 | 线程, 基本, 窗口 | Frame rendered ... swap | 渲染后交换帧。 |
Render Preprocess | 渲染 | 线程, 基本, 窗口 | time in renderer ... preprocess | 在所有需要预处理的节点上调用QSGNode::preprocess() 。这是Render 步骤的一部分。 |
Render Update | 渲染 | 线程, 基本, 窗口 | time in renderer ... updates | 迭代并处理场景图中的所有节点,以更新它们的几何形状、变换、不透明度和其他状态。在Render Thread Sync 阶段,每个节点都分别使用来自 GUI 线程的状态进行更新。在Render Update 中,所有节点被组合起来创建最终场景。这是Render 总步骤的一部分。 |
Render Bind | 渲染 | 线程、基本、Windows | time in renderer ... binding | 为 OpenGL 渲染绑定正确的帧缓冲。这是总Render 步骤的一部分。 |
Render Render | 渲染 | 线程, 基本, 窗口 | time in renderer ... rendering | 通过 OpenGL 向 GPU 发送所有数据的实际过程。这是总Render 步骤的一部分。 |
Material Compile | 渲染 | 线程, 基本, Windows | shader compiled | 编译 GLSL 着色程序。 |
Glyph Render | 渲染 | 线程, 基本, Windows | glyphs ... rendering | 将字体字形渲染为纹理。 |
Glyph Upload | 渲染 | 线程, 基本, Windows | glyphs ... upload | 将字形纹理上传到 GPU。 |
Texture Bind | 渲染 | 线程, 基本, 视窗 | plain texture ... bind | 使用 glBindTextures 在 OpenGL 上下文中绑定纹理。 |
Texture Convert | 渲染 | 线程, 基本, 视窗 | plain texture ... convert | 转换图像格式并缩放图像,使其适合用作纹理。 |
Texture Swizzle | 渲染 | 线程, 基本, Windows | plain texture ... swizzle | 必要时在 CPU 上旋转纹理数据。 |
Texture Upload | 渲染 | 线程, 基本, Windows | plain texture ... upload / atlastexture uploaded | 将纹理数据上传到 GPU。 |
Texture Mipmap | 渲染 | 线程, 基本, Windows | plain texture ... mipmap | 在 GPU 上映射纹理。 |
Texture Delete | 渲染 | 线程, 基本, 视窗 | plain texture deleted | 从 GPU 上删除已不再需要的纹理。 |
分析Qt Quick 3D 事件
以下是Qt Quick 3D 的事件列表。每个渲染帧都由同步、准备和渲染阶段组成,这三个阶段依次进行。同步发生在场景图同步阶段,而准备和渲染发生在场景图渲染阶段。
设置环境变量QSG_RENDERER_DEBUG=render
可获得不同渲染阶段的渲染调用次数的额外文本输出。这些调用次数会在渲染帧事件中汇总。
事件类型 | 线程 | 事件描述 |
---|---|---|
Render Frame | 渲染 | 帧的渲染时间。还显示绘制调用的次数。 |
Prepare Frame | 渲染 | 准备帧所需的时间。资源是在准备阶段分配和加载的。场景加载后的第一帧通常比其他帧耗时更长,因为大多数资源都是在此时加载的。 |
Synchronize Frame | 渲染 | 帧的同步时间。同步负责更新前端的后台值。同时管理Qt Quick 场景图和Qt Quick 3D 之间的共享资源。 |
Mesh Load | 渲染 | 网格的加载时间。显示所有网格的总内存使用情况。还显示卸载情况。 |
Custom Mesh Load | 渲染 | 自定义网格的加载时间。显示所有网格的总内存使用情况。也显示未加载的情况。 |
Texture Load | 渲染 | 纹理的加载时间。显示所有纹理的总内存使用情况。同时显示卸载情况。 |
Generate Shader | 渲染 | 为材质生成着色器的时间。 |
Load Shader | 渲染 | 加载内置着色器的时间。 |
Particle Update | 图形用户界面 | 粒子系统的更新时间。显示更新的粒子数量。 |
Mesh Memory Consumption | 渲染 | 显示网格总内存消耗的条形视图。 |
Texture Memory Consumption | 渲染 | 显示纹理内存总消耗量的条形视图。 |
查看统计数据
Statistics 视图显示每个绑定、创建、编译、JavaScript 或信号事件的触发次数和平均耗时。查看统计信息可了解哪些事件需要优化。发生次数多可能表明某个事件被不必要地触发。要查看事件发生时间的中位数、最长和最短时间,请在上下文菜单中选择Extended Event Statistics 。
在代码编辑器中选择一个事件,移动到源代码中。
Callers 和Callees 显示事件之间的依赖关系。Callers 总结了触发绑定的 QML 事件。Callees 总结了绑定触发的 QML 事件。这告诉你如果更改绑定,哪些 QML 事件会受到影响。
在代码编辑器的源代码中选择一个事件移动到它。
当你在Timeline 视图中选择一个事件时,有关它的信息就会显示在Statistics 和Flame Graph 视图中。
要将一个视图或一行的内容复制到剪贴板,请在上下文菜单中选择Copy Table 或Copy Row 。
将统计数据可视化为火焰图
Flame Graph 视图显示 QML 和 JavaScript 执行的更简洁统计概览。在Total Time 视图中,水平条显示了相对于所有 JavaScript 和 QML 事件的总运行时间而言,某个函数的所有调用加在一起所花的时间。嵌套显示哪些函数被其他函数调用。
要查看函数分配的内存总量,请在下拉菜单中选择Memory 。
要查看函数执行的内存分配次数,请选择Allocations 。
双击视图中的项目可放大该项目。双击视图中的空白区域可再次放大。
与Timeline 视图不同,Flame Graph 视图不显示 QML 或 JavaScript 未运行时的时间跨度。因此,它不适合分析每帧执行时间。不过,在这里很容易看到各种 QML 和 JavaScript 事件的总影响。
另请参阅 Profile QML applications(QML 配置文件应用程序)、How To: Analyze(如何:分析)、Analyzers(分析器)和Analyzing Code(分析代码)。
Copyright © The Qt Company Ltd. and other contributors. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.