天天看點

QML 性能優化建議(一)

作者:QT教程

時間因素

開發程式時,必須盡可能實作一緻的60幀/秒重新整理率。60幀/秒意味着每幀之間大約有16毫秒可以進行處理,其中包括将繪圖基元上傳到圖形硬體所需的處理。

那麼,就需要注意以下幾個重要的點:

1.盡可能使用異步,事件驅動程式設計

2.使用工作線程進行重要處理

3.永遠不要手動控制事件循環

4.在阻塞函數中,每幀的花費不要超過幾毫秒

如果不這樣做,那麼将會發生調整,影響使用者體驗。

注意:永遠不應該使用的模式是建立自己的QEventLoop或調用QCoreApplication :: processEvents(),以避免在從QML調用的C ++代碼塊中阻塞。這樣做非常危險,因為當在信号處理程式或綁定中輸入事件循環時,QML引擎繼續運作其他綁定,動畫,轉換等。然後這些綁定會導緻副作用,例如,破壞包含整體層次結構事件循環。

剖析

最重要的提示是:使用Qt Creator附帶的QML分析器。了解應用程式在何處花費時間将使您能夠專注于實際存在的問題區域,而不是可能存在的問題區域。有關如何使用QML分析工具的更多資訊,請參閱Qt creator 幫助文檔。

如果不進行分析而直接去優化代碼,可能效果并不會很明顯,借助分析器将會更快的定位到消耗性能的子產品,然後再進行重新設計,以便提高性能。

JavaScript代碼

大多數QML應用程式将以動态函數、信号處理程式和屬性綁定表達式的形式包含大量JavaScript代碼。這通常不是問題,由于QML引擎中的一些優化,例如對綁定編譯器所做的那些優化,它可以(在某些用例中)比調用C ++函數更快。但是,必須注意確定不會意外觸發不必要的處理。

綁定

QML中有兩種類型的綁定:優化綁定和非優化綁定。保持綁定表達式盡可能簡單是一個好主意,因為QML引擎使用優化的綁定表達式求值程式,它可以評估簡單的綁定表達式,而無需切換到完整的JavaScript執行環境。與更複雜(非優化)的綁定相比,這些優化的綁定的評估效率更高。優化綁定的基本要求是在編譯時必須知道所通路的每個符号的類型資訊。

綁定表達式時要避免的事情,以達到最大的優化:

1.聲明中間JavaScript變量

2.通路“var”屬性

3.調用JavaScript函數

4.構造閉包或在綁定表達式中定義函數

5.通路直接評估範圍之外的屬性

6.寫作其他屬性作為副作用

立即評估範圍可以概括為它包含:

1.表達式範圍對象的屬性(對于綁定表達式,這是屬性綁定所屬的對象)

2.元件中任何對象的ID

3.元件中根項的屬性

來自其他元件的對象和任何此類對象的屬性,以及JavaScript導入中定義或包含的符号都不在直接評估範圍内,是以不會優化通路任何這些對象的綁定。

類型轉換

使用JavaScript的一個主要成本是,在大多數情況下,當通路QML類型的屬性時,會建立一個包含底層C ++資料(或對它的引用)的外部資源的JavaScript對象。在大多數情況下,這是不會太影響性能,但在其他情況下,它可能相當消耗性能。比如是将C ++ QVariantMap Q_PROPERTY配置設定給QML“variant”屬性。清單也可能是有損性能的,盡管(特定類型的序列的QList為int, qreal,布bool,QString,和QUrl)應該相對來說不會太影響, 其他清單類型可能會帶來昂貴的轉換成本(建立新的JavaScript數組,逐個添加新類型,從C ++類型執行個體到JavaScript值的每類型轉換)。

在一些基本屬性類型(例如“string”和“url”屬性)之間轉換也可能很影響性能。使用最接近的比對屬性類型将避免不必要的轉換。

如果必須将QVariantMap公開給QML,請使用“var”屬性而不是“variant”屬性。一般來說,對于來自QtQuick 2.0及更新版本的每個用例,“property var”應該被認為優于“property variant” (注意“property variant”被标記為過時),因為它允許真正的JavaScript引用存儲(可以減少某些表達式中所需的轉換次數)。

解決屬性

雖然在某些情況下可以緩存和重用查找結果,但如果可能的話,最好完全避免完成不必要的工作。

在下面的示例中,我們有一個經常運作的代碼塊(在這種情況下,它是顯式循環的内容;但它可能是一個通常評估的綁定表達式,例如),在其中,我們解決了具有“rect”id及其“color”屬性的對象多次調用:

// bad.qml
import QtQuick 2.3

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which, value) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            printValue("red", rect.color.r);
            printValue("green", rect.color.g);
            printValue("blue", rect.color.b);
            printValue("alpha", rect.color.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}           

我們可以在塊中隻解析一次公共基數:

// good.qml
import QtQuick 2.3

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which, value) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            var rectColor = rect.color; // resolve the common base.
            printValue("red", rectColor.r);
            printValue("green", rectColor.g);
            printValue("blue", rectColor.b);
            printValue("alpha", rectColor.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}           

隻需這一簡單的改變就可以顯着提高性能。請注意,上面的代碼可以進一步改進(因為在循環處理期間查找的屬性永遠不會改變),通過将屬性解析提升出循環,如下所示:

// better.qml
import QtQuick 2.3

Item {
    width: 400
    height: 200
    Rectangle {
        id: rect
        anchors.fill: parent
        color: "blue"
    }

    function printValue(which, value) {
        console.log(which + " = " + value);
    }

    Component.onCompleted: {
        var t0 = new Date();
        var rectColor = rect.color; // resolve the common base outside the tight loop.
        for (var i = 0; i < 1000; ++i) {
            printValue("red", rectColor.r);
            printValue("green", rectColor.g);
            printValue("blue", rectColor.b);
            printValue("alpha", rectColor.a);
        }
        var t1 = new Date();
        console.log("Took: " + (t1.valueOf() - t0.valueOf()) + " milliseconds for 1000 iterations");
    }
}           

屬性綁定

如果更改了引用的任何屬性,則将重新評估屬性綁定表達式。是以,綁定表達式應盡可能簡單。

如果你有一個循環來進行某些處理,但隻有處理的最終結果很重要,通常最好更新一個臨時累加器,然後将其配置設定給需要更新的屬性,而不是逐漸更新屬性本身,以避免在累積的中間階段觸發重新評估結合表達。

以下的例子說明了這一點:

// bad.qml
import QtQuick 2.3

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        for (var i = 0; i < someData.length; ++i) {
            accumulatedValue = accumulatedValue + someData[i];
        }
    }
}
           

onCompleted處理程式中的循環導緻“text”屬性綁定被重新評估六次(然後導緻依賴于文本值的任何其他屬性綁定,以及onTextChanged信号處理程式,每次重新評估時間,并列出每次顯示的文本)。在這種情況下,這顯然是不必要的,因為我們隻關心最終的值。

那麼,以上代碼可以改成這樣:

// good.qml
import QtQuick 2.3

Item {
    id: root
    width: 200
    height: 200
    property int accumulatedValue: 0

    Text {
        anchors.fill: parent
        text: root.accumulatedValue.toString()
        onTextChanged: console.log("text binding re-evaluated")
    }

    Component.onCompleted: {
        var someData = [ 1, 2, 3, 4, 5, 20 ];
        var temp = accumulatedValue;
        for (var i = 0; i < someData.length; ++i) {
            temp = temp + someData[i];
        }
        accumulatedValue = temp;
    }
}           

序列提示

如前所述,某些序列類型很快(例如,QList ,QList ,QList ,QList < QString >,QStringList和QList < QUrl >),而其他序列類型則要慢得多。除了盡可能使用這些類型而不是較慢類型之外,還需要注意一些其他與性能相關的文法以獲得最佳性能。

首先,對于序列類型的兩種不同的實作:一個是當序列是Q_PROPERTY一個的QObject的(我們稱此為參考序列),另一個用于在序列從傳回Q_INVOKABLE一個功能的QObject(我們将這稱為複制序列)。

通過QMetaObject :: property()讀取和寫入引用序列,是以讀取和寫入QVariant。這意味着從JavaScript更改序列中任何元素的值将導緻三個步驟發生:将從QObject讀取完整序列(作為QVariant,但随後轉換為正确類型的序列); 指定索引處的元素将在該序列中更改; 并且完整的序列将被寫回QObject(作為QVariant)。

複制序列更簡單,因為實際序列存儲在JavaScript對象的資源資料中,是以不會發生讀取/修改/寫入循環(而是直接修改資源資料)。

是以,對參考序列的元素的寫入将比寫入複制序列的元素慢得多。實際上,寫入N元素參考序列的單個元素與将N元素複制序列配置設定給該參考序列的成本相當大,是以通常最好修改臨時複制序列,然後将結果配置設定給計算過程中的參考序列。

假設以下C ++類型存在,并且已經正常注冊過:

class SequenceTypeExample : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY (QList<qreal> qrealListProperty READ qrealListProperty WRITE setQrealListProperty NOTIFY qrealListPropertyChanged)

public:
    SequenceTypeExample() : QQuickItem() { m_list << 1.1 << 2.2 << 3.3; }
    ~SequenceTypeExample() {}

    QList<qreal> qrealListProperty() const { return m_list; }
    void setQrealListProperty(const QList<qreal> &list) { m_list = list; emit qrealListPropertyChanged(); }

signals:
    void qrealListPropertyChanged();

private:
    QList<qreal> m_list;
};           

以下示例在多次循環中寫入引用序列的元素,進而導緻性能下降:

// bad.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        qrealListProperty.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                qrealListProperty[j] = j;
            }
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}           

由表達式引起的内部循環中的QObject屬性讀取和寫入"qrealListProperty[j] = j"使得此代碼非常不理想。相反,更好的一種方法是:

// good.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root
    width: 200
    height: 200

    Component.onCompleted: {
        var t0 = new Date();
        var someData = [1.1, 2.2, 3.3]
        someData.length = 100;
        for (var i = 0; i < 500; ++i) {
            for (var j = 0; j < 100; ++j) {
                someData[j] = j;
            }
            qrealListProperty = someData;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}
           

其次,如果屬性中的任何元素發生變化,則會發出屬性的更改信号。如果你對序列屬性中的特定元素有很多綁定,最好建立一個綁定到該元素的動态屬性,并将該動态屬性用作綁定表達式中的符号而不是sequence元素,因為它将隻有在其值發生變化時才會重新評估綁定。

// bad.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root

    property int firstBinding: qrealListProperty[1] + 10;
    property int secondBinding: qrealListProperty[1] + 20;
    property int thirdBinding: qrealListProperty[1] + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}           

請注意,即使在循環中僅修改索引2處的元素,也會重新評估三個綁定,因為更改信号的粒度是整個屬性已更改。是以,添加中間綁定有時可能是有益的:

// good.qml
import QtQuick 2.3
import Qt.example 1.0

SequenceTypeExample {
    id: root

    property int intermediateBinding: qrealListProperty[1]
    property int firstBinding: intermediateBinding + 10;
    property int secondBinding: intermediateBinding + 20;
    property int thirdBinding: intermediateBinding + 30;

    Component.onCompleted: {
        var t0 = new Date();
        for (var i = 0; i < 1000; ++i) {
            qrealListProperty[2] = i;
        }
        var t1 = new Date();
        console.log("elapsed: " + (t1.valueOf() - t0.valueOf()) + " milliseconds");
    }
}           

在上面的示例中,每次僅重新評估中間綁定,進而導緻顯著的性能提升。

值類型提示

值類型屬性(font,color,vector3d等)具有類似的QObject屬性,并将通知語義更改為序列類型屬性。是以,上面給出的序列提示也适用于值類型屬性。雖然它們通常不是值類型的問題(因為值類型的子屬性的數量通常遠小于序列中元素的數量),是以重新評估的綁定數量的任何增加不必要地會對績效産生負面影響。

其他JavaScript對象

不同的JavaScript引擎提供不同的優化。Qt Quick 2使用的JavaScript引擎針對對象執行個體化和屬性查找進行了優化,但它提供的優化依賴于某些标準。如果你的應用程式不符合标準,則JavaScript引擎會回退到“慢速路徑”模式,性能會更差。是以,請始終盡量確定您符合以下條件:

1.盡可能避免使用eval()

2.不要删除對象的屬性

【領QT開發教程學習資料,點選下方連結莬費領取↓↓,先碼住不迷路~】

點選這裡:「連結」

繼續閱讀