From 2413cc1e87c051760210028979b4db8e4c13eca0 Mon Sep 17 00:00:00 2001 From: Aaron Kennedy Date: Tue, 25 Oct 2011 15:41:33 +0100 Subject: Cache QObject method arguments This more than doubles the performance of invoking simple QObject methods with parameters - such as myFunction(int,int) - multiple times. Change-Id: I4bf21fb3980b09aedf0f440a246682c418933a65 Reviewed-by: Aaron Kennedy --- src/declarative/qml/qdeclarativepropertycache.cpp | 119 +++++++++++++++++++- src/declarative/qml/qdeclarativepropertycache_p.h | 14 ++- src/declarative/qml/v8/qv8qobjectwrapper.cpp | 129 +++++++++------------- 3 files changed, 174 insertions(+), 88 deletions(-) (limited to 'src') diff --git a/src/declarative/qml/qdeclarativepropertycache.cpp b/src/declarative/qml/qdeclarativepropertycache.cpp index 134c588538..6cb952e5bf 100644 --- a/src/declarative/qml/qdeclarativepropertycache.cpp +++ b/src/declarative/qml/qdeclarativepropertycache.cpp @@ -54,6 +54,13 @@ Q_DECLARE_METATYPE(QDeclarativeV8Handle); QT_BEGIN_NAMESPACE +class QDeclarativePropertyCacheMethodArguments +{ +public: + QDeclarativePropertyCacheMethodArguments *next; + int arguments[0]; +}; + // Flags that do *NOT* depend on the property's QMetaProperty::userType() and thus are quick // to load static QDeclarativePropertyCache::Data::Flags fastFlagsForProperty(const QMetaProperty &p) @@ -143,7 +150,7 @@ void QDeclarativePropertyCache::Data::load(const QMetaProperty &p, QDeclarativeE void QDeclarativePropertyCache::Data::load(const QMetaMethod &m) { coreIndex = m.methodIndex(); - relatedIndex = -1; + arguments = 0; flags |= Data::IsFunction; if (m.methodType() == QMetaMethod::Signal) flags |= Data::IsSignal; @@ -170,7 +177,7 @@ void QDeclarativePropertyCache::Data::load(const QMetaMethod &m) void QDeclarativePropertyCache::Data::lazyLoad(const QMetaMethod &m) { coreIndex = m.methodIndex(); - relatedIndex = -1; + arguments = 0; flags |= Data::IsFunction; if (m.methodType() == QMetaMethod::Signal) flags |= Data::IsSignal; @@ -200,7 +207,8 @@ void QDeclarativePropertyCache::Data::lazyLoad(const QMetaMethod &m) Creates a new empty QDeclarativePropertyCache. */ QDeclarativePropertyCache::QDeclarativePropertyCache(QDeclarativeEngine *e) -: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0) +: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0), metaObject(0), + argumentsCache(0) { Q_ASSERT(engine); } @@ -209,7 +217,8 @@ QDeclarativePropertyCache::QDeclarativePropertyCache(QDeclarativeEngine *e) Creates a new QDeclarativePropertyCache of \a metaObject. */ QDeclarativePropertyCache::QDeclarativePropertyCache(QDeclarativeEngine *e, const QMetaObject *metaObject) -: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0) +: engine(e), parent(0), propertyIndexCacheStart(0), methodIndexCacheStart(0), metaObject(0), + argumentsCache(0) { Q_ASSERT(engine); Q_ASSERT(metaObject); @@ -221,6 +230,13 @@ QDeclarativePropertyCache::~QDeclarativePropertyCache() { clear(); + QDeclarativePropertyCacheMethodArguments *args = argumentsCache; + while (args) { + QDeclarativePropertyCacheMethodArguments *next = args->next; + qFree(args); + args = next; + } + if (parent) parent->release(); parent = 0; engine = 0; @@ -298,6 +314,7 @@ QDeclarativePropertyCache *QDeclarativePropertyCache::copy(int reserve) cache->methodIndexCacheStart = methodIndexCache.count() + methodIndexCacheStart; cache->stringCache.copyAndReserve(stringCache, reserve); cache->allowedRevisionCache = allowedRevisionCache; + cache->metaObject = metaObject; // We specifically do *NOT* copy the constructor @@ -317,6 +334,8 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb Q_UNUSED(revision); Q_ASSERT(constructor.IsEmpty()); // We should not be appending to an in-use property cache + this->metaObject = metaObject; + bool dynamicMetaObject = isDynamicMetaObject(metaObject); allowedRevisionCache.append(0); @@ -404,8 +423,8 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb if (old) { // We only overload methods in the same class, exactly like C++ - if (old->flags & Data::IsFunction && old->coreIndex >= methodOffset) - data->relatedIndex = old->coreIndex; + if (old->flags & Data::IsFunction && old->coreIndex >= methodOffset) + data->flags |= Data::IsOverload; data->overrideIndexIsProperty = !bool(old->flags & Data::IsFunction); data->overrideIndex = old->coreIndex; } @@ -588,6 +607,94 @@ QStringList QDeclarativePropertyCache::propertyNames() const return keys; } +static int EnumType(const QMetaObject *meta, const QByteArray &str) +{ + QByteArray scope; + QByteArray name; + int scopeIdx = str.lastIndexOf("::"); + if (scopeIdx != -1) { + scope = str.left(scopeIdx); + name = str.mid(scopeIdx + 2); + } else { + name = str; + } + for (int i = meta->enumeratorCount() - 1; i >= 0; --i) { + QMetaEnum m = meta->enumerator(i); + if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope))) + return QVariant::Int; + } + return QVariant::Invalid; +} + +// Returns an array of the arguments for method \a index. The first entry in the array +// is the number of arguments. +int *QDeclarativePropertyCache::methodParameterTypes(QObject *object, int index, + QVarLengthArray &dummy, + QByteArray *unknownTypeError) +{ + Q_ASSERT(object && index >= 0); + + QDeclarativeData *ddata = QDeclarativeData::get(object, false); + + if (ddata && ddata->propertyCache) { + typedef QDeclarativePropertyCacheMethodArguments A; + + QDeclarativePropertyCache *c = ddata->propertyCache; + Q_ASSERT(index < c->methodIndexCacheStart + c->methodIndexCache.count()); + + while (index < c->methodIndexCacheStart) + c = c->parent; + + Data *rv = const_cast(&c->methodIndexCache.at(index - c->methodIndexCacheStart)); + + if (rv->arguments) + return static_cast(rv->arguments)->arguments; + + const QMetaObject *metaObject = object->metaObject(); + QMetaMethod m = metaObject->method(index); + QList argTypeNames = m.parameterTypes(); + + A *args = static_cast(qMalloc(sizeof(A) + (argTypeNames.count() + 1) * sizeof(int))); + args->arguments[0] = argTypeNames.count(); + + for (int ii = 0; ii < argTypeNames.count(); ++ii) { + int type = QMetaType::type(argTypeNames.at(ii)); + if (type == QVariant::Invalid) + type = EnumType(object->metaObject(), argTypeNames.at(ii)); + if (type == QVariant::Invalid) { + if (unknownTypeError) *unknownTypeError = argTypeNames.at(ii); + qFree(args); + return 0; + } + args->arguments[ii + 1] = type; + } + + rv->arguments = args; + args->next = c->argumentsCache; + c->argumentsCache = args; + return static_cast(rv->arguments)->arguments; + + } else { + QMetaMethod m = object->metaObject()->method(index); + QList argTypeNames = m.parameterTypes(); + dummy.resize(argTypeNames.count() + 1); + dummy[0] = argTypeNames.count(); + + for (int ii = 0; ii < argTypeNames.count(); ++ii) { + int type = QMetaType::type(argTypeNames.at(ii)); + if (type == QVariant::Invalid) + type = EnumType(object->metaObject(), argTypeNames.at(ii)); + if (type == QVariant::Invalid) { + if (unknownTypeError) *unknownTypeError = argTypeNames.at(ii); + return 0; + } + dummy[ii + 1] = type; + } + + return dummy.data(); + } +} + QDeclarativePropertyCache::Data * QDeclarativePropertyCache::property(QDeclarativeEngine *engine, QObject *obj, const QHashedV8String &name, Data &local) diff --git a/src/declarative/qml/qdeclarativepropertycache_p.h b/src/declarative/qml/qdeclarativepropertycache_p.h index 068db589bd..176d6b34fe 100644 --- a/src/declarative/qml/qdeclarativepropertycache_p.h +++ b/src/declarative/qml/qdeclarativepropertycache_p.h @@ -58,6 +58,7 @@ #include "qdeclarativenotifier_p.h" #include +#include #include QT_BEGIN_NAMESPACE @@ -66,6 +67,7 @@ class QDeclarativeEngine; class QMetaProperty; class QV8Engine; class QV8QObjectWrapper; +class QDeclarativePropertyCacheMethodArguments; class Q_DECLARATIVE_EXPORT QDeclarativePropertyCache : public QDeclarativeRefCount, public QDeclarativeCleanup { @@ -109,9 +111,10 @@ public: IsVMESignal = 0x00040000, // Signal was added by QML IsV8Function = 0x00080000, // Function takes QDeclarativeV8Function* args IsSignalHandler = 0x00100000, // Function is a signal handler + IsOverload = 0x00200000, // Function is an overload of another function // Internal QDeclarativePropertyCache flags - NotFullyResolved = 0x00200000 // True if the type data is to be lazily resolved + NotFullyResolved = 0x00400000 // True if the type data is to be lazily resolved }; Q_DECLARE_FLAGS(Flags, Flag) @@ -141,6 +144,7 @@ public: bool isVMESignal() const { return flags & IsVMESignal; } bool isV8Function() const { return flags & IsV8Function; } bool isSignalHandler() const { return flags & IsSignalHandler; } + bool isOverload() const { return flags & IsOverload; } union { int propType; // When !NotFullyResolved @@ -149,7 +153,7 @@ public: int coreIndex; union { int notifyIndex; // When !IsFunction - int relatedIndex; // When IsFunction + void *arguments; // When IsFunction && HasArguments }; union { struct { // When !IsValueTypeVirtual @@ -219,9 +223,10 @@ public: inline QDeclarativeEngine *qmlEngine() const; static Data *property(QDeclarativeEngine *, QObject *, const QString &, Data &); static Data *property(QDeclarativeEngine *, QObject *, const QHashedV8String &, Data &); + static int *methodParameterTypes(QObject *, int index, QVarLengthArray &dummy, + QByteArray *unknownTypeError); static bool isDynamicMetaObject(const QMetaObject *); - protected: virtual void destroy(); virtual void clear(); @@ -252,6 +257,9 @@ private: StringCache stringCache; AllowedRevisionCache allowedRevisionCache; v8::Persistent constructor; + + const QMetaObject *metaObject; + QDeclarativePropertyCacheMethodArguments *argumentsCache; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QDeclarativePropertyCache::Data::Flags); diff --git a/src/declarative/qml/v8/qv8qobjectwrapper.cpp b/src/declarative/qml/v8/qv8qobjectwrapper.cpp index 1dc3db7d29..031cf8386e 100644 --- a/src/declarative/qml/v8/qv8qobjectwrapper.cpp +++ b/src/declarative/qml/v8/qv8qobjectwrapper.cpp @@ -1173,20 +1173,17 @@ int QV8QObjectConnectionList::qt_metacall(QMetaObject::Call method, int index, v QList connections = connectionList; - QMetaMethod method = data()->metaObject()->method(index); - Q_ASSERT(method.methodType() == QMetaMethod::Signal); - // XXX TODO: We should figure out a way to cache the parameter types to avoid resolving - // them each time. - QList params = method.parameterTypes(); + QVarLengthArray dummy; + int *argsTypes = QDeclarativePropertyCache::methodParameterTypes(data(), index, dummy, 0); v8::HandleScope handle_scope; v8::Context::Scope scope(engine->context()); - QVarLengthArray > args(params.count()); - int argCount = params.count(); + int argCount = argsTypes?argsTypes[0]:0; + QVarLengthArray, 9> args(argCount); for (int ii = 0; ii < argCount; ++ii) { - int type = QMetaType::type(params.at(ii).constData()); + int type = argsTypes[ii + 1]; if (type == qMetaTypeId()) { args[ii] = engine->fromVariant(*((QVariant *)metaArgs[ii + 1])); } else { @@ -1452,34 +1449,13 @@ static v8::Handle CallMethod(QObject *object, int index, int returnTy } } -static int EnumType(const QMetaObject *meta, const QString &strname) -{ - QByteArray str = strname.toUtf8(); - QByteArray scope; - QByteArray name; - int scopeIdx = str.lastIndexOf("::"); - if (scopeIdx != -1) { - scope = str.left(scopeIdx); - name = str.mid(scopeIdx + 2); - } else { - name = str; - } - for (int i = meta->enumeratorCount() - 1; i >= 0; --i) { - QMetaEnum m = meta->enumerator(i); - if ((m.name() == name) && (scope.isEmpty() || (m.scope() == scope))) - return QVariant::Int; - } - return QVariant::Invalid; -} - /*! Returns the match score for converting \a actual to be of type \a conversionType. A zero score means "perfect match" whereas a higher score is worse. The conversion table is copied out of the QtScript callQtMethod() function. */ -static int MatchScore(v8::Handle actual, int conversionType, - const QByteArray &conversionTypeName) +static int MatchScore(v8::Handle actual, int conversionType) { if (actual->IsNumber()) { switch (conversionType) { @@ -1551,11 +1527,13 @@ static int MatchScore(v8::Handle actual, int conversionType, case QMetaType::VoidStar: case QMetaType::QObjectStar: return 0; - default: - if (!conversionTypeName.endsWith('*')) - return 10; - else + default: { + const char *typeName = QMetaType::typeName(conversionType); + if (typeName && typeName[strlen(typeName) - 1] == '*') return 0; + else + return 10; + } } } else if (actual->IsObject()) { v8::Handle obj = v8::Handle::Cast(actual); @@ -1615,28 +1593,32 @@ static const QDeclarativePropertyCache::Data * RelatedMethod(QObject *object, QDeclarativePropertyCache::Data &dummy) { QDeclarativePropertyCache *cache = QDeclarativeData::get(object)->propertyCache; - if (current->relatedIndex == -1) + if (!current->isOverload()) return 0; + Q_ASSERT(!current->overrideIndexIsProperty); + if (cache) { - return cache->method(current->relatedIndex); + return cache->method(current->overrideIndex); } else { const QMetaObject *mo = object->metaObject(); int methodOffset = mo->methodCount() - QMetaObject_methods(mo); - while (methodOffset > current->relatedIndex) { + while (methodOffset > current->overrideIndex) { mo = mo->superClass(); methodOffset -= QMetaObject_methods(mo); } - QMetaMethod method = mo->method(current->relatedIndex); + QMetaMethod method = mo->method(current->overrideIndex); dummy.load(method); // Look for overloaded methods QByteArray methodName = QMetaMethod_name(method); - for (int ii = current->relatedIndex - 1; ii >= methodOffset; --ii) { + for (int ii = current->overrideIndex - 1; ii >= methodOffset; --ii) { if (methodName == QMetaMethod_name(mo->method(ii))) { - dummy.relatedIndex = ii; + dummy.setFlags(dummy.getFlags() | QDeclarativePropertyCache::Data::IsOverload); + dummy.overrideIndexIsProperty = 0; + dummy.overrideIndex = ii; return &dummy; } } @@ -1650,30 +1632,27 @@ static v8::Handle CallPrecise(QObject *object, const QDeclarativeProp { if (data.hasArguments()) { - QMetaMethod m = object->metaObject()->method(data.coreIndex); - QList argTypeNames = m.parameterTypes(); - QVarLengthArray argTypes(argTypeNames.count()); - - // ### Cache - for (int ii = 0; ii < argTypeNames.count(); ++ii) { - argTypes[ii] = QMetaType::type(argTypeNames.at(ii)); - if (argTypes[ii] == QVariant::Invalid) - argTypes[ii] = EnumType(object->metaObject(), QString::fromLatin1(argTypeNames.at(ii))); - if (argTypes[ii] == QVariant::Invalid) { - QString error = QString::fromLatin1("Unknown method parameter type: %1").arg(QLatin1String(argTypeNames.at(ii))); - v8::ThrowException(v8::Exception::Error(engine->toString(error))); - return v8::Handle(); - } + int *args = 0; + QVarLengthArray dummy; + QByteArray unknownTypeError; + + args = QDeclarativePropertyCache::methodParameterTypes(object, data.coreIndex, dummy, + &unknownTypeError); + + if (!args) { + QString typeName = QString::fromLatin1(unknownTypeError); + QString error = QString::fromLatin1("Unknown method parameter type: %1").arg(typeName); + v8::ThrowException(v8::Exception::Error(engine->toString(error))); + return v8::Handle(); } - if (argTypes.count() > callArgs.Length()) { + if (args[0] > callArgs.Length()) { QString error = QLatin1String("Insufficient arguments"); v8::ThrowException(v8::Exception::Error(engine->toString(error))); return v8::Handle(); } - return CallMethod(object, data.coreIndex, data.propType, argTypes.count(), - argTypes.data(), engine, callArgs); + return CallMethod(object, data.coreIndex, data.propType, args[0], args + 1, engine, callArgs); } else { @@ -1708,12 +1687,18 @@ static v8::Handle CallOverloaded(QObject *object, const QDeclarativeP const QDeclarativePropertyCache::Data *attempt = &data; do { - QList methodArgTypeNames; - - if (attempt->hasArguments()) - methodArgTypeNames = object->metaObject()->method(attempt->coreIndex).parameterTypes(); + QVarLengthArray dummy; + int methodArgumentCount = 0; + int *methodArgTypes = 0; + if (attempt->hasArguments()) { + typedef QDeclarativePropertyCache PC; + int *args = PC::methodParameterTypes(object, attempt->coreIndex, dummy, 0); + if (!args) // Must be an unknown argument + continue; - int methodArgumentCount = methodArgTypeNames.count(); + methodArgumentCount = args[0]; + methodArgTypes = args + 1; + } if (methodArgumentCount > argumentCount) continue; // We don't have sufficient arguments to call this method @@ -1723,22 +1708,8 @@ static v8::Handle CallOverloaded(QObject *object, const QDeclarativeP continue; // We already have a better option int methodMatchScore = 0; - QVarLengthArray methodArgTypes(methodArgumentCount); - - bool unknownArgument = false; - for (int ii = 0; ii < methodArgumentCount; ++ii) { - methodArgTypes[ii] = QMetaType::type(methodArgTypeNames.at(ii)); - if (methodArgTypes[ii] == QVariant::Invalid) - methodArgTypes[ii] = EnumType(object->metaObject(), - QString::fromLatin1(methodArgTypeNames.at(ii))); - if (methodArgTypes[ii] == QVariant::Invalid) { - unknownArgument = true; - break; - } - methodMatchScore += MatchScore(callArgs[ii], methodArgTypes[ii], methodArgTypeNames.at(ii)); - } - if (unknownArgument) - continue; // We don't understand all the parameters + for (int ii = 0; ii < methodArgumentCount; ++ii) + methodMatchScore += MatchScore(callArgs[ii], methodArgTypes[ii]); if (bestParameterScore > methodParameterScore || bestMatchScore > methodMatchScore) { best = attempt; @@ -1887,7 +1858,7 @@ v8::Handle QV8QObjectWrapper::Invoke(const v8::Arguments &args) } CallArgs callArgs(argCount, &arguments); - if (method.relatedIndex == -1) { + if (!method.isOverload()) { return CallPrecise(object, method, resource->engine, callArgs); } else { return CallOverloaded(object, method, resource->engine, callArgs); -- cgit v1.2.3