1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qmltime.h" |
5 | |
6 | #include <QQmlEngine> |
7 | #include <QDebug> |
8 | #include <QGuiApplication> |
9 | #include <QElapsedTimer> |
10 | #include <QQmlContext> |
11 | #include <QQuickItem> |
12 | |
13 | #include <private/qquickview_p.h> |
14 | |
15 | Timer *Timer::m_timer = nullptr; |
16 | |
17 | Timer::Timer() |
18 | : m_component(nullptr) |
19 | , m_willparent(false) |
20 | , m_item(new QQuickItem) |
21 | { |
22 | if (m_timer) |
23 | qWarning(msg: "Timer: Timer already registered" ); |
24 | QQuickViewPrivate::get(view: &m_view)->setRootObject(m_item); |
25 | m_timer = this; |
26 | } |
27 | |
28 | QQmlComponent *Timer::component() const |
29 | { |
30 | return m_component; |
31 | } |
32 | |
33 | void Timer::setComponent(QQmlComponent *c) |
34 | { |
35 | m_component = c; |
36 | } |
37 | |
38 | Timer *Timer::timerInstance() |
39 | { |
40 | return m_timer; |
41 | } |
42 | |
43 | void Timer::run(uint iterations) |
44 | { |
45 | QQmlContext context(qmlContext(this)); |
46 | |
47 | QObject *o = m_component->create(context: &context); |
48 | QQuickItem *i = qobject_cast<QQuickItem *>(o); |
49 | if (m_willparent && i) |
50 | i->setParentItem(m_item); |
51 | delete o; |
52 | |
53 | runTest(&context, iterations); |
54 | } |
55 | |
56 | bool Timer::willParent() const |
57 | { |
58 | return m_willparent; |
59 | } |
60 | |
61 | void Timer::setWillParent(bool p) |
62 | { |
63 | m_willparent = p; |
64 | } |
65 | |
66 | void Timer::runTest(QQmlContext *context, uint iterations) |
67 | { |
68 | QElapsedTimer t; |
69 | t.start(); |
70 | for (uint ii = 0; ii < iterations; ++ii) { |
71 | QObject *o = m_component->create(context); |
72 | QQuickItem *i = qobject_cast<QQuickItem *>(o); |
73 | if (m_willparent && i) |
74 | i->setParentItem(m_item); |
75 | delete o; |
76 | } |
77 | |
78 | int e = t.elapsed(); |
79 | |
80 | qWarning() << "Total:" << e << "ms, Per iteration:" << qreal(e) / qreal(iterations) << "ms" ; |
81 | |
82 | } |
83 | |
84 | void usage(const char *name) |
85 | { |
86 | qWarning(msg: "Usage: %s [-iterations <count>] [-parent] <qml file>\n" , name); |
87 | |
88 | qWarning(msg: "qmltime is a tool for benchmarking the runtime cost of instantiating\n" |
89 | "a QML component. It is typically run as follows:\n" |
90 | "\n" |
91 | "%s path/to/benchmark.qml\n" |
92 | "\n" |
93 | "If the -parent option is specified, the component being measured will also\n" |
94 | "be parented to an item already in the scene.\n" |
95 | "\n" |
96 | "If the -iterations option is specified, the benchmark will run the specified\n" |
97 | "number of iterations. If -iterations is not specified, 1024 iterations\n" |
98 | "are performed.\n" |
99 | "\n" |
100 | "qmltime expects the file to be benchmarked to contain a certain structure.\n" |
101 | "Specifically, it requires the presence of a QmlTime.Timer element. For example,\n" |
102 | "say we wanted to benchmark the following list delegate:\n" |
103 | "\n" |
104 | "Rectangle {\n" |
105 | " color: \"green\"\n" |
106 | " width: 400; height: 100\n" |
107 | " Text {\n" |
108 | " anchors.centerIn: parent\n" |
109 | " text: name\n" |
110 | " }\n" |
111 | "}\n" |
112 | "\n" |
113 | "we would create a benchmark file that looks like this:\n" |
114 | "\n" |
115 | "import QtQuick 2.0\n" |
116 | "import QmlTime 1.0 as QmlTime\n" |
117 | "\n" |
118 | "Item {\n" |
119 | "\n" |
120 | " property string name: \"Bob Smith\"\n" |
121 | "\n" |
122 | " QmlTime.Timer {\n" |
123 | " component: Rectangle {\n" |
124 | " color: \"green\"\n" |
125 | " width: 400; height: 100\n" |
126 | " Text {\n" |
127 | " anchors.centerIn: parent\n" |
128 | " text: name\n" |
129 | " }\n" |
130 | " }\n" |
131 | " }\n" |
132 | "}\n" |
133 | "\n" |
134 | "The outer Item functions as a dummy data provider for any additional\n" |
135 | "data required by the bindings in the component being benchmarked (in the\n" |
136 | "example above we provide a \"name\" property).\n" |
137 | "\n" |
138 | "When started, the component is instantiated once before running\n" |
139 | "the benchmark, which means that the reported time does not include\n" |
140 | "compile time (as the results of compilation are cached internally).\n" |
141 | "In this sense the times reported by qmltime best correspond to the\n" |
142 | "costs associated with delegate creation in the view classes, where the\n" |
143 | "same delegate is instantiated over and over. Conversely, it is not a\n" |
144 | "good approximation for e.g. Loader, which typically only instantiates\n" |
145 | "an element once (and so for Loader the compile time is very relevant\n" |
146 | "to the overall cost)." , name); |
147 | |
148 | exit(status: -1); |
149 | } |
150 | |
151 | int main(int argc, char ** argv) |
152 | { |
153 | QGuiApplication app(argc, argv); |
154 | QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); |
155 | |
156 | uint iterations = 1024; |
157 | QString filename; |
158 | bool willParent = false; |
159 | |
160 | for (int ii = 1; ii < argc; ++ii) { |
161 | QByteArray arg(argv[ii]); |
162 | |
163 | if (arg == "-iterations" ) { |
164 | if (ii + 1 < argc) { |
165 | ++ii; |
166 | QByteArray its(argv[ii]); |
167 | bool ok = false; |
168 | iterations = its.toUInt(ok: &ok); |
169 | if (!ok || iterations == 0) |
170 | usage(name: argv[0]); |
171 | } else { |
172 | usage(name: argv[0]); |
173 | } |
174 | } else if (arg == "-parent" ) { |
175 | willParent = true; |
176 | } else if (arg == "-help" ) { |
177 | usage(name: argv[0]); |
178 | } else { |
179 | filename = QLatin1String(argv[ii]); |
180 | } |
181 | } |
182 | |
183 | if (filename.isEmpty()) |
184 | usage(name: argv[0]); |
185 | |
186 | QQmlEngine engine; |
187 | QQmlComponent component(&engine, filename); |
188 | if (component.isError()) { |
189 | qWarning() << component.errors(); |
190 | return -1; |
191 | } |
192 | |
193 | QObject *obj = component.create(); |
194 | if (!obj) { |
195 | qWarning() << component.errors(); |
196 | return -1; |
197 | } |
198 | |
199 | Timer *timer = Timer::timerInstance(); |
200 | if (!timer) { |
201 | qWarning() << "A QmlTime.Timer instance is required." ; |
202 | return -1; |
203 | } |
204 | |
205 | timer->setWillParent(willParent); |
206 | |
207 | if (!timer->component()) { |
208 | qWarning() << "The timer has no component" ; |
209 | return -1; |
210 | } |
211 | |
212 | timer->run(iterations); |
213 | |
214 | return 0; |
215 | } |
216 | |