QML Function Call Performance Comparison
Is calling QML functions faster than calling Javascript functions? How about calling QObject functions? Which has speed advantage?
Note that this is merely calling functions but not running for-loops and while-loops. The intent is to compare this speed of calling Javascript functions to the speed of calling QObjects functions.
Calling C++ QObjects from QML is 344% slower than calling another function in a native QML object!
Time taken for a QML object to call functions in the following objects:
Native QML: 51.04 seconds (baseline)
Javascript ECMAScript (ES7): 30.53 seconds
Old style Javascript: 51.61 seconds
C++ QObject: 175.49 seconds
The reader may be interested in a higher level summary of these data in the article:
Javascript is faster than C++.
Even calling functions in "native" QML objects are not as fast as calling functions in .mjs ECMAScript modules. ECMAScript modules is the module that supports Javascript ES7 syntax.
Make as little QML to C++ calls as possible (again, only if cross function calls is a bottleneck of the application).
Note that all these functions are called without any function parameters. Should function parameters be included in this benchmark, it is expected that C++ functions will perform worse than the current numbers. This is because conversion of QML object types to C++ QVariant and then to native C++ types requires more processing than converting from QML objects to Javascript objects.
File: main.qml
Line 5: Import new style Javascript ECMAScript (ES7) modules.
Line 6: Import old style Javascript modules.
Line 7: Import C++ QObject.
Line 15: QML function used in the benchmark.
Line 17-18: Instance the C++ QObject.
Line 32: Calls the native QML function.
Line 33: Calls the Javascript ECMAScript (ES7) module.
Line 34: Calls the old style Javascript module.
Line 35: Calls the C++ QObject.
This file is modified four times. Line 32, 33, 34 and 35 are commented out respectively when the benchmark for the particular module is run.
A mistake was made while creating this benchmark. Ideally, line 27 and line 29 should swap. The cost of creating a new Javascript object should not be charged to every other benchmark. A run was made to ensure that the cost of creating a new Javascript object is negligible to the overall benchmarks. However, there was no time to re-run all benchmarks.
The following is source code for the ECMAScript (ES7) module that is loaded from line 5 in the file main.qml above.
File: benchm01.mjs
The source code for old style Javascript module that is loaded from line 6 in the file main.qml above.
File: benchm01.js
The source code for QObject that is loaded from line 7 in the file main.qml above.
File: mycppobject.h
The source code for the entry point of the project.
File: main.cpp
Note that this is merely calling functions but not running for-loops and while-loops. The intent is to compare this speed of calling Javascript functions to the speed of calling QObjects functions.
Outcome of Benchmark
Surprisingly, calling Javascript .mjs functions (referred to by Qt as ECMAScript modules, which conforms to Javascript ES7 standards) is 40% faster than calling either QML functions or old style Javascript functions.Calling C++ QObjects from QML is 344% slower than calling another function in a native QML object!
Time taken for a QML object to call functions in the following objects:
Native QML: 51.04 seconds (baseline)
Javascript ECMAScript (ES7): 30.53 seconds
Old style Javascript: 51.61 seconds
C++ QObject: 175.49 seconds
The reader may be interested in a higher level summary of these data in the article:
Javascript is faster than C++.
Conclusion
If the bottleneck of an application is cross function calls, write your functions and methods in .mjs methods. You get all the benefit of Javascript classes, ability to load modules, arrow functions plus better speed!Even calling functions in "native" QML objects are not as fast as calling functions in .mjs ECMAScript modules. ECMAScript modules is the module that supports Javascript ES7 syntax.
Make as little QML to C++ calls as possible (again, only if cross function calls is a bottleneck of the application).
Note that all these functions are called without any function parameters. Should function parameters be included in this benchmark, it is expected that C++ functions will perform worse than the current numbers. This is because conversion of QML object types to C++ QVariant and then to native C++ types requires more processing than converting from QML objects to Javascript objects.
Equipment Used in Benchmark
Hardware
- MacBook Pro
- 8GB memory
- 2.3 GHz Intel Core i5
Software
- MacOS Version 10.14.5
- Qt version 15.5.0
- XCode version 11.2.1
- Zero additional compiler tuning and optimization
Benchmark Procedure
- Compile project in release mode (not debug mode which does not reflect actual customer experience)
- Run benchmark a total of 7 times
- Drop result of first four runs to allow hardware cache to take effect
- Compute average of last 3 runs (i.e. run 5, 6 and 7)
The Code Used for Benchmark
The code below is the exact code used for this benchmark.File: main.qml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import QtQuick 2.15 import QtQuick.Window 2.15 import QtQuick.Controls 2.0 import "benchm01.mjs" as Bench01mjs import "benchm01.js" as Bench01js import BenchCPP 1.0 Window { visible: true width: 640 height: 480 title: qsTr("Hello World") function qmlFunction() { /* do nothing */ } MyCPPObject { id: myCppObject } Button { text: "Start benchmark" x: 20 y: 20 onClicked: { let startTime = new Date().getTime() / 1000; let bench = new Bench01mjs.BenchMJS(); const stop = 1000000 * 1000 for (let i = 0; i < stop ; i++) { // qmlFunction(); // bench.bench01(); // Bench01js.bench02(); myCppObject.cppBench01(); } let stopTime = new Date().getTime() / 1000; console.log("startTime:", startTime); console.log("stopTime:", stopTime); console.log("diff:", stopTime - startTime); } } } |
Line 5: Import new style Javascript ECMAScript (ES7) modules.
Line 6: Import old style Javascript modules.
Line 7: Import C++ QObject.
Line 15: QML function used in the benchmark.
Line 17-18: Instance the C++ QObject.
Line 32: Calls the native QML function.
Line 33: Calls the Javascript ECMAScript (ES7) module.
Line 34: Calls the old style Javascript module.
Line 35: Calls the C++ QObject.
This file is modified four times. Line 32, 33, 34 and 35 are commented out respectively when the benchmark for the particular module is run.
A mistake was made while creating this benchmark. Ideally, line 27 and line 29 should swap. The cost of creating a new Javascript object should not be charged to every other benchmark. A run was made to ensure that the cost of creating a new Javascript object is negligible to the overall benchmarks. However, there was no time to re-run all benchmarks.
The following is source code for the ECMAScript (ES7) module that is loaded from line 5 in the file main.qml above.
File: benchm01.mjs
1 2 3 | export class BenchMJS { bench01() { } } |
The source code for old style Javascript module that is loaded from line 6 in the file main.qml above.
File: benchm01.js
1 2 3 4 5 | .pragma library
function bench02() {
}
|
The source code for QObject that is loaded from line 7 in the file main.qml above.
File: mycppobject.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #ifndef MYCPPOBJECT_H #define MYCPPOBJECT_H #include <QObject> #include <qqml.h> class MyCPPObject : public QObject { Q_OBJECT public slots: void cppBench01() { /* do nothing */ } }; #endif // MYCPPOBJECT_H |
The source code for the entry point of the project.
File: main.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <QGuiApplication> #include <QQmlApplicationEngine> #include "mycppobject.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); qmlRegisterType<MyCPPObject>("BenchCPP", 1, 0, "MyCPPObject"); engine.load(url); return app.exec(); } |
The Application
The boring look of the final application used for benchmark.
Result: QML calling QML Function Benchmark
The following is the result when the onClicked handler calls the native QML function. That is, in file main.qml, line 32 is un-commented but line 33, 34 and 35 are commented out.Result:
Run 1: 50.962000131607056 seconds
Run 2: 51.23300004005432 seconds
Run 3: 50.91599988937378 seconds
Average: 51.04 seconds
Result: QML calling Javascript .mjs (ECMAScript) Function Benchmark
The following is the result when the onClicked handler calls the Javascript ECMAScript (ES7) function. That is, in file main.qml, line 33 is un-commented but line 32, 34 and 35 are commented out.Result:
Run 1: 30.49500012397766 seconds
Run 2: 30.520999908447266 seconds
Run 3: 30.58300018310547 seconds
Average: 30.53 seconds
Result: QML calling Javascript (old style) Function Benchmark
The following is the result when the onClicked handler calls the old style Javascript function. That is, in file main.qml, line 34 is un-commented but line 32, 33 and 35 are commented out.Result:
Run 1: 50.532999992370605 seconds
Run 2: 50.375 seconds
Run 3: 50.68599987030029 seconds
Average: 51.61 seconds
Result: QML calling C++ QObject Function Benchmark
The following is the result when the onClicked handler calls the C++ QObject function. That is, in file main.qml, line 35 is un-commented but line 32, 33 and 34 are commented out.Result:
Run 1: 175.6309998035431 seconds
Run 2: 175.26600003242493 seconds
Run 3: 175.57200002670288 seconds
Average: 175.49 seconds
2 comments:
What version of Qt was used for this test?
Very interesting. I'd also like to know which version you were testing with?
Post a Comment