Skip to content

QML: Various easy optimizations#12968

Merged
DonLakeFlyer merged 2 commits intomavlink:masterfrom
Ventor-Innovations:perf/various-easy-qml-optimizations
Jun 7, 2025
Merged

QML: Various easy optimizations#12968
DonLakeFlyer merged 2 commits intomavlink:masterfrom
Ventor-Innovations:perf/various-easy-qml-optimizations

Conversation

@rubenp02
Copy link
Contributor

@rubenp02 rubenp02 commented Jun 5, 2025

QML: Various easy optimizations

Description

Replaced manual QML loading and object lifetime management in SimpleItemMapVisual and MissionItemMapVisual with Loaders. This substantially improves performance, as Loader is optimized for dynamic component loading, and avoids JavaScript on the object construction. It's also a more canonical and therefore maintainable approach.

Set the asynchronous property of MissionItemEditor's Loader to true, to prevent blocking the main GUI thread when expanding mission items.

Key changes:

  • Removed manual lifecycle and error handling code for component creation.
  • Introduced Loaders to dynamically load map visuals asynchronously.
  • Set properties and reconnected signal bindings via Loader.setSource and Loader.onLoaded, respectively, avoiding slow JavaScript logic.

Profiling results (before)

Location	Type	Time in Percent	Total Time	Self Time in Percent	Self Time	Calls	Mean Time	Median Time	Longest Time	Shortest Time	Details
<program>		100 %	4.27 s	0.00 %	0 ns	1	4.27 s	4.27 s	4.27 s	4.27 s	Main program
ApplicationWindow.qml:8	Creating	27.01 %	1.15 s	26.93 %	1.15 s	2	576 ms	576 ms	723 ms	430 ms	QtQuick.Templates/ApplicationWindow
MainWindow.qml:28	Creating	13.72 %	585 ms	0.03 %	1.22 ms	1	585 ms	585 ms	585 ms	585 ms	ApplicationWindow.qml
MissionItemEditor.qml:315	Binding	8.83 %	377 ms	0.13 %	5.33 ms	187	2.02 ms	5.7 µs	111 ms	2.6 µs	source: _currentItem ? missionItem.editorQml : ""
MissionItemEditor.qml:36	Binding	8.82 %	377 ms	0.01 %	315 µs	187	2.01 ms	1.3 µs	112 ms	400 ns	property bool _currentItem: missionItem.isCurrentItem
CameraSection.qml:12	Creating	7.75 %	331 ms	0.01 %	417 µs	14	23.6 ms	1.03 ms	74.4 ms	1.4 µs	QtQuick/Column
IntegratedAttitudeIndicator.qml:68	Handling Signal	7.63 %	326 ms	1.06 %	45.4 ms	8102	40.2 µs	31.1 µs	503 µs	8.2 µs	onPaint: { var centerX = width / 2 var centerY = height / 2 var ctx = getContext("2d") ctx.reset() ctx.strokeStyle = qgcPal.primaryButton ctx.lineWidth = attitudeSize ctx.beginPath() ctx.arc(centerX, centerY, _attitudeRadius, startRollRadiansOrdered, endRollRadiansOrdered) ctx.stroke() }
CameraSection.qml:25	Creating	7.53 %	322 ms	2.98 %	127 ms	7	45.9 ms	63.6 ms	73 ms	1.14 ms	SectionHeader.qml
MissionItemMapVisual.qml:32	Handling Signal	7.01 %	299 ms	0.00 %	144 µs	346	865 µs	794 µs	6.82 ms	646 µs	Component.onCompleted: { if (object.mapVisualQML) { var component = Qt.createComponent(object.mapVisualQML) if (component.status === Component.Error) { console.log("Error loading Qml: ", object.mapVisualQML, component.errorString()) } _visualItem = component.createObject(map, { "map": _root.map, vehicle: _root.vehicle, 'opacity': Qt.binding(function() { return _root.opacity }), 'interactive': Qt.binding(function() { return _root.interactive }) }) _visualItem.clicked.connect(_root.clicked) } }
MissionItemMapVisual.qml:32	JavaScript	7.01 %	299 ms	0.97 %	41.4 ms	346	865 µs	793 µs	6.82 ms	646 µs	expression for onCompleted
SimpleItemEditor.qml:51	Creating	6.79 %	290 ms	0.02 %	727 µs	8	36.2 ms	32.9 ms	75.2 ms	128 µs	QtQuick/Column
SimpleItemEditor.qml:13	Creating	6.78 %	289 ms	0.00 %	175 µs	8	36.2 ms	32.8 ms	75.3 ms	100 ns	QtQuick/Rectangle
SimpleItemEditor.qml:112	Creating	6.75 %	288 ms	0.00 %	47.7 µs	8	36 ms	32.6 ms	75 ms	1.6 µs	QtQuick/Column
SimpleItemEditor.qml:279	Creating	6.70 %	286 ms	0.00 %	24.9 µs	4	71.5 ms	73.4 ms	74.4 ms	64.8 ms	CameraSection.qml
IntegratedAttitudeIndicator.qml:68	JavaScript	6.56 %	280 ms	6.56 %	280 ms	8102	34.6 µs	26 µs	478 µs	7.1 µs	expression for onPaint
MissionItemEditor.qml:62	Handling Signal	5.92 %	253 ms	0.00 %	52.5 µs	3	84.2 ms	85.2 ms	87.3 ms	80.2 ms	onClicked: { if (mainWindow.allowViewSwitch()) { currentItemScope.focus = true _root.clicked() } }
MissionItemEditor.qml:62	JavaScript	5.92 %	253 ms	0.00 %	160 µs	3	84.2 ms	85.2 ms	87.3 ms	80.2 ms	expression for onClicked
PlanView.qml:767	Handling Signal	5.91 %	252 ms	0.00 %	12.2 µs	3	84.1 ms	85.1 ms	87.3 ms	80 ms	onClicked: (sequenceNumber) => { _missionController.setCurrentPlanViewSeqNum(object.sequenceNumber, false) }
PlanView.qml:767	JavaScript	5.91 %	252 ms	0.05 %	2.14 ms	3	84.1 ms	85.1 ms	87.3 ms	80 ms	Source code not available
SimpleItemMapVisual.qml:78	Handling Signal	5.72 %	244 ms	0.00 %	134 µs	344	710 µs	650 µs	6.68 ms	526 µs	Component.onCompleted: { showItemVisuals() updateDragArea() }
SimpleItemMapVisual.qml:78	JavaScript	5.72 %	244 ms	0.01 %	323 µs	344	709 µs	649 µs	6.67 ms	525 µs	expression for onCompleted
SimpleItemMapVisual.qml:46	JavaScript	5.70 %	243 ms	0.33 %	14.2 ms	344	708 µs	647 µs	6.67 ms	524 µs	showItemVisuals
SectionHeader.qml:8	Creating	5.33 %	228 ms	5.32 %	227 ms	17	13.4 ms	772 µs	44.4 ms	7 µs	CheckBox.qml
PlanMapItems.qml:40	Binding	3.71 %	158 ms	0.05 %	2.14 ms	4	39.6 ms	704 µs	157 ms	5.9 µs	model: largeMapView ? _missionController.visualItems : 0
PlanView.qml:414	Binding	3.47 %	148 ms	0.09 %	3.88 ms	4	37 ms	734 µs	147 ms	1.2 µs	model: _missionController.visualItems
MainWindow.qml:0	Compiling	3.01 %	128 ms	0.70 %	29.7 ms	1	128 ms	128 ms	128 ms	128 ms	MainWindow.qml
FlyViewMap.qml:103	Handling Signal	2.95 %	126 ms	0.03 %	1.3 ms	258	487 µs	439 µs	1.11 ms	289 µs	onAnimatedLatitudeChanged: _root.center = QtPositioning.coordinate(animatedLatitude, animatedLongitude)
IntegratedCompassAttitude.qml:40	Binding	2.93 %	125 ms	0.62 %	26.3 ms	4753	26.3 µs	15.5 µs	186 µs	1 µs	attitudeAngleDegrees: vehicle ? vehicle.roll.rawValue : 0
FlyViewMap.qml:103	JavaScript	2.92 %	124 ms	2.68 %	114 ms	258	482 µs	434 µs	1.1 ms	287 µs	expression for onAnimatedLatitudeChanged

Profiling results (after)

Location	Type	Time in Percent	Total Time	Self Time in Percent	Self Time	Calls	Mean Time	Median Time	Longest Time	Shortest Time	Details
<program>		100 %	4.85 s	0.00 %	0 ns	1	4.85 s	4.85 s	4.85 s	4.85 s	Main program
ApplicationWindow.qml:8	Creating	25.38 %	1.23 s	25.32 %	1.23 s	2	616 ms	616 ms	789 ms	442 ms	QtQuick.Templates/ApplicationWindow
MainWindow.qml:28	Creating	12.07 %	585 ms	0.02 %	812 µs	1	585 ms	585 ms	585 ms	585 ms	ApplicationWindow.qml
CameraSection.qml:12	Creating	11.53 %	559 ms	0.01 %	487 µs	16	35 ms	1.19 ms	125 ms	2.1 µs	QtQuick/Column
CameraSection.qml:25	Creating	11.35 %	550 ms	4.52 %	219 ms	8	68.8 ms	89.7 ms	124 ms	1.3 ms	SectionHeader.qml
SimpleItemEditor.qml:51	Creating	10.68 %	518 ms	0.02 %	969 µs	10	51.8 ms	46.1 ms	126 ms	147 µs	QtQuick/Column
SimpleItemEditor.qml:13	Creating	10.67 %	518 ms	0.01 %	329 µs	10	51.8 ms	46.1 ms	126 ms	100 ns	QtQuick/Rectangle
SimpleItemEditor.qml:112	Creating	10.63 %	515 ms	0.00 %	69.6 µs	10	51.5 ms	45.7 ms	125 ms	2.2 µs	QtQuick/Column
SimpleItemEditor.qml:279	Creating	10.55 %	512 ms	0.00 %	33.2 µs	5	102 ms	99.7 ms	125 ms	90.6 ms	CameraSection.qml
SectionHeader.qml:8	Creating	7.54 %	365 ms	7.53 %	365 ms	18	20.3 ms	864 µs	77 ms	7.2 µs	CheckBox.qml
IntegratedAttitudeIndicator.qml:68	Handling Signal	6.64 %	322 ms	1.03 %	49.9 ms	8224	39.2 µs	31.1 µs	5.16 ms	7.6 µs	onPaint: { var centerX = width / 2 var centerY = height / 2 var ctx = getContext("2d") ctx.reset() ctx.strokeStyle = qgcPal.primaryButton ctx.lineWidth = attitudeSize ctx.beginPath() ctx.arc(centerX, centerY, _attitudeRadius, startRollRadiansOrdered, endRollRadiansOrdered) ctx.stroke() }
IntegratedAttitudeIndicator.qml:68	JavaScript	5.62 %	272 ms	5.62 %	272 ms	8224	33.1 µs	26.1 µs	422 µs	6.7 µs	expression for onPaint
QGCMapCircleVisuals.qml:81	JavaScript	2.84 %	138 ms	0.02 %	1.13 ms	708	194 µs	20.5 µs	2.18 ms	600 ns	updateInternalComponents
QGCMapCircleVisuals.qml:42	JavaScript	2.70 %	131 ms	0.50 %	24.1 ms	348	376 µs	342 µs	979 µs	300 ns	addVisuals
QGCMapCircleVisuals.qml:95	Handling Signal	2.67 %	129 ms	0.00 %	186 µs	346	374 µs	345 µs	985 µs	1.3 µs	Component.onCompleted: { updateInternalComponents() }
QGCMapCircleVisuals.qml:95	JavaScript	2.66 %	129 ms	0.01 %	326 µs	346	373 µs	344 µs	984 µs	1.1 µs	expression for onCompleted
MainWindow.qml:0	Compiling	2.61 %	127 ms	0.58 %	28 ms	1	127 ms	127 ms	127 ms	127 ms	MainWindow.qml
IntegratedCompassAttitude.qml:40	Binding	2.54 %	123 ms	0.55 %	26.7 ms	4942	25 µs	14.1 µs	308 µs	1 µs	attitudeAngleDegrees: vehicle ? vehicle.roll.rawValue : 0
MissionSettingsEditor.qml:59	Creating	2.54 %	123 ms	0.04 %	1.98 ms	6	20.5 ms	3.18 ms	109 ms	441 µs	QtQuick.Layouts/ColumnLayout
MissionSettingsEditor.qml:15	Creating	2.52 %	122 ms	0.01 %	273 µs	6	20.3 ms	3.03 ms	109 ms	200 ns	QtQuick/Rectangle
MissionSettingsEditor.qml:144	Creating	2.46 %	119 ms	0.00 %	210 µs	6	19.9 ms	2.62 ms	108 ms	2.5 µs	QtQuick/Column
TerrainStatus.qml:119	Binding	2.20 %	107 ms	0.22 %	10.8 ms	4	26.7 ms	381 µs	106 ms	20.2 µs	model: missionController.visualItems
QGCColoredImage.qml:27	Creating	2.08 %	101 ms	1.64 %	79.7 ms	1638	61.5 µs	10.6 µs	13.3 ms	400 ns	QtQuick/Image
MainWindow.qml:212	JavaScript	2.02 %	98.1 ms	0.00 %	23.6 µs	3	32.7 ms	4.2 ms	90.3 ms	3.64 ms	performCloseChecks
QGCPopupDialog.qml:236	Handling Signal	1.95 %	94.4 ms	0.00 %	8.6 µs	2	47.2 ms	47.2 ms	90.6 ms	3.84 ms	onClicked: _accept()
QGCPopupDialog.qml:236	JavaScript	1.95 %	94.4 ms	0.00 %	27.8 µs	2	47.2 ms	47.2 ms	90.6 ms	3.83 ms	expression for onClicked
QGCPopupDialog.qml:101	JavaScript	1.95 %	94.4 ms	0.01 %	273 µs	2	47.2 ms	47.2 ms	90.6 ms	3.81 ms	_accept
QGCSimpleMessageDialog.qml:21	Handling Signal	1.94 %	94 ms	0.00 %	11.6 µs	2	47 ms	47 ms	90.3 ms	3.66 ms	onAccepted: { if (acceptFunction) { acceptFunction() } }
QGCSimpleMessageDialog.qml:21	JavaScript	1.94 %	94 ms	0.00 %	10.3 µs	2	47 ms	47 ms	90.3 ms	3.66 ms	expression for onAccepted

Note the lack of entries for SimpleItemMapVisual, MissionItemMapVisual or MissionItemEditor. The profiling tests were performed manually (following the exact same actions each time), which is the reason for the differences in total time.

Checklist:

Related Issue

#12236

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

rubenp02 added 2 commits June 5, 2025 15:57
Replaced manual QML loading and object lifetime management with Loaders.
This substantially improves performance, as Loader is optimized for
dynamic component loading, and avoids JavaScript on the object
construction. It's also a more canonical and therefore maintainable
approach.

Key changes:
- Removed manual lifecycle and error handling code for component
  creation.
- Introduced Loaders to dynamically load map visuals asynchronously.
- Set properties and reconnected signal bindings via Loader.setSource
  and Loader.onLoaded, respectively, avoiding slow JavaScript logic.
Set the asynchronous property of the mission item editor's Loader to
true, to prevent blocking the main GUI thread when expanding mission
items.
@rubenp02
Copy link
Contributor Author

rubenp02 commented Jun 5, 2025

There's many more QML components which should be refactored to use a Loader instead of manual loading with JS, including most map visual items, but here I focused on the SimpleItemMapVisual which is by far the most common one to have multiple of.

The asynchronous property of Loaders should also be used more often in the codebase IMO.

@DonLakeFlyer DonLakeFlyer self-requested a review June 6, 2025 17:56
@DonLakeFlyer DonLakeFlyer added this to the Release V5.1 milestone Jun 6, 2025
@DonLakeFlyer DonLakeFlyer merged commit 435b7aa into mavlink:master Jun 7, 2025
11 checks passed
@rubenp02 rubenp02 deleted the perf/various-easy-qml-optimizations branch June 7, 2025 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants