QML и C++. Гоняем данные, оцениваем скорость взаимодействия

О том как отправлять данные из QML в C++ и после манипуляций с ними возвращать их (данные) обратно, было уже неоднократно рассказано. В большинстве статей приводятся одни и те же примеры или, в лучшем случае, слегка измененные авторами статей.

Давайте рассмотрим пару самых явных способов передавать данные из QML в C++ и обратно. Также оценим их эффективность.

Ну, пожалуй, начнем


Берем самое простое, что может прийти в голову: есть окно с текстом, при нажатии на текст, нам нужно сделать какие-нибудь манипуляции с этим текстом средствами c++ и после показать конечный вариант в этом же окне.

Первый вариант реализации столь сложной задачи: из QML вызываем слот, описанный в C++ классе и передаем ему наш текст. После изменения текста вызываем сигнал и вместе с этим сигналом передаем измененный текст в QML.

Файл main.cpp | Создаем экземпляр класса, в котором описаны наш сигнал и слот, декларируем контекстное свойство (как бы передаем ссылку на наш класс в qml)
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <QDeclarativeContext>
#include "asd.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> app(createApplication(argc, argv));

    QmlApplicationViewer viewer;

    asd ASD;
    viewer.rootContext()->setContextProperty("ASD", &ASD);

    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
    viewer.setMainQmlFile(QLatin1String("qml/habr1/main.qml"));

    viewer.showExpanded();

    return app->exec();
}


Файл asd.h (заголовок нашего класса с сигналом и слотом)
#ifndef ASD_H
#define ASD_H

#include <QObject>

class asd : public QObject
{
    Q_OBJECT

public:
    explicit asd(QObject *parent = 0);
    
signals:
    void transmitNewText(QString text);
    
public slots:
    void getOldText(QString text);
};

#endif // ASD_H


Файл asd.cpp (описываем наш слот) | Как видно из кода, слот принимает текст, добавляет к нему слово и после вызывает сигнал
#include "asd.h"

asd::asd(QObject *parent) :
    QObject(parent)
{
}

void asd::getOldText(QString text){
    emit transmitNewText(text + " Hello!");
}


Ну и main.qml (наше окно с текстом) | По нажатию кнопки мыши (onClicked) вызываем слот и ждем сигнал (onTransmitNewText) при получении которого меняем текст сообщения в нашем окне
import QtQuick 1.1

Rectangle {
    width: 360
    height: 360

    Connections {
        target: ASD
        onTransmitNewText: text1.text = text
    }

    Text {
        id: text1
        text: qsTr("Hello World.")
        anchors.centerIn: parent
    }

    MouseArea {
        anchors.fill: parent
        onClicked: ASD.getOldText(text1.text)
    }
}


На этом скучная часть заканчивается. Давайте это включим и посмотрим, сколько времени требуется на вызов слота и отправку нашего измененного текста с сигналом.



Для чистоты эксперимента кликнем на тексте 20 раз:


Видим две строки, называющиеся «Сигналы». Открываем временную шкалу, убеждаемся, что для оценки скорости всего процесса нам нужна только верхняя строка с нашей картинки т.к. именно она длится с самого начала вызова слота до момента завершения отправки сигнала:


Второй вариант реализации: при нажатии на текст генерируем сигнал, соединяем этот сигнал со слотам уже знакомого нам класса, после чего все тем же путем после изменения текста вызываем все тот же сигнал и отвечаем на него слотом, описанным в QML.

На первый взгляд может показаться, что для этого потребуется больше времени, чем в первом варианте.

Изменяем наш main.cpp (тут все просто и понятно, соединяем слоты и сигналы)
#include <QApplication>
#include "qmlapplicationviewer.h"
#include <QGraphicsObject>
#include "asd.h"

Q_DECL_EXPORT int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> app(createApplication(argc, argv));

    QmlApplicationViewer viewer;
    viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
    viewer.setMainQmlFile(QLatin1String("qml/habr2/main.qml"));

    asd ASD;
    QObject *qml = viewer.rootObject();

    QObject::connect(qml, SIGNAL(transmitOldText(QString)),
                     &ASD, SLOT(getOldText(QString)));

    QObject::connect(&ASD, SIGNAL(transmitNewText(QVariant)),
                     qml, SLOT(getNewText(QVariant)));

    viewer.showExpanded();

    return app->exec();
}


asd.h (немного изменяем заголовок нашего класса) | т.к. в qml определяя функцию нельзя явно указать тип данных, придется заменить QString на QVariant
#ifndef ASD_H
#define ASD_H

#include <QObject>
#include <QVariant>

class asd : public QObject
{
    Q_OBJECT

public:
    explicit asd(QObject *parent = 0);

signals:
    void transmitNewText(QVariant text);

public slots:
    void getOldText(QString text);
};

#endif // ASD_H


asd.cpp (добавлен все тот же QVariant)
#include "asd.h"

asd::asd(QObject *parent) :
    QObject(parent)
{
}

void asd::getOldText(QString text){
    emit transmitNewText(QVariant(text + " Hello!"));
}


Ну и наше новое окно main.qml
import QtQuick 1.1

Rectangle {
    id: rth
    width: 360
    height: 360

    function getNewText(text){
        text1.text = text
    }

    signal transmitOldText(string text)

    Text {
        id: text1
        text: qsTr("Hello World.")
        anchors.centerIn: parent
    }

    MouseArea {
        anchors.fill: parent
        onClicked: transmitOldText(text1.text)
    }
}


Проверим, сколько времени нам понадобится на этот раз:


По просьбам читателей, добавим в наше тестирование функцию, возвращающую значение


Итак, main.cpp остается точно таким, каким и был в первом варианте. Внем все также декларируется контекстное свойство (ссылка на экземпляр нашего класса)
asd ASD;
viewer.rootContext()->setContextProperty("ASD", &ASD);


Слегка изменяем наш C++ слот (теперь он возвращает значение)
QString asd::getOldTextTransmitNewText(QString text){
    return text + " Hello!";
}


После чего немного меняем main.qml
MouseArea {
        anchors.fill: parent
        onClicked: text1.text = ASD.getOldTextTransmitNewText(text1.text)
    }


Включаем, смотрим на затраченное время:


Этот же вариант, только с Q_INVOKABLE
Q_INVOKABLE
    QString getOldTextTransmitNewText(QString text);


Оказывается совсем немного медленнее слота:


Подводим итоги (напоминаю, что приведенные миллисекунды — это сумма для 20 вызовов)


Самым долгим оказался первый способ (вызов слота из qml с последующей ловлей сигнала): 14.885 мс.
Более быстрым оказывается второй вариант (где мы соединяли слоты и сигналы): 13.993 мс.
Самым быстрый вариант — вызов функции, возвращающей значение: 13.456 мс. для слота и 13.508 мс. для Q_INVOKABLE (в пределах погрешности).

Да, штука, конечно, не шибко заметная, но никто ведь не ожидал отличий миллисекунд эдак в 20. Можно сослаться на погрешность, но при многократном повторении сего, если можно так сказать, эксперимента, результат оказывался примерно равен приведенному в этой статье.

Зачем и для кого все это делалось? Мне в голову почему-то пришла именно эта мысль, ибо такой вот «ерундой» я никогда еще не занимался. Ну а после решил поделиться результатами с вами.
Share post

Similar posts

Comments 11

    +2
    Раз пошла такая пьянка, то просто функцию вызывать с возвращаемым значением почему уж не проверили? Q_INVOKABLE.
      +3
      А чем оно от слота отличается кроме как номером в сигнатуре?
        0
        Хм, проверим и добавим в статью результаты.
      • UFO just landed and posted this here
          –1
          Тест проводился для самой, так сказать, последней на данный момент версии — Qt 5.0.1 for Windows 32-bit (MinGW 4.7, 823 MB)
          Почему QtQuick 1.1? В Qt Creator 2.6.2 есть проблемы с дизайнером. По крайней мере мне дизайнер любезно сообщает, что QtQuick 2.0 он пока еще не знает. Не будите же вы в своих проектах элементы вручную в коде позиционировать, нет, можно, конечно, но как по мне, лучше уж немного посидеть на QtQuick 1.1.
          • UFO just landed and posted this here
              –1
              Ну дак это просто замечательно, что Вы умеете все собирать сами. Как именно это относится к статье? Вам не понравилось, что использовался QtQuick 1.1? Создал проект 2.0. Накидал пару перечисленных способов, результаты относительно друг друга не изменились, но затраченное время пропорционально возросло. Например, для слота, возвращающего значение, ушло примерно 18 мс.
            –1
            > В Qt 5 сигналы\слоты быстрее должны работать
            Они, может и должны, но получается совсем на оборот. И на погрешность тут уже не сослаться. Ибо разница достаточно заметна и стабильна.
            +2
            Что-то как-то это все странно очень выглядит.

            Во-первых, 10 мс для одного вызова — это оочень много. Qt может переваривать до нескольких тысяч meta-вызовоов в секунду (без QML, правда).

            Во-вторых, при использовании сигналов/слотов необходимо совершить 2 meta-вызова, а при прямом вызове функции — один. То есть, по идее, в первых двух случаях работы в 2 раза больше. А время отличается на несколько процентов всего лишь.

            Все-это наводит на мысль, что тормозит «что-то еще», а не механизм meta-вызовов.
              0
              Приведенные миллисекунды — это сумма для 20 вызовов, как было сказано в самой статье. Время, требуемое для одного вызова измеряется в микросекундах (самая последняя цифра на картинках с результатами). Именно сумма для 20 вызовов в подводе итогов используется для большей наглядности.
                +1
                Извиняюсь, не досмотрел. Тогда это больше похоже на правду.

                Вопрос же, почему вызов одной функции не в 2 раза быстрее, чем вызов двух, тем не менее, остается открытым.

            Only users with full accounts can post comments. Log in, please.