本文翻譯自Qt官網文檔:
http://doc.qt.io/qt-5/qtquick-performance.html
QtQml應用程序的性能考慮與建議
1、時間考慮
作為一名程序開發者,應該努力使渲染引擎的刷新率維持在60fps,也就是說在每幀之間大約有16ms,這段時間包括了基本圖元在圖形硬件上的描畫。具體內容如下:
>盡可能的使用異步事件驅動來編程。
>使用工作者線程來處理重要的事情,比如說QML的WorkerScript類型就是起用了一個新的線程。
>不要手動重復事件循環。
>每幀的函數阻塞的時間不要超過幾毫秒。
如果不注意上面提到的內容,就會導致跳幀,影響用戶體驗。
注意:QML與C++交互時,為了避免阻塞就去創建自己的QEventLoop或調用QCoreApplication::processEvents(),這雖說是一種常見的模式,但也是危險的,因為信號處理或綁定進入事件循環時,QML引擎會繼續運行其它的綁定、動畫、狀態遷移等,這些動作就可能帶來副作用,例如,破壞包含了事件循環的層級結構。
2、性能分析
最重要的建議:使用QtCreator軟件提供的QML性能分析工具,以查看應用程序的時間花銷,這樣就可以把着重點放在實際存在的問題上,而不是那些潛在的問題,QtCreator文檔提供了QML性能分析工具的用法,可參考如下網址:
http://doc.qt.io/qtcreator/creator-qml-performance-monitor.html
通過QML性能分析工具,查看高頻度的綁定、時間花銷較大的函數,以優化焦點問題、重寫實現細節,相反不使用QML性能分析工具的話,就沒有顯著的性能提升效果。
3、JavaScript代碼
QML是對JavaScript的擴展,所以在QML應用程序中經常有大量的JavaScript代碼,例如函數與信號的動態參數類型或屬性綁定表達式,這些通常都不是問題所在,反而由於QML引擎的優化,使得綁定比C++函數調用效率更高,但也要注意不必要事件的偶發性。
3.1綁定
綁定在QML中有兩種類型:優化的和非優化的。綁定表達式越簡單越好,QML引擎發揮了優化的綁定表達式的求值特性,使得簡單的綁定表達式不用轉換到純JavaScript運行環境就可以求值。優化的綁定在求值時比復雜的非優化的綁定效率更高,前提是所有用到的類型信息在編譯時刻都應該是知道的。最大化地優化綁定表達式應該避免以下事情:
>聲明JavaScript中間變量。
>訪問“var”類型的變量。
>調用JavaScript函數。
>用綁定表達式構建閉包或定義函數。
>在即時求值范圍外訪問屬性。
>與其它屬性綁定引起的副作用。
在運行QML應用程序時,可能要設置QML_COMPILER_STATS環境變量以打印與綁定相關的數據,當知道對象和屬性的類型時,綁定速度是最快的,也就是說在綁定表達式查值過程中某些非最終狀態的屬性的類型可能會有變化,這樣綁定速度就變慢了。即時求值范圍包括以下內容:
>綁定表達式所在對象的屬性。
>組件中的id。
>組件中的根元素id。
其它組件的對象id、屬性,還有通過import導入的東西,是不在即時求值范圍的,在這種情況下,綁定是不被優化的。需要注意的是,如果綁定沒有被QML引擎優化,就會在純JavaScript環境中求值,這時上面的幾點建議就不再適用了。有時候在非常復雜的綁定中,緩存JavaScript中間變量的屬性求值結果是有益的,這個在后面的內容中會有描述。
3.2類型轉換
當訪問QML類型的屬性時,對應的JavaScript對象就會被創建,這個對象帶有外部資源,包括基本的C++數據,這是使用JavaScript的一個主要花銷,在大多數情況下這個花銷很小,但有時這個花銷就很大,例如用C++的QvariantMap類型的數據(用到了Q_PROPERTY)賦值給QML的variant類型的屬性,盡管int、qreal、bool、QString、QUrl參數化的QList列表花銷不大,但其它類型的列表花銷就很大了,這個涉及到JavaScript數組的創建、類型添加及轉換。在基本類型間轉換的花銷也可能不小,比如說string和url類型間的轉換,應該
使用匹配最接近的類型,避免不必要的類型轉換。
如果必須暴露QVariantMap類型給QML時,QML的屬性類型應該是var而不是variant,通常情況下,也應該優先考慮var類型,這樣就可以存儲真正的JavaScript引用,而且variant類型在QtQuick2.0及以后的版本中已標記為廢棄使用。
3.3屬性解析
屬性解析要花一點時間,在某些情況下,多次訪問同一屬性時,其結果被存儲起來的話,就可避免重復性的不必要的工作。如下例子,有個要經常用到的代碼塊,在for循環中多次訪問了rect對象的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");
- }
- }
在for循環中保存相同的rect的color:
- // 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");
- }
- }
一個簡單的改變就可以顯著提高性能,上面的代碼還可以做進一步的修改,因為rect對象的color屬性在for循環中沒有變,所以可以把這個存儲過程移到for循環外:
- // 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");
- }
- }
3.4屬性綁定
因為屬性綁定表達式中的某一個屬性改變時,這個表達式就會重新求值,所以綁定表達式要力求簡單。在循環中使用了綁定表達式時,綁定屬性的最后結果才是重要的,這時就可以使用一個臨時累加變量,而不是直接在綁定屬性上累加,以免額外的重復求值過程,下面的粒子說明了這一點:
- // 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];
- }
- }
- }
在上面的for循環中,accumulatedValue改了六次,導致與其綁定的text屬性也改了六次,
onTextChanged信號處理器也響應了六次,這些都是不必要的,accumulatedValue的最終結果才是最重要的,使用中間變量temp修改如下:
- // 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;
- }
- }
3.5列表屬性
前面提到了,QList<int>、QList<qreal>、QList<bool>、QList<QString>、QStringList、
QList<QUrl>等類型在C++和QML間的轉換是快速的,其它的列表類型則是很慢的。即使是使用這些轉換快速的列表類型,也要格外注意,以獲得最佳性能。
首先,列表類型的實現有兩個方法,一個是QObject對象的Q_PROPERTY宏,稱作列表引用,另一個是來自QObject對象的Q_INVOKABLE宏標記的函數的返回值,稱作列表拷貝。列表引用的讀寫是通過QMetaObject::property()進行的,屬性類型通過QVariant來處理。
通過JavaScript改變列表屬性值時要經過三個步驟:讀取列表、改變列表中特定下標的元素、寫入列表。列表拷貝相對來說就簡單多了,因為JavaScript對象的資源數據存儲了實際的列表,直接修改資源數據即可,不用經過列表引用的讀、改、寫這個過程。所以,列表拷貝比列表引用速度更快,事實上對單一的元素列表而言,兩者速度是差不多的。通常情況下,要修改臨時列表拷貝,然后把這個結果賦值給列表引用。假設定義了下面例子中的C++類且注冊到了Qt.example包中,版本是1.0:
- 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;
- };
下面的例子在for循環中給列表引用賦值,性能較差:
- // 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屬性列表,性能欠佳,下面的更改效果會更好:
- // 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");
- }
- }
其次,列表中任意一個元素改變時,都會發一個信號,如果有許多綁定了列表中特定元素的屬性,最好是創建一個與這個特定元素關聯的動態屬性,而不是直接綁定,這樣也是為了避免不必要的重復求值過程,這種情況雖不尋常,但也值得多多注意,如下面的例子:
- // 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的元素,但綁定到了列表中下標為1的屬性也會重新求值,所以引入一個中間變量效果更好:
- // 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");
- }
- }
通過上面的修改,只有中間變量會重新求值,性能得到了改善。
3.6值類型
如font、color、vector3d等值類型,也有類似QObject的C++類,對於上面提到的列表屬性的優化問題也是適用的,也要避免不必要的重復求值過程,注意性能影響。
3.7其它JavaScript對象
不同的JavaScript引擎提供了不同的優化措施,QtQuick2使用的JavaScript引擎用來對象實例和屬性查詢的優化,但也有一定的前提條件,如果前提條件不滿足,性能就會受到嚴重的影響,下面兩個條件是必須要保證的:
>盡量避免使用eval()。
>不要刪除對象的屬性。
4、通用的接口元素
4.1文本元素
文本布局解析是一個較慢的操作,為了減少布局引擎的工作量,盡可能的使用PlainText而不是StyledText。如文本中要嵌入圖片或標簽,以及文本字體加粗或傾斜等,這時就要用到StyledText了,但還是有文本解析上的開銷,用AutoText即可。另外,不建議使用RichText,因為StyledText已提供了幾乎所有的文本特性。
4.2圖片
圖片在UI的地位是舉足輕重的,但由於其加載時間的開銷、內存消耗及不同的使用方式,也是許多問題的主要來源。
異步加載——
圖片資源一般是很大的,加載時要避免阻塞UI線程,異步加載是個很好的選擇,它的加載過程會在一個低優先級的工作線程執行。使用QML的Image元素加載本地圖片時,把它的asynchronous屬性設置為true就是異步加載,如果加載的是遠程非本地的圖片,加載方式默認就是異步的。
顯式指定圖片尺寸——
圖片的sourceSize屬性改變時,圖片會重新加載。如果在一個小的元素中加載一張大的圖片,應該設置sourceSize屬性與小元素的尺寸一致,保證圖片是以小尺寸而非本身大尺寸的形式緩存的。
運行時避免圖片合成——
避免在運行時是提供預先合成的圖片資源,比如說提供帶有陰影效果的元素。
4.3anchors錨布局
在元素布局時,使用anchors錨布局比屬性綁定效率更高。下面例子rect2對象綁定到了rect1對象上:
- Rectangle {
- id: rect1
- x: 20
- width: 200; height: 200
- }
- Rectangle {
- id: rect2
- x: rect1.x
- y: rect1.y + rect1.height
- width: rect1.width - 20
- height: 200
- }
如下例子使用anchors錨布局效果更好:
- Rectangle {
- id: rect1
- x: 20
- width: 200; height: 200
- }
- Rectangle {
- id: rect2
- height: 200
- anchors.left: rect1.left
- anchors.top: rect1.bottom
- anchors.right: rect1.right
- anchors.rightMargin: 20
- }
位置綁定雖說靈活,但相對anchors錨布局來說效率還是不高的。如果布局非動態的話,使用靜態初始化是最好的方式,坐標是相對於父元素的,子元素相對於父元素的偏移量想要是固定值的話,就不應該使用anchors錨布局。下面的例子中有兩個子Rectangle,位置和大小一樣,但布局方式不同,anchors錨布局這時就沒有靜態初始化效率高了。
- Rectangle {
- width: 60
- height: 60
- Rectangle {
- id: fixedPositioning
- x: 20
- y: 20
- width: 20
- height: 20
- }
- Rectangle {
- id: anchorPositioning
- anchors.fill: parent
- anchors.margins: 20
- }
- }
5、模型與視圖
許多應用程序都至少有一個給視圖提供數據的模型,在性能最優化方面,這也是一個需要注意的地方。
5.1自定義的C++模型
在C++中自定義模型供QML中的視圖使用,最優實現在很大程度上依賴具體的用例,使用指南如下:
>盡可能的使用異步。
>在低優先級的工作者線程中做所有的處理。
>分批執行后端操作以降低I/O和IPC。
>使用滑動窗口緩存結果。
使用低優先級的工作者線程是很重要的,這樣可以減小GUI線程“挨餓”的風險,以免導致糟糕的性能,另外,同步與上鎖機制也會影響性能,也是推薦避免使用的。
5.2ListModel這個QML類型
QML提供的ListModel類型給ListView提供數據,只要運用正確的話,大多數用例也會有相對較佳的性能。
5.2.1使用工作者線程填充
ListModel元素可在低優先級的工作者線程用JavaScript形式來填充,在WorkScript中作了改變時,要調用sync()以同步這些變化到主線程。WorkScript用法可參考Qt文檔。使用WorkScript元素時,會調用各自的JavaScript引擎,增加內存使用,然而多個WorkScript元素用的是同一個工作者線程,在內存的影響上就可以忽略不計了。
5.2.2不要使用動態角色
ListModel元素在QtQuick2中的性能要好於QtQuick1,這主要是基於給定模型中每個角色的類型,類型不變時,緩存性能的提升是非常明顯的,類型動態變化時,性能提升就會受到影響,因此dynamicRoles默認是不可用的,手動設置為true時就要承擔隨之而來的性能退化了,如果可能的話重新設計應用程序而不要使用動態類型。
5.3視圖
視圖代理越簡單越好,使用QML展示必要的信息,任何額外的功能都不是即時的,在真正用到它時才會體現出來,設計視圖代理時要注意以下內容:
>代理中的元素越少,代理的創建越快,視圖的滾動越快。
>代理中的綁定盡可能少,位置布局時用anchors替代綁定或相對位置。
>代理中的元素避免使用ShaderEffect。
>代理中不要使用clip剪切效果。
設置cacheBuffer屬性,允許異步創建和可視化區域外的緩存,對於那些重要的不可能在單幀下創建的代理,cacheBuffer便是推薦設置的。cacheBuffer會使用一部分內存,這是開發者要權衡的事情,避免視圖滾動時幀刷新率的降低。
6、可視化特效
QtQuick2提供了一些特性,供開發設計者創建例外地誘人的UI,如流暢的動態變換和一些可視化的特性,在QML中使用這些特性的同時,也要注意性能方面的影響。
6.1動畫
通常說來,一個屬性播放動畫時會引起與之綁定的表達式重新求值,這是想要的結果,但有些情況下,期望綁定優先於屬性動畫,所以在屬性動畫結束后重新設置這個綁定。在動畫中要避免使用JavaScript,如x屬性動畫的每幀要避免復雜的JavaScript表達式。開發者也要注意腳本動畫的使用,因為腳本動畫是在主線程執行的,時間過長的話有可能導致跳幀。
6.2粒子
QtQuick.Particles模塊使得漂亮的粒子效果可以UI中得以實現,然而每個平台的圖形硬件性能都不同,粒子當然也就不能無限的設置其參數了,以免超出硬件支持的范圍。試圖描畫的粒子越多,所需的圖形硬件以60FPS來描畫就要求越高,渲染更多的粒子也要求更快的CPU,因此在目標平台上測試粒子效果是非常重要的,校正以60FPS渲染粒子的數量和尺寸。用不到粒子系統時不要使用粒子,如不可視元素,減少不必要的仿真。關於粒子系統的性能指南可參考如下Qt官方網址:
http://doc.qt.io/qt-5/qtquick-particles-performance.html
7、元素生命周期的控制
把一個應用程序拆分成簡單的、模塊化的組件,每個組件包含有一個單一的QML文件,這樣就可以獲得快速的啟動時,擁有更好的內存管理,減少程序中已激活卻不可見的元素。
7.1實例化延遲
QML引擎做了一些微妙的事情來保證組件加載和實例化不引起跳幀,然而除了避免做不必要的工作和工作延遲外,卻沒有更好的辦法來減少啟動時,QML的Loader便可以動態的創建組件。
7.1.1使用Loader
Loader是QML中的一個元素,可以動態的加載和卸載一個組件:
>使用active屬性,可以延遲實例化。
>使用setSource()函數,提供初始屬性值。
>asynchronouse為ture時,在組件實例化時可提高流暢性。
7.1.2使用動態創建
開發者可使用Qt.createComponnet()在運行時動態的創建組件,然后調用createObject()來完成實例化,還需要的就是在適當的時候手動刪除創建的對象,具體可參考如下網址:
http://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html
7.2銷毀不用的元素
有些不可見元素,如Tab插件的第一個插件顯示時后面的就不可見,因為它們是不可見元素的孩子,所以在大多數情況下要延遲實例化,而且在不用的時候還要銷毀,以避免不必要的開銷,如渲染、動畫、綁定等。使用Loader加載元素時,可以重置source或sourceComponnet屬性為空來釋放這個元素,其它的元素可調用destroy()來銷毀。在某些情況下,這些元素還是要保留激活狀態,但在最后要設置為不可見。下面的渲染部分也會談到這一點。
8、渲染
在QtQuick2中,用作渲染的場景圖可在60FPS下高度動態的渲染UI,但有些東西會極大的降低渲染性能,這些缺點便是開發者需要注意的地方。
8.1剪切
剪切默認是不可用的,在需要的時候再去設置它。剪切效果是可視的,它並不是一項優化措施,而是根據邊界剪切掉自己和孩子的多余部分,打破了自由的渲染各個元素的順序,使最優變成了次優,特別是在視圖代表中要不惜一切代價避免使用剪切功能。
8.2可視元素與覆蓋
如果某些元素被其它不透明的元素完全覆蓋時,最好的辦法是設置這些元素的visible屬性為false,讓它們不可見,否則它們的描畫就是多余的。同樣,在不可見元素需要在啟動時初始化的情況下,也要設置其visible屬性為false。
8.3透明與不透明
不透明的內容通常比透明的內容描畫起來要快點,這是因為透明還需混調,不透明在優化方面反而更好。一張圖片即使是不透明的,但在某個像素透明時是被當作全透明來處理的,這個對有透明邊框的BorderImage也是適用的。
8.4着色
QML的ShaderEffect類型可以在QtQuick應用程序中嵌入GLSL代碼,重要的是,着色范圍內的每一個像素都要執行片源着色程序,如果在低端硬件上着色大量的像素時就會導致性能變差。用GLSL寫的着色程序支持復雜的變換和可視化的特性,使用ShaderEffectSource可以在場景描畫之前預先着色到FBO中,FBO即幀緩存對象。
9、內存分配與回收
在應用程序中內存分配的總量及分配方式是非常重要的,拋去設備的內存限制不說,在堆上分配內存也是一筆昂貴的花銷,這導致了整頁數據的碎片化,JavaScript對內存的處理則是自動回收,這是它的好處。一個QML應用程序的內存要用到C++的堆和JavaScript的自動內存管理,在性能優化上,這兩者的微妙之處需要格外注意。
9.1對於QML應用程序開發者的建議
這些建議只是一個簡單的指南,不一定在所有的場合下都適用,但為了做最好的決定,一定要通過一個富有經驗的基准來分析。
9.1.1延時實例化
如果在一個應用程序中有多個視圖,但在某一時刻只會用到其中一個,那么其它的視圖就可以使用延時實例化來降低內存消耗。
9.1.2銷毀不用的對象
如果延時實例化了一個組件,或者用JavaScript表達式動態的創建了一個對象,較好的做法是調用destroy()來銷毀,而不是等待垃圾自動回收機制來處理它。
9.1.3不要手動觸發垃圾回收器
在大多數情況下,手動觸發垃圾回收器是不明智的,不僅沒必要反而還有副作用,因為這會阻塞GUI線程一段時間,導致跳幀和動畫的不連貫,這個需要極力避免。
9.1.4避免復雜綁定
復雜的綁定會降低性能,同時還會占用更多的內存。
9.1.5避免定義多個隱式而又相同的類型
如果在QML中自定義了一個元素的屬性,該屬性就成了這個元素的隱式類型,這方面的內容會在下面作詳細介紹。如果定義了多個隱式而又相同的類型,有些內存就浪費了,較好的做法是顯式地定義一個新的組件來做同樣的事情,這個組件還可復用。自定義屬性可以優化性能,比如說減少了綁定和表達式重新求值的次數,而且還提高了組件的模塊化特性和可維護性,然而這個屬性多次使用時,就要放到自己的組件中。
9.1.6復用存在的組件
如果正在考慮定義一個新的組件時,一定要仔細確認這個組件是不存在的,否則就應該從另一個存在的對象中作個拷貝。
9.1.7使用單例代替pragma庫
如果使用pragma庫存儲應用程序的實例數據時,考慮使用QObject單例是個更好的選擇,因為后者性能更好,也會使用更少的JavaScript堆。
9.2在QML應用程序中的內存分配
在QML應用程序中的內存用法可以分為兩個部分:C++和JavaScript。這兩者中的有些內存分配是不可避免的,可能由QML引擎或JavaScript引擎分配,然后其它的內存分配主要取決於開發者。
C++堆包括:
>固定的不可避免的QML引擎分配的內存。
>每一個組件編譯時的數據和類型信息。
>每一個C++對象的數據,以及每一個元對象的元素。
>QML的import中的數據。
Javascript堆包括:
>固定的不可避免的JavaScript引擎分配的內存。
>固定的不可避免的JavaScript集成。
>在JavaSciprt引擎運行時的類型信息。
>JavaScript的對象數據,如var類型、函數、信號等。
>表達式求值分配的變量。
進一步來說,JavaScript堆分配有一種情況是在主線程的,還有一種可選的情況是在WorkerScript線程的。如果在應用程序中沒有使用WorkerScript元素的話,也就沒有上面提到的工作者線程。JavaScript堆分配可能是幾百萬字節的,對於內存受限的設備來說,雖然WorkerScript元素有助於異步模型填充,但也要避免使用WorkerScript。
QML引擎和JavaScript引擎都會自動地產生緩存數據,每一個應用程序加載的組件是一個不同的、明確的類型,在QML中每一個元素自定義的屬性是隱式類型,任何沒有自定義屬性的元素都被JavaScript和QML引擎當作是組件定義的顯式類型,而不是它們自己的隱式類型,看下面的例子:
- import QtQuick 2.3
- Item {
- id: root
- Rectangle {
- id: r0
- color: "red"
- }
- Rectangle {
- id: r1
- color: "blue"
- width: 50
- }
- Rectangle {
- id: r2
- property int customProperty: 5
- }
- Rectangle {
- id: r3
- property string customProperty: "hello"
- }
- Rectangle {
- id: r4
- property string customProperty: "hello"
- }
- }
在上面的例子中,r0和r1對象都沒有自定義屬性,JavaScript和QML引擎認為它們是同一個類型,即顯式的Rectangle類型。r2、r3和r4都有一個自定義屬性,它們就被隱式地認為是不同的類型,r3和r4雖有相同的自定義屬性,但也是不同的類型。如果r3和r4是RectangleWithString組件的實例,這個組件定義了customProperty字符串屬性,那么r3和r4就被認為是同一個類型。
9.3深度考慮內存分配
無論何時決定考慮內存分配和性能權衡性能時,注意CPU緩存、操作系統分頁和JavaScript引擎垃圾回收都是重要的,對比分析一些潛在的方法后選一個最佳方案。下面的計算機科學原則是應用程序開發者在實現細節中的實踐經驗結合起來的,對於這些原則的理解是非常重要的,另外,權衡性能時的對比分析是不能由理論計算代替的。
9.3.1碎片
碎片是C++開發的一個問題,如果應用程序開發者沒有定義任何C++類型或插件的話,這倒是安全的,可以忽略這部分內容。久而久之,應用程序將分派很大一部分內存並寫入數據,隨后某些內存結束了數據使用后又會被釋放掉,但是這些釋放的內存不是在一個連續塊里,是不能被操作系統的其它應用程序再次使用的,而且在緩存和特性訪問上也有影響,因為這些數據可能占據了物理內存的不同分頁,反過來又影響了操作系統的性能。碎片是可以避免的,使用池分配器或其它連續的內存分配器,減少內存總量,在任何時候分配內存時,小心的管理對象生命周期,周期性地清潔、重建緩存,或者使用類似JavaScript的運行時內存管理進行垃圾回收。
9.3.2垃圾回收
JavaScript提供了垃圾回收器,內存在JavaScript引擎管理的JavaScript堆上分配,JavaScript引擎周期性地在JavaScript堆上收集所有的未引用的數據,如果有了碎片的話,就會壓縮這個堆,把現有的全部數據移到連續的內存區域,這樣釋放掉的內存就又可以被操作系統的其它應用程序使用了。
9.3.2.1垃圾回收的意義
垃圾回收是吧雙刃劍,好處是解決了碎片問題,也就是說手動管理對象的生命周期不再那么重要了,但是不足之處在於它的潛在的長時間持續的操作,這個可能由JavaScript引擎在某個時刻就開始了,卻在應用程序開發者的控制之外。如果JavaScript堆運用不當的話,垃圾回收的頻率和持續時間對程序會有負面影響。
9.3.2.2手動觸發垃圾回收器
QML應用程序在某些階段需要垃圾回收,當可用內存不足時,JavaScript引擎就會自動觸發垃圾回收,有時候程序開發者決定何時觸發垃圾回收器也是可行的。程序開發者很可能已經理解了程序在何時將會有一段很長的空閑時間,在QML程序中使用了很多的JavaScript堆內存,在特定的性能易受影響的任務中會引起周期性的垃圾回收,所以開發者可以在不考慮用戶體驗受到影響時手動觸發垃圾回收,要調用gc()函數,這將帶來深層次的垃圾回收及壓縮周期,可能是幾百毫秒,也可能超過幾千毫秒,如果可能的哈,還是不要手動觸發垃圾回收器。
9.3.3內存與性能
在某些情況下,需要犧牲內存的消耗以換取性能的提升,如前面例子中提到的增加JavaScript臨時變量以緩存查詢結果的情形。有時候,增加內存消耗帶來的影響也是很嚴重的,這時候就要權衡內存與性能的影響了。
http://blog.csdn.net/ieearth/article/details/44754573
http://blog.csdn.net/qyvlik/article/details/51470130