QmlProfiler: Reduce memory usage for file names and URLs

As the various file names are actually not kept as QStrings in the
respective objects being profiled, our saving them as QStrings in each
and every profiling event is not implicitly shared and causes a huge
memory overhead. Avoid that by saving each location only once, indexed
by a disguised pointer to the object it refers to.

Normally, objects could disappear during the profiling session, and new
objects could be allocated in their place, which would mess up our
indexing system. We prevent that by referencing the objects when we
index them, thus preventing them from getting auto-destructed.

Mind that those are not JavaScript objects but rather functions,
bindings, components and the like. So, this will only cause a memory
leak if you're compiling and dropping QML components over and over.

Task-number: QTBUG-52937
Change-Id: Ia4dfb09a71a5c9a2d6ce25c3811bbe2a1036c1c1
Reviewed-by: Simon Hausmann <simon.hausmann@theqtcompany.com>
This commit is contained in:
Ulf Hermann 2016-04-25 14:52:12 +02:00
parent a2e64a2077
commit 434750f1a6
7 changed files with 196 additions and 87 deletions

View File

@ -58,14 +58,18 @@ QQmlProfilerAdapter::QQmlProfilerAdapter(QQmlProfilerService *service, QQmlEngin
connect(this, SIGNAL(dataRequested()), engine->profiler, SLOT(reportData()));
connect(this, SIGNAL(referenceTimeKnown(QElapsedTimer)),
engine->profiler, SLOT(setTimer(QElapsedTimer)));
connect(engine->profiler, SIGNAL(dataReady(QVector<QQmlProfilerData>)),
this, SLOT(receiveData(QVector<QQmlProfilerData>)));
connect(engine->profiler,
SIGNAL(dataReady(QVector<QQmlProfilerData>,QQmlProfiler::LocationHash)),
this,
SLOT(receiveData(QVector<QQmlProfilerData>,QQmlProfiler::LocationHash)));
}
// convert to QByteArrays that can be sent to the debug client
// use of QDataStream can skew results
// (see tst_qqmldebugtrace::trace() benchmark)
static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, QList<QByteArray> &messages)
static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d,
const QQmlProfiler::LocationHash &locations,
QList<QByteArray> &messages)
{
QQmlDebugPacket ds;
Q_ASSERT_X((d.messageType & (1 << 31)) == 0, Q_FUNC_INFO,
@ -78,15 +82,18 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, QList<QByteA
//### using QDataStream is relatively expensive
ds << d.time << decodedMessageType << static_cast<quint32>(d.detailType);
QQmlProfiler::Location l = locations.value(d.locationId);
switch (decodedMessageType) {
case QQmlProfilerDefinitions::RangeStart:
case QQmlProfilerDefinitions::RangeEnd:
break;
case QQmlProfilerDefinitions::RangeData:
ds << (d.detailString.isEmpty() ? d.detailUrl.toString() : d.detailString);
ds << (l.location.sourceFile.isEmpty() ? l.url.toString() : l.location.sourceFile);
break;
case QQmlProfilerDefinitions::RangeLocation:
ds << (d.detailUrl.isEmpty() ? d.detailString : d.detailUrl.toString()) << d.x << d.y;
ds << (l.url.isEmpty() ? l.location.sourceFile : l.url.toString())
<< static_cast<qint32>(l.location.line) << static_cast<qint32>(l.location.column);
break;
default:
Q_ASSERT_X(false, Q_FUNC_INFO, "Invalid message type.");
@ -103,21 +110,29 @@ qint64 QQmlProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messag
const QQmlProfilerData &nextData = data.at(next);
if (nextData.time > until || messages.length() > s_numMessagesPerBatch)
return nextData.time;
qQmlProfilerDataToByteArrays(nextData, messages);
qQmlProfilerDataToByteArrays(nextData, locations, messages);
++next;
}
next = 0;
data.clear();
locations.clear();
return -1;
}
void QQmlProfilerAdapter::receiveData(const QVector<QQmlProfilerData> &new_data)
void QQmlProfilerAdapter::receiveData(const QVector<QQmlProfilerData> &new_data,
const QQmlProfiler::LocationHash &new_locations)
{
if (data.isEmpty())
data = new_data;
else
data.append(new_data);
if (locations.isEmpty())
locations = new_locations;
else
locations.unite(new_locations);
service->dataReady(this);
}

View File

@ -63,10 +63,12 @@ public:
qint64 sendMessages(qint64 until, QList<QByteArray> &messages) Q_DECL_OVERRIDE;
public slots:
void receiveData(const QVector<QQmlProfilerData> &new_data);
void receiveData(const QVector<QQmlProfilerData> &new_data,
const QQmlProfiler::LocationHash &locations);
private:
QVector<QQmlProfilerData> data;
QQmlProfiler::LocationHash locations;
int next;
};

View File

@ -45,7 +45,9 @@ QT_BEGIN_NAMESPACE
QQmlProfiler::QQmlProfiler() : featuresEnabled(0)
{
static int metatype = qRegisterMetaType<QVector<QQmlProfilerData> >();
static int metatype2 = qRegisterMetaType<QQmlProfiler::LocationHash> ();
Q_UNUSED(metatype);
Q_UNUSED(metatype2);
m_timer.start();
}
@ -62,8 +64,18 @@ void QQmlProfiler::stopProfiling()
void QQmlProfiler::reportData()
{
emit dataReady(m_data);
m_data.clear();
LocationHash resolved;
resolved.reserve(m_locations.size());
for (auto it = m_locations.constBegin(), end = m_locations.constEnd(); it != end; ++it)
resolved.insert(it.key(), it.value());
// This unrefs all the objects. We have to make sure we do this in the GUI thread. Also, it's
// a good idea to release the memory before creating the packets to be sent.
m_locations.clear();
QVector<QQmlProfilerData> data;
data.swap(m_data);
emit dataReady(data, resolved);
}
QT_END_NAMESPACE

View File

@ -54,6 +54,8 @@
#include <private/qv4function_p.h>
#include <private/qqmlboundsignal_p.h>
#include <private/qfinitestack_p.h>
#include <private/qqmlbinding_p.h>
#include <private/qqmlcompiler_p.h>
#include "qqmlprofilerdefinitions_p.h"
#include "qqmlabstractprofileradapter_p.h"
@ -79,38 +81,16 @@ QT_BEGIN_NAMESPACE
// out.
struct Q_AUTOTEST_EXPORT QQmlProfilerData : public QQmlProfilerDefinitions
{
QQmlProfilerData() {}
QQmlProfilerData(qint64 time, int messageType, RangeType detailType, const QUrl &url,
int x = 0, int y = 0) :
time(time), messageType(messageType), detailType(detailType), detailUrl(url),
x(x), y(y) {}
QQmlProfilerData(qint64 time, int messageType, RangeType detailType, const QString &str,
int x = 0, int y = 0) :
time(time), messageType(messageType), detailType(detailType),detailString(str),
x(x), y(y) {}
QQmlProfilerData(qint64 time, int messageType, RangeType detailType, const QString &str,
const QUrl &url, int x = 0, int y = 0) :
time(time), messageType(messageType), detailType(detailType), detailString(str),
detailUrl(url), x(x), y(y) {}
QQmlProfilerData(qint64 time, int messageType, RangeType detailType) :
time(time), messageType(messageType), detailType(detailType) {}
QQmlProfilerData(qint64 time = -1, int messageType = -1,
RangeType detailType = MaximumRangeType, quintptr locationId = 0) :
time(time), locationId(locationId), messageType(messageType), detailType(detailType)
{}
qint64 time;
quintptr locationId;
int messageType; //bit field of QQmlProfilerService::Message
RangeType detailType;
// RangeData prefers detailString; RangeLocation prefers detailUrl.
QString detailString; //used by RangeData and possibly by RangeLocation
QUrl detailUrl; //used by RangeLocation and possibly by RangeData
int x; //used by RangeLocation
int y; //used by RangeLocation
};
Q_DECLARE_TYPEINFO(QQmlProfilerData, Q_MOVABLE_TYPE);
@ -118,27 +98,123 @@ Q_DECLARE_TYPEINFO(QQmlProfilerData, Q_MOVABLE_TYPE);
class QQmlProfiler : public QObject, public QQmlProfilerDefinitions {
Q_OBJECT
public:
void startBinding(const QQmlSourceLocation &location)
class BindingRefCount : public QQmlRefCount {
public:
BindingRefCount(QQmlBinding *binding):
m_binding(binding)
{
m_binding->ref.ref();
}
BindingRefCount(const BindingRefCount &other) :
QQmlRefCount(other), m_binding(other.m_binding)
{
m_binding->ref.ref();
}
BindingRefCount &operator=(const BindingRefCount &other)
{
if (this != &other) {
QQmlRefCount::operator=(other);
other.m_binding->ref.ref();
if (!m_binding->ref.deref())
delete m_binding;
m_binding = other.m_binding;
}
return *this;
}
~BindingRefCount()
{
if (!m_binding->ref.deref())
delete m_binding;
}
private:
QQmlBinding *m_binding;
};
struct Location {
Location(const QQmlSourceLocation &location = QQmlSourceLocation(),
const QUrl &url = QUrl()) :
location(location), url(url) {}
QQmlSourceLocation location;
QUrl url;
};
// Unfortunately we have to resolve the locations right away because the QML context might not
// be available anymore when we send the data.
struct RefLocation : public Location {
RefLocation() : Location(), locationType(MaximumRangeType), ref(nullptr)
{}
RefLocation(QQmlBinding *binding, QV4::FunctionObject *function) :
Location(function->sourceLocation()), locationType(Binding),
ref(new BindingRefCount(binding), QQmlRefPointer<QQmlRefCount>::Adopt)
{}
RefLocation(QQmlCompiledData *ref, const QUrl &url, const QV4::CompiledData::Object *obj,
const QString &type) :
Location(QQmlSourceLocation(type, obj->location.line, obj->location.column), url),
locationType(Creating), ref(ref)
{}
RefLocation(QQmlBoundSignalExpression *ref) :
Location(ref->sourceLocation()), locationType(HandlingSignal), ref(ref)
{}
RefLocation(QQmlDataBlob *ref) :
Location(QQmlSourceLocation(), ref->url()), locationType(Compiling), ref(ref)
{}
bool isValid() const
{
return locationType != MaximumRangeType;
}
RangeType locationType;
QQmlRefPointer<QQmlRefCount> ref;
};
typedef QHash<quintptr, Location> LocationHash;
void startBinding(QQmlBinding *binding, QV4::FunctionObject *function)
{
quintptr locationId(id(binding));
m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(),
(1 << RangeStart | 1 << RangeLocation), Binding,
location.sourceFile, qmlSourceCoordinate(location.line), qmlSourceCoordinate(location.column)));
locationId));
RefLocation &location = m_locations[locationId];
if (!location.isValid())
location = RefLocation(binding, function);
}
// Have toByteArrays() construct another RangeData event from the same QString later.
// This is somewhat pointless but important for backwards compatibility.
void startCompiling(const QUrl &url)
void startCompiling(QQmlDataBlob *blob)
{
quintptr locationId(id(blob));
m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(),
(1 << RangeStart | 1 << RangeLocation | 1 << RangeData),
Compiling, url, 1, 1));
Compiling, locationId));
RefLocation &location = m_locations[locationId];
if (!location.isValid())
location = RefLocation(blob);
}
void startHandlingSignal(const QQmlSourceLocation &location)
void startHandlingSignal(QQmlBoundSignalExpression *expression)
{
quintptr locationId(id(expression));
m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(),
(1 << RangeStart | 1 << RangeLocation), HandlingSignal,
location.sourceFile, location.line, location.column));
locationId));
RefLocation &location = m_locations[locationId];
if (!location.isValid())
location = RefLocation(expression);
}
void startCreating()
@ -146,18 +222,24 @@ public:
m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(), 1 << RangeStart, Creating));
}
void startCreating(const QString &typeName, const QUrl &fileName, int line, int column)
void startCreating(const QV4::CompiledData::Object *obj)
{
m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(),
(1 << RangeStart | 1 << RangeLocation | 1 << RangeData),
Creating, typeName, fileName, line, column));
Creating, id(obj)));
}
void updateCreating(const QString &typeName, const QUrl &fileName, int line, int column)
void updateCreating(const QV4::CompiledData::Object *obj, QQmlCompiledData *ref,
const QUrl &url, const QString &type)
{
quintptr locationId(id(obj));
m_data.append(QQmlProfilerData(m_timer.nsecsElapsed(),
(1 << RangeLocation | 1 << RangeData),
Creating, typeName, fileName, line, column));
Creating, locationId));
RefLocation &location = m_locations[locationId];
if (!location.isValid())
location = RefLocation(ref, url, obj, type);
}
template<RangeType Range>
@ -170,6 +252,12 @@ public:
quint64 featuresEnabled;
template<typename Object>
static quintptr id(const Object *pointer)
{
return reinterpret_cast<quintptr>(pointer);
}
public slots:
void startProfiling(quint64 features);
void stopProfiling();
@ -177,10 +265,11 @@ public slots:
void setTimer(const QElapsedTimer &timer) { m_timer = timer; }
signals:
void dataReady(const QVector<QQmlProfilerData> &);
void dataReady(const QVector<QQmlProfilerData> &, const QQmlProfiler::LocationHash &);
protected:
QElapsedTimer m_timer;
QHash<quintptr, RefLocation> m_locations;
QVector<QQmlProfilerData> m_data;
};
@ -194,11 +283,12 @@ struct QQmlProfilerHelper : public QQmlProfilerDefinitions {
};
struct QQmlBindingProfiler : public QQmlProfilerHelper {
QQmlBindingProfiler(QQmlProfiler *profiler, const QV4::FunctionObject *function) :
QQmlBindingProfiler(QQmlProfiler *profiler, QQmlBinding *binding,
QV4::FunctionObject *function) :
QQmlProfilerHelper(profiler)
{
Q_QML_PROFILE(QQmlProfilerDefinitions::ProfileBinding, profiler,
startBinding(function->sourceLocation()));
startBinding(binding, function));
}
~QQmlBindingProfiler()
@ -213,7 +303,7 @@ struct QQmlHandlingSignalProfiler : public QQmlProfilerHelper {
QQmlProfilerHelper(profiler)
{
Q_QML_PROFILE(QQmlProfilerDefinitions::ProfileHandlingSignal, profiler,
startHandlingSignal(expression->sourceLocation()));
startHandlingSignal(expression));
}
~QQmlHandlingSignalProfiler()
@ -224,10 +314,10 @@ struct QQmlHandlingSignalProfiler : public QQmlProfilerHelper {
};
struct QQmlCompilingProfiler : public QQmlProfilerHelper {
QQmlCompilingProfiler(QQmlProfiler *profiler, const QUrl &url) :
QQmlCompilingProfiler(QQmlProfiler *profiler, QQmlDataBlob *blob) :
QQmlProfilerHelper(profiler)
{
Q_QML_PROFILE(QQmlProfilerDefinitions::ProfileCompiling, profiler, startCompiling(url));
Q_QML_PROFILE(QQmlProfilerDefinitions::ProfileCompiling, profiler, startCompiling(blob));
}
~QQmlCompilingProfiler()
@ -239,14 +329,6 @@ struct QQmlCompilingProfiler : public QQmlProfilerHelper {
struct QQmlVmeProfiler : public QQmlProfilerDefinitions {
public:
struct Data {
Data() : m_line(0), m_column(0) {}
QUrl m_url;
int m_line;
int m_column;
QString m_typeName;
};
QQmlVmeProfiler() : profiler(0) {}
void init(QQmlProfiler *p, int maxDepth)
@ -255,30 +337,30 @@ public:
ranges.allocate(maxDepth);
}
Data pop()
const QV4::CompiledData::Object *pop()
{
if (ranges.count() > 0)
return ranges.pop();
else
return Data();
return nullptr;
}
void push(const Data &data)
void push(const QV4::CompiledData::Object *object)
{
if (ranges.capacity() > ranges.count())
ranges.push(data);
ranges.push(object);
}
QQmlProfiler *profiler;
private:
QFiniteStack<Data> ranges;
QFiniteStack<const QV4::CompiledData::Object *> ranges;
};
#define Q_QML_OC_PROFILE(member, Code)\
Q_QML_PROFILE_IF_ENABLED(QQmlProfilerDefinitions::ProfileCreating, member.profiler, Code)
class QQmlObjectCreationProfiler : public QQmlVmeProfiler::Data {
class QQmlObjectCreationProfiler {
public:
QQmlObjectCreationProfiler(QQmlProfiler *profiler) : profiler(profiler)
@ -291,13 +373,10 @@ public:
Q_QML_PROFILE(QQmlProfilerDefinitions::ProfileCreating, profiler, endRange<QQmlProfilerDefinitions::Creating>());
}
void update(const QString &typeName, const QUrl &url, int line, int column)
void update(QQmlCompiledData *ref, const QV4::CompiledData::Object *obj,
const QString &typeName, const QUrl &url)
{
profiler->updateCreating(typeName, url, line, column);
m_typeName = typeName;
m_url = url;
m_line = line;
m_column = column;
profiler->updateCreating(obj, ref, url, typeName);
}
private:
@ -310,8 +389,7 @@ public:
profiler(parent->profiler)
{
Q_QML_PROFILE_IF_ENABLED(QQmlProfilerDefinitions::ProfileCreating, profiler, {
QQmlVmeProfiler::Data data = parent->pop();
profiler->startCreating(data.m_typeName, data.m_url, data.m_line, data.m_column);
profiler->startCreating(parent->pop());
});
}
@ -326,5 +404,6 @@ private:
QT_END_NAMESPACE
Q_DECLARE_METATYPE(QVector<QQmlProfilerData>)
Q_DECLARE_METATYPE(QQmlProfiler::LocationHash)
#endif // QQMLPROFILER_P_H

View File

@ -168,7 +168,7 @@ void QQmlBinding::update(QQmlPropertyPrivate::WriteFlags flags)
return;
}
QQmlBindingProfiler prof(ep->profiler, f);
QQmlBindingProfiler prof(ep->profiler, this, f);
setUpdatingFlag(true);
QQmlJavaScriptExpression::DeleteWatcher watcher(this);

View File

@ -1037,8 +1037,8 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo
if (compiledData->isComponent(index)) {
isComponent = true;
QQmlComponent *component = new QQmlComponent(engine, compiledData, index, parent);
Q_QML_OC_PROFILE(sharedState->profiler, profiler.update(QStringLiteral("<component>"),
context->url(), obj->location.line, obj->location.column));
Q_QML_OC_PROFILE(sharedState->profiler, profiler.update(
compiledData, obj, QStringLiteral("<component>"), context->url()));
QQmlComponentPrivate::get(component)->creationContext = context;
instance = component;
ddata = QQmlData::get(instance, /*create*/true);
@ -1048,8 +1048,8 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo
installPropertyCache = !typeRef->isFullyDynamicType;
QQmlType *type = typeRef->type;
if (type) {
Q_QML_OC_PROFILE(sharedState->profiler, profiler.update(type->qmlTypeName(),
context->url(), obj->location.line, obj->location.column));
Q_QML_OC_PROFILE(sharedState->profiler, profiler.update(
compiledData, obj, type->qmlTypeName(), context->url()));
instance = type->create();
if (!instance) {
recordError(obj->location, tr("Unable to create object of type %1").arg(stringAt(obj->inheritedTypeNameIndex)));
@ -1071,8 +1071,9 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo
sharedState->allCreatedObjects.push(instance);
} else {
Q_ASSERT(typeRef->component);
Q_QML_OC_PROFILE(sharedState->profiler, profiler.update(typeRef->component->fileName(),
context->url(), obj->location.line, obj->location.column));
Q_QML_OC_PROFILE(sharedState->profiler, profiler.update(
compiledData, obj, typeRef->component->fileName(),
context->url()));
if (typeRef->component->compilationUnit->data->isSingleton())
{
recordError(obj->location, tr("Composite Singleton Type %1 is not creatable").arg(stringAt(obj->inheritedTypeNameIndex)));
@ -1115,7 +1116,7 @@ QObject *QQmlObjectCreator::createInstance(int index, QObject *parent, bool isCo
parserStatus->classBegin();
// push() the profiler state here, together with the parserStatus, as we'll pop() them
// together, too.
Q_QML_OC_PROFILE(sharedState->profiler, sharedState->profiler.push(profiler));
Q_QML_OC_PROFILE(sharedState->profiler, sharedState->profiler.push(obj));
sharedState->allParserStatusCallbacks.push(parserStatus);
parserStatus->d = &sharedState->allParserStatusCallbacks.top();
}

View File

@ -654,7 +654,7 @@ void QQmlDataBlob::notifyComplete(QQmlDataBlob *blob)
Q_ASSERT(m_waitingFor.contains(blob));
Q_ASSERT(blob->status() == Error || blob->status() == Complete);
QQmlCompilingProfiler prof(QQmlEnginePrivate::get(typeLoader()->engine())->profiler,
blob->url());
blob);
m_inCallback = true;
@ -1208,7 +1208,7 @@ void QQmlTypeLoader::setData(QQmlDataBlob *blob, QQmlFile *file)
void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::Data &d)
{
QML_MEMORY_SCOPE_URL(blob->url());
QQmlCompilingProfiler prof(QQmlEnginePrivate::get(engine())->profiler, blob->url());
QQmlCompilingProfiler prof(QQmlEnginePrivate::get(engine())->profiler, blob);
blob->m_inCallback = true;
@ -1228,7 +1228,7 @@ void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::Data &d)
void QQmlTypeLoader::setCachedUnit(QQmlDataBlob *blob, const QQmlPrivate::CachedQmlUnit *unit)
{
QML_MEMORY_SCOPE_URL(blob->url());
QQmlCompilingProfiler prof(QQmlEnginePrivate::get(engine())->profiler, blob->url());
QQmlCompilingProfiler prof(QQmlEnginePrivate::get(engine())->profiler, blob);
blob->m_inCallback = true;