Статьи

Оптимизация JavaScript для Windows 8: использование инструмента анализа производительности Visual Studio

Производительность всегда является важным моментом в любом проекте. И вы должны быть хорошо подготовлены к тому, чтобы находить потенциальные проблемы в своем коде, чтобы обеспечить наилучшее взаимодействие с пользователем независимо от используемого им устройства.

В моем случае у меня возникла проблема с UrzaGatherer на низкокачественном оборудовании: потребовалось много времени, чтобы открыть и отобразить первый экран, который в основном представляет собой просмотр списка, представляющий список расширений:

образ

На моем компьютере (который мы можем назвать высокопроизводительным компьютером Sourire) у меня не было проблем, потому что грубая мощность моей системы компенсирует проблему производительности.

Это первый урок этой статьи: ВСЕГДА ПРОВЕРЯЙТЕ ВАШУ ЗАЯВКУ НА НИЗКОЕ КОНЕЧНОЕ ОБОРУДОВАНИЕ! Windows 8 (и Windows RT) может работать на таком большом количестве оборудования, что вы не можете позволить себе не проводить тестирование на младших устройствах.

Когда я обнаружил эту проблему, я решил использовать отличный инструмент Visual Studio 2012: интегрированный инструмент анализа производительности, который доступен в меню «Отладка» (или «Анализ»):

образ

Использование инструмента анализа производительности Visual Studio

Чтобы использовать этот инструмент, вам просто нужно запустить его с помощью [Start Performance Analysis], если вы хотите интегрировать начальную фазу вашего приложения в ваш анализ, или с помощью [Start Performance Analysis Paused], если вы хотите проанализировать конкретную деталь вашего приложения.

В моем случае мне нужно было проанализировать, почему потребовалось много времени, чтобы начать, поэтому я использовал первое меню.

Затем отобразится следующий экран, и ваше приложение будет запущено:

образ

В любой момент вы можете остановить или приостановить анализ.

И когда анализ закончен, вы можете получить доступ к отчету профилирования:

образ

Обратите внимание на следующее, чтобы понять этот отчет:

  • Инклюзивное время определяет время, проведенное внутри функции, включая дочерние функции
  • Эксклюзивное время определяет время, проведенное внутри функции БЕЗ дочерних функций

Нахождение проблемы

Using this report, you should be able to find some bottlenecks.

One obvious way to use this report is to analyze the hot path which shows you the main consuming path code.

In my case, we can see that something related to the UI (processElement —> marginLeft) is displayed. I you click on a line, you can see the associated code:

образ

For this example, there is no code because the marginLeft property is inside the rendering engine (IE 10) and not inside the WinJS library. But if you click on calling functions, you will be able to see calling code:

образ

Analyzing this part of code, I can see that it is related to WinJS UI code so for now, I will leave it unchanged Sourire

Back to the main report screen, we can also see that JSON.parse if the function with the most individual work associated (13,47% of the total time was spent inside this function). This is normal as I load a 20Mb json file which contains more than 20 000 cards information. I want to have all my cards in memory to be able to search through them quickly so I decided to also leave it unchanged.

In my quest to reduce the loading time of my application I then went to the functions page:

образ

I found that a specific function inside ui.js was called more than 379 000 times! By clicking on the line, I discovered this code:

образ

I added a breakpoint on it to understand that the listview used these function to determine the occupancyMap which is used to populate and draw the listview.

But 379 000 calls is a bit too much for only 40 entries in my screen:

образ

And then I realized my error!! In order to be able to display the random card AND the expansions tiles, I decided to use a really small cell size for the grid (The items must have a size proportional with the cell size so with a really small size I was able to draw any virtual size):

ui.setOptions(listView, {
    oniteminvoked: this.itemInvoked,
    itemTemplate: this.itemRenderer,
    groupHeaderTemplate: this.headerRenderer,
    layout: new ui.GridLayout({
        groupHeaderPosition: "top",
        groupInfo: function () {
            return {
                enableCellSpanning: true,
                cellWidth: 28,
                cellHeight: 13
            };
        }
    })
});

But by doing that, I’m forcing the listview to go through a too important count of cells to build its layout!!

Fixing the problem

Fixing the problem is obvious. I just need to use bigger cells (so there will be less cells to go through):

ui.setOptions(listView, {
    oniteminvoked: this.itemInvoked,
    itemTemplate: this.itemRenderer,
    groupHeaderTemplate: this.headerRenderer,
    layout: new ui.GridLayout({
        groupHeaderPosition: "top",
        groupInfo: function () {
            return {
                enableCellSpanning: true,
                cellWidth: 280,
                cellHeight: 130
            };
        }
    })
});

I also need to update my CSS to accommodate the new layout and voila!

The new performance report is now the following:

образ

The UI code is now called only 6 867 times which is clearly more logical.

With this optimization, UrzaGatherer launches twice as fast!

Another small thing…

Another thing really simple to fix is related to Math.floor which is called more than 130 000 times. I used it with the code for cropping cards to generate live tiles:

var pixels = imageData.data;
for (var index = 0; index < pixels.length; index += 4) {
    var r = pixels[index];
    var g = pixels[index + 1];
    var b = pixels[index + 2];

    var luminance = Math.floor(r * 0.3 + g * 0.59 + b * 0.11);

    luminance += (255 - luminance) * amplification;

    if (luminance > 255)
        luminance = 255;

    pixels[index] = luminance;
    pixels[index + 1] = luminance;
    pixels[index + 2] = luminance;
}

To fix this issue, just use a bitwise operator with 0 (old developer trick ^^):

var pixels = imageData.data;
for (var index = 0; index < pixels.length; index += 4) {
    var r = pixels[index];
    var g = pixels[index + 1];
    var b = pixels[index + 2];

    var luminance = (r * 0.3 + g * 0.59 + b * 0.11) >> 0;

    luminance += (255 - luminance) * amplification;

    if (luminance > 255)
        luminance = 255;

    pixels[index] = luminance;
    pixels[index + 1] = luminance;
    pixels[index + 2] = luminance;
}

And the result:

образ

Far better no?

Obviously many other things can be optimized but I just wanted to write an article and not a book Clignement d'œil