17

In a Lightning app that I am building, there is a reasonably large table (has 7 rows for each contact we are dealing with, there can easily be 40+ contacts that we are showing data from). Most of the rows are hidden most of the time (they are shown based on which day the user chooses to show).

I was having problems getting Aura to render this table quickly, I made a few tweaks to speed things up, but performance is still slower than I'd like (and also the time to render was not scaling linearly with the number of rows).

So I thought for this table, I'd try bypassing Aura's aura:iteration and just build the table using jQuery. I then discovered that using jQuery was many times slower than aura:iteration. It was taking nearly two minutes to render my table for 40 contacts.

I then turned off LockerService and found that the performance returned to what I'd hope from using jQuery. With LockerService the table renders in just over a second.

For my 40 contact case, with LockerService the code runs 81 times slower than with it off.

I can't sensibly show the actual code I used in our app. But this is some benchmark code I put together that simulates the amount of DOM manipulation that the real code does:

    var selectedDay = 3;

    var build = function (numContacts) {
        var start = Date.now();
        var tablebody = $("<tbody/>");
        for (var contact = 0; contact < numContacts; contact++) {
            for(var day = 0; day < 7; day++) {
                var tr = $("<tr class='" + (selectedDay === day ?
                    'slds-hint-parent' : 'slds-hint-parent slds-hide') + "'/>");
                tr.append("<td>" + contact + "</td>");
                for(var hour = 6; hour < 22; hour++) {
                    tr.append("<td class='officeDivider' />");
                    tr.append("<td class='officeHourIn' />");
                    tr.append("<td class='officeHourIn' />");
                    tr.append("<td class='officeHourIn' />");
                    tr.append("<td class='officeHourIn' />");                   
                }
                tablebody.append(tr);           
            }
        }

        var elapsed = Date.now() - start;
        console.log("Contacts " + numContacts + " time " + elapsed  + " per contact " + elapsed/numContacts);
    };

    build(10);
    build(20);
    build(40);

The results of running this are (all times in milliseconds):

Contacts    Without LS  With LS
10                 329    15432
20                 596    39548
40                1289   104684

Am I using jQuery poorly, or is this the expected overhead of having LockerService turned on ?

Is there a way I can use jQuery with LockerService and not incur this massive performance degradation ?

When I profile the code, when LockerService is enabled pretty much all the time is spent in Aura's sanitization routines.

Update:

As suggested by doug-chasman I have created a quick and dirty vanilla DOM version of the code:

    var buildNative = function (numContacts) {
        var start = Date.now();
        var tablebody = document.createElement("tbody");
        for (var contact = 0; contact < numContacts; contact++) {
            for(var day = 0; day < 7; day++) {
                var tr = document.createElement("tr");
                var td = document.createElement("td");
                td.textContent = contact;
                tr.appendChild(td);
                for(var hour = 6; hour < 22; hour++) {
                    td = document.createElement("td");
                    td.className = "officeDivider";
                    tr.appendChild(td);

                    td = document.createElement("td");
                    td.className = "officeHourIn";
                    tr.appendChild(td);

                    td = document.createElement("td");
                    td.className = "officeHourIn";
                    tr.appendChild(td);

                    td = document.createElement("td");
                    td.className = "officeHourIn";
                    tr.appendChild(td);

                    td = document.createElement("td");
                    td.className = "officeHourIn";
                    tr.appendChild(td);
                }
                tablebody.appendChild(tr);          
            }
        }
        console.log("Contacts " + numContacts + " time " + elapsed  + " per contact " + elapsed/numContacts);
    };

    buildNative(10);
    buildNative(20);
    buildNative(40);

This of course runs much faster. With LockerService turned off it runs at about 2ms per contact, compared to the 30ms per contact using jQuery with LockerService turned off.

However there is still a massive overhead with LockerService turned on, ~170ms per contact compared to ~2ms with it off. So the same ~80 times slow down I saw with jQuery.

The 40 contact case takes over 6 seconds with createElement and LockerService, which is still 4 times slower than the jQuery version with LockerService off.

Contacts    jQuery LS off    jQuery LS on   createEl LS off   createEl LS on
10                    329           15432                22             1483
20                    596           39548                37             3715
40                   1289          104684                66             6702

I'm hoping that I'm just doing something wrong ?

6

This is the first report of performance issues related to LockerService (both internally to the company and externally). The sanitizatiin routine you mention seeing the high overhead in is from DOMPurify.sanitize(), correct? This is a 3rd party string-to-HTML sanitizer used in LS's SecureElement.innerHTML= setter that is invoked from jQuery's string to DOM methods such as .append(). I expect that the overhead can be avoided by directly creating the td's using document.createElement("TD") (same for TR etc). Can you try that and get new timings? We will need to work with Cure53, the authors of DOMPurify, to see about improving performance of their code.

  • 2
    The only reason I was experimenting with jQuery was because I wasn't getting the performance I wanted from aura:iteration. While aura:iteration doesn't take the same massive hit from LS, it is a lot slower than jQuery to begin with. Had aura:iteration not been giving slow render times then I'd have never have started playing with alternatives. – andynormancx Jul 28 '16 at 8:37
  • 1
    80 times slower, createElement with LS on is 80 times slower than with LS off. The 4 times comparison was between createElement with LS on and jQuery with LS off. – andynormancx Jul 28 '16 at 8:39
  • 1
    For reference, the overhead of turning on LS with my original aura:iteration is about a 2-3 times slow down. – andynormancx Jul 28 '16 at 9:26
  • 1
    Hey, we are having the same issue with jQuery Autocomplete plugin. We had to limit number of rows because if there were more than 50 we noticed performance degradation. With 1000 rows returned the browser freezes totaly. Without the Locker Service ON it works much faster. – diff Nov 15 '16 at 10:15
  • 2
    @DougChasman 4x slower is still A LOT slower. – dzh Feb 28 '18 at 11:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.