接獲使用者反應,某個使用KendoGrid顯示大量資料的網頁,用IE檢視的話速度慢到嚇人。聞此言,馬上打開IE11測試,果真嚇得我差點閃了兩滴… 也太慢了吧!

先交代問題情境,專案使用Angular搭配Kendo UI開發,順理成章使用Kendo UI提供的Angular Directive,寫成<div kendo-grid …>,資料筆數偏多,大約一千多到兩千筆,基於KendoGri的強大彈性及高複雜度,處理起來多耗點時間倒也合理,但這個案例在IE的表現與期望出入頗大,值得調查。

修改KendoGrid的官方範例,我設計了一個實驗:

    <div id="example" ng-app="KendoDemos">
        <div ng-controller="MyCtrl">
            <button ng-click="loadData()">Load Data</button>
            <span ng-bind="duration"></span>
            <div kendo-grid="grid" options="mainGridOptions">
            </div>
 
        </div>
    </div>
    <script>
    angular.module("KendoDemos", [ "kendo.directives" ])
        .controller("MyCtrl", function($scope){
            var data = [];
            for (var i = 0; i < 2000; i++) {
              data.push({
                FirstName: "FN" + i,
                LastName: "LN" + i,
                Country: "CN" + i,
                City: "CT" + i,
                Title: "T" + i
              });
            }
            $scope.loadData = function () {
                st = new Date();
                $scope.grid.dataSource.data(data);
            };
            var st;
            $scope.mainGridOptions = {
                dataSource: [],
                height: 600,
                sortable: true,
                pageable: false,
                columns: [
                    { field: "FirstName", title: "First Name", width: "120px" },
                    { field: "LastName", title: "Last Name", width: "120px" },
                    { field: "Country", width: "120px" },
                    { field: "City", width: "120px" },
                    { field: "Title" }
                ],
                dataBound: function () {
                    if (st) {
                        setTimeout(function () {
                            $scope.duration = new Date() - st + "ms";
                            $scope.$digest();
                        }, 1);
                    }
                }
            };
        })
    </script>

網頁使用Directive建立KendoGrid,按鈕後將2000筆資料放入KendoGrid中,重點按鈕到資料顯示完成所需的時間(dataBound事件將在整個Grid的DOM生成後觸發),實測IE11耗時3秒。透過F12開發者工具的分析工具,抓出速度慢的源頭在Angular的compile、publicLinkFn等方法,共執行1979次,耗費2秒以上。

此一發現讓人合理推測如果不用Angular Directive,改以純jQuery方式建立KendoGrid,效率應會提升。所以我又寫了對照組:

    <div id="example">
        <div>
            <button>Load Data</button>
            <span id="duration"></span>
            <div id="grid">
            </div>
 
        </div>
    </div>
    <script>
        var data = [];
        for (var i = 0; i < 2000; i++) {
            data.push({
                FirstName: "FN" + i,
                LastName: "LN" + i,
                Country: "CN" + i,
                City: "CT" + i,
                Title: "T" + i
            });
        }
        var st, ed;
        $("#grid").kendoGrid({
            dataSource: [],
            height: 600,
            sortable: true,
            pageable: false,
            columns: [
                { field: "FirstName", title: "First Name", width: "120px" },
                { field: "LastName", title: "Last Name", width: "120px" },
                { field: "Country", width: "120px" },
                { field: "City", width: "120px" },
                { field: "Title" }
            ],
            dataBound: function () {
                if (st) $("#duration").text(new Date() - st + "ms");
            }
        });
        $("button").click(function () {
            st = new Date();
            $("#grid").data("kendoGrid").dataSource.data(data);
        });
    </script>

幾乎相同的程式邏輯,差別在改用jQuery建立KendoGrid及處理按鈕事件,實測速度可縮短至1.2秒,證明速度慢確實與啟用Angular有關。使用分析工具觀察,同樣的欄位設定,不使用Angular時,KendoGrid在_renderContent()經由指定innerHTML產生畫面元素,省去Angular模式的comple、建立scope、產生dataItem物件環節,邏輯相對單純,應是耗費時間變少的主因。

不過,這種模式不能在欄位Template使用<span ng-bind="dataItem.propName">等Angular寫法,無法叫用現成的Directive、Filter等,不利邏輯集中與程式碼共用。有利有弊,實務應用時就必須做出取捨,而二者的效能差距數字是判斷的關鍵。

寫了兩個測試網頁放在JSBin:KendoGrid + Angular版 vs KendoGrid + 純jQuery,用IE11、Edge、Chrome及Firefox做了不專業的測試。操作方法是重新載入網頁,按下Load Data鈕,反覆數次取最低值,測試結果為:

  • IE11:2.3秒 vs 0.8秒
  • Edge:2.2秒 vs 0.8秒
  • Chrome:1.1秒 vs 0.3秒
  • Firefox:1.1秒 vs 0.3秒

由於沒有精準地控制環境、變因,以上數據並不具權威性,但足以確定啟用Angular時速度慢了約三倍,在資料量小的情境差別有限,若遇到上千筆資料時,就有可能讓使用者皺眉。而這個問題在IE上又格外嚴重,在面對類似情境時要特別留意,甚至放棄Angular Template回歸純jQuery模式改善效能。

留下兩點值得探討:

  1. 原本預期Edge的效能應逼近Chrom,但實測卻跟IE11近乎相同,有一種可能是KendoGrid的程式有針對不同瀏覽器優化,Edge未受惠,或是我的測試環境有問題,歡迎使用Windows 10的朋友幫忙複測看看。
  2. 直覺KendoGrid使用Angular架構產生資料列所遇到的效能議題,在巨量ng-repeat或網頁內含大量採用Template生成DOM的Directive時也將面臨相同挑戰,這點留待日後驗證。

Comments

# by player

有考慮改用 DataTables 嗎? 如果做成分頁顯示的話, 應該會快不少 https://www.datatables.net/

# by Fish

Edge用 jQuery要8.8秒!?筆誤?

# by pbnttttt

kendo grid 有提出解決方案「Virtualization 」,不知對於黑大的case是否適用? http://demos.telerik.com/kendo-ui/grid/virtualization-local-data

# by Jeffrey

to Player, KendoGrid有些難以取代的特性,像是欄位凍結、跟Angular的整合性等。不過DataTable看起來是好東西,謝謝分享。在客戶端分頁應該也是個好主意。 to Fish,是筆誤沒錯 orz,謝謝指正。 to pbnttttt,想讓使用者可以快速排序、用關鍵字篩選資料,所以沒想到Virtualization,但應該也是另一種解套的做法,謝謝補充。

# by Hina

超大量資料確實使用 Angular 會拖慢速度,我自己測試過 9MB 以上的 JSON(資料量 3K+,極端測試甚至有 20MB 的 JSON,資料筆數 8K 以上,在 Angular 要繪製到 DOM 的時候速度真的蠻慘的(使用 Chrome Canary 後來即便使用 React 也無法解決問題,實際上,超大量資料幾乎無關於用了什麼 Framework 或是 Library 了(攤手 問過 ng 與 react 這方面的高手,大部分的建議都是,用 Canvas 效能應該會好很多(苦笑

# by Jeffrey

to Hina, 八千筆資料 @@,夠猛!原本我也好奇react能否克服這種艱險,感謝先賢先烈(喂)的寶貴血淚經驗分享,已筆記。

# by pet

請問如果大量的資料( ex : 8mb) 來源是從restful api那邊透過ajax呼叫的方式取得, 那如果使用無線網路或是手機來使用application在資料取得上就會很慢. 有沒有辦法壓縮回傳回來的json data讓回傳大小減少?或是有更好的處理方式? 感謝黑大~~

# by Jeffrey

to pet, 實務上瀏覽器跟伺服器的HTTP傳輸多半會啟用內容壓縮,故實際傳遞的資料量已被壓縮過,且資料重覆性愈高,壓縮幅度愈大,你可以使用Fiddler之類的工具觀察,若壓縮後的尺寸已可接受,就不必傷腦筋了。面對大量陣列,我常用的另一種手法是不用JSON改用CSV(前題是陣列中的元素屬性一致)甚至自訂編碼格式,可以進一步提高資料密度減少體積,但程式較複雜且偏向非標準化,使用時機得拿捏。

Post a comment