1

I'm trying to understand why it takes so long to rebuild a table using javascript on Firefox 43.0.2

A simplified version of my code is below. Both the "real" code and the simple version use "publishTable() to add rows to a table. PublishTable deletes a table body element if it exists, creates a new one, adds about 9000 rows to it, and attaches the completed table body to the table.

PublishTable runs on load, and when the user clicks a "go" button. Therefore, I expect performance to be similar on load and rebuild.

When the "real" page first loads, Firefox takes about 300ms to construct the table [according to performance.now()]. When the alert() announcing this result is closed, i can immediately scroll the page up and down.

But if i click the "go" button to rebuild the table, Firefox spins its wheels for tens of seconds (or more) after I close the alert() dialog. A "stop script?" dialog can appear more than once. The simple version behaves similarly.

So:
Why
is the performance so radically different between initial build, and rebuild? It seems clearly possible to build the table in 300ms! Is there anything I can do about it?

Some further observations:
IE's performance is much worse on initial load, and as bad on rebuild. Chrome's performance is pretty good: 2 seconds to build or rebuild.

If I use innerHTML, rather than insertRow, appendChild, etc., results are similar.

If i remove the line attaching the table body to the table, the wheel-spinning symptom does not occur.

In the "waterfall" chart (in the Firefox performance tool), the the "DOM event" takes up much more time than the "event handler" (which I think covers the run-time of my code), and I don't know what that means. What is happening between the time the js stops running, and the DOM event ends, that doesn't fall in one of the other waterfall categories?

The DOM event is followed by a brief time to recalculate style, a time to paint, and then a sequence of many "cycle collection" periods, then "incremental gc", "cc graph reduction", "cycle collection", "graph reduction", and so on, for tens of seconds. In one case, the performance call-tree allocated 49 seconds to "Gecko" (which seems to be idle time) and another 25 seconds to "graphics" (and within that, a mere 1 second is allocated to publishTable()). Is there something here I can act on?

I'm out of reasonable ideas for further analysis, or how I might modify the js. I don't understand enough about the performance information to act on it. (And now, after timing with IE and Chrome, I'm not even sure to whom the question should be addressed.)

Is there a fundamental error in the code? A better table construction strategy? A way to use the performance tool to understand the problem? A bug in Firefox? (And now I'm going to do the thing on the server side. But I'm still curious about what's going on.)

<!DOCTYPE html>    
<html>     
<head>    
<meta charset="UTF-8">    
</head>    
<body>    
<div id='a'>    
<button type="button" disabled id="btnGo">Go</button><br />    
    <button type="button"  id="btnQ">?</button><br />    
<table id="tideTable" style="Width:40%;margin-left:auto;margin-right:auto;">    
</table>    
</div>    
<div id="b">    
hello    
</div>    
<script>    
(function() {
var mmm = ['one', 'two', 'three', "four", "five", "six", "seven"];
function publishTable() {
//  The user may run this several times, varying some parameters each time.
    var tStart = performance.now();
    var table = document.getElementById('tideTable');
//  var tableBody = table.getElementsByTagName('tbody')[0];
    var tableBody = table.tBodies[0];
    if (tableBody) {
        tableBody.parentNode.removeChild(tableBody);
        }
    showHeight();
    tableBody = document.createElement('tbody');
    for (var i=0; i < 8500; i++) {
        appendTableRow(tableBody, mmm);
        }
    table.appendChild(tableBody);
    document.getElementById("btnGo").disabled = false;
    alert("Time: " + (performance.now() - tStart) +  "ms");
    showHeight();
}

function appendTableRow(tableBody, columns) {
    var cell;
    var textNode;
    var row = tableBody.insertRow(-1);
    for (var i = 0; i < columns.length; i++) {
        cell = row.insertCell(i);
        textNode = document.createTextNode(columns[i]);
        cell.appendChild(textNode);
    }
}
    function showHeight(){
        var el = document.getElementById('b');
        var topPos = el.offsetTop;
        alert("position: " + topPos);
    }

document.getElementById("btnGo").addEventListener("click", publishTable);
document.getElementById("btnQ").addEventListener("click", showHeight);
publishTable();
})();
</script>
</body>
</html> 
captain puget
  • 107
  • 1
  • 5
  • 1
    I suspect you have a browser plugin that's monitoring DOM changes. Try disabling all your plugins and see if you still have the same problem. – Barmar Dec 28 '15 at 22:25
  • 1
    Just a small suggestion, I would only publish a page worth at a time, IE: if your page can show 100, do the first 200, then 100 at a time. That will allow the browser to show results faster, then you won't have the issue. But @Barmar is probably correct. Check your browser plugins. – Jacques ジャック Dec 28 '15 at 22:27
  • Put your `` above your `

    ` and watch your table move ;)

    – zer00ne Dec 28 '15 at 22:29
  • @zer00ne What difference does that make? He's not running it until the user clicks on a button. – Barmar Dec 28 '15 at 22:30
  • Didn't see that, I just saw the glaring error. It's in a IIEF, so yeah I think it does make a big dif – zer00ne Dec 28 '15 at 22:31
  • the script is at the bottom because I want publishTable to fire after the html has loaded, and I want it all to be contained in an anonymous function. If this isn't the way to accomplish those ends, or there is another way (in vanilla js) please advise. Had to look up IIEF. Best to append "javascript" to the search string :-) – captain puget Dec 29 '15 at 04:36
  • @Barmar, I'm feeling kinda dumb here, how can I 'disable' plugins (all the "developer" stuff)? My "Plugins" list includes nothing that looks remotely like a debug/development function, and in the "extensions" only "valence" looks important. I disabled it and...no change. The hypothesis seems reasonable except...how does it explain the zippy performance on load? – captain puget Dec 29 '15 at 04:52
  • @user2868847 Parsing the original HTML is a single, highly-optimized action, it doesn't result in `DOMModified` events for each element that's changed. Although I think the way you're doing this, with a detached element that you append at the end, should only cause one event as well, so I think my original hypothesis was unlikely. – Barmar Dec 29 '15 at 15:12
  • FYI, there's a Chrome Extension called "Toggle Extensions On/Off" that can be used to disable/enable all extensions with one click. Maybe there's something similar for FF. – Barmar Dec 29 '15 at 15:13
  • @Barmar, I think the plugin you're referring to has to be invoked (under Developer Toools). If you start the Web Console loading, it seems to know nothing of the js code. To see the code, you have to first start the WC, then load or refresh. It is (apparently) disabled by default. – captain puget Dec 30 '15 at 03:30
  • Following an idea below, I tried to force reflow following removal of tBody. The thought was that it would put the internals in the same state they were when the page first loaded - before rebuilding the table. I did that by adding a div beneath the table, and requesting its position (offsetTop) after removing the tbody. Sad to say it seemed to have no effect (in ff). – captain puget Dec 30 '15 at 05:12
  • went to about:config and set devtools.debugger.enabled to false, devtools.performance.enabled to false.\ – captain puget Dec 30 '15 at 18:24

3 Answers3

1

I guess, it could be because of the removal of existing items, before inserting the new ones. You could try the following:

  1. meassure what happens to the performance, if you just extend the table, without deletion

  2. build the table before inserting it, e.g. make a variable tableContent put the rows in it, and then add tableContent to the table, that should be faster, because your browser has to rerender the page on every insert.

And I would advice you to consider the use of angularJS, if you plan to make the table dynamic

user1403333
  • 98
  • 12
  • Did you read the code? That's how he's doing it, he adds the rows to a disconnected `tableBody` element, then at the end he appends `tableBody` to `table`. – Barmar Dec 28 '15 at 22:31
  • I'm not sure I understand the reason for "angular JS, if you plan to make the table dynamic". The "go" button is about as dynamic as I plan to get with this thing (I didn't publish the settable parameters, but there are only a few, you set them, you press go. You're going to study the result a bit before you make another change - IF you make another change. ) It seems to me some simple js is adequate and, well, simple. But you do make me feel a bit sheepish that I have not learned aJS :-( – captain puget Dec 29 '15 at 06:32
  • @Barmar, i think he has a point. At startup, the state is no-table-painted. At the first Go, the state is table-fully-painted. I don't think logical removal of the tbody changes that. (But then why is performance on subsequent clicks of "Go" so much better?) – captain puget Dec 30 '15 at 03:23
1

Your JS is minified and in a CloudFront CDN.

The first demo is async and the second demo is defer

Async

https://jsfiddle.net/zer00ne/6m9f24j5/

Defer

https://jsfiddle.net/zer00ne/fcpy9z0c/

Results

Same times.


142ms on Firefox loading.

Avg of 230ms on each click event.

846ms on Chrome loading.

Avg of 930ms on each click event.

Put your <script> tags before the closing </body> tag

https://jsfiddle.net/zer00ne/y7mguyry/

Firefox

Crome

(function() {
  var mmm = ['one', 'two', 'three', "four", "five", "six", "seven"];

  function publishTable() {
    //  The user may run this several times, varying some parameters each time.
    var tStart = performance.now();
    var table = document.getElementById('tideTable');
    var tableBody = table.getElementsByTagName('tbody')[0];
    if (tableBody) {
      tableBody.parentNode.removeChild(tableBody);
    }
    tableBody = document.createElement('tbody');
    for (var i = 0; i < 8500; i++) {
      appendTableRow(tableBody, mmm);
    }
    table.appendChild(tableBody);
    document.getElementById("btnGo").disabled = false;
    alert("Time: " + (performance.now() - tStart) + "ms");
  }

  function appendTableRow(tableBody, columns) {
    var cell;
    var textNode;
    var row = tableBody.insertRow(-1);
    for (var i = 0; i < columns.length; i++) {
      cell = row.insertCell(i);
      textNode = document.createTextNode(columns[i]);
      cell.appendChild(textNode);
    }
  }

  document.getElementById("btnGo").addEventListener("click", publishTable);
  publishTable();
})();
<button type="button" disabled id="btnGo">
  Go</button>
<br />
<table id="tideTable" style="Width:40%;margin-left:auto;margin-right:auto;">
</table>
Community
  • 1
  • 1
zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • With https://jsfiddle.net/zer00ne/y7mguyry/, i got a result similar to my result after making @Anthony's apparently innocuous change: the load goes fine, the first click of the "Go" button results in a "stop-this-script" dialog, and after clicking "stop....", all subsequent clicks of the Go button have a good result: no script gone nuts. I have only looked at that one item of yours at this point though. – captain puget Dec 29 '15 at 06:23
  • I never get a "stop-this-script" dialog, that being said, I only use Firefox to test, not to browse and it has no extensions. Basically my Firefox installation is basic, it performs like lightning and makes my Chrome look pathetically slow relatively speaking. On the other hand, my Chrome is the workhorse and has extensions, but every test result is still under a second, that's not too shabby. FYI, when I tested, I cleared my cache. – zer00ne Dec 29 '15 at 17:56
  • I just ran the jsfiddle demos labeled "asynch" and "defer" (on firefox). In both cases, I got the "do you want to kill this script" dialog. Like the others, both those work quickly - usually. But not every time. The first, or second, time, they present the kill dialog. – captain puget Dec 30 '15 at 17:22
1

I tried swapping out the line:

var tableBody = table.getElementsByTagName('tbody')[0];

with the built-in getter:

var tableBody = table.tBodies[0];

and this seems to stabilize the build time. Still a bit slow, but near-consistent times reported for initial build and rebuilds.

This could be coincidental, but something you may want to mess around with.

Anthony
  • 36,459
  • 25
  • 97
  • 163
  • Anthony, I don't understand why but this may in fact be doing something. I reload the page (re-enter the url, not just "refresh" [and I wish I understood why those are not the same thing]). Then click go; I get the usual poor performance, and click "stop the script". then I wait a few minutes, then click Go and voila, there seems to be no problem. And I click Go again and again, and...no problem. This is becoming somewhat of a random walk, and not easy to repeat. I don't know, for example, if it is the change you suggested, or the fact that I hit "stop script" & waited. 2 mny vars! – captain puget Dec 29 '15 at 05:11