MSEdge super slow when creating >1000 elements in DOM - javascript

Every other browser gets through this JS almost instantly, see below benchmarks
It is slowing down only on this code in a function (rest runs instant), which is so simple its making me confused as to what I can do to fix it.
I cannot reproduce this in JSFiddle, here is the equivalent code https://jsfiddle.net/5ax7mshz/ . I can see with performance.now() that this is the only code slowing it down in our app, and its a purely JS+JQ app, there are no other variables here...somehow it takes 600x as long in our app as it does in the fiddle. Very much at my wits' end here.
Thanks All!
var options = "";
for (var i = 0; i < data.Vendor.length; i++) {
options += "<option value='" + data.Vendor[i].VendorID + "'>" + data.Vendor[i].Name + "</option>";
}
$el.append(options);
Actual benchmarking values
Average Edge benchmark (trying some of the solutions here so far unfortunately still resulted in similar values)
28155 ms total
6968 ms list1 (2232 items)
21179 ms list2 (4016 items)
7.6 ms list3 (10 items)
Here's Chrome if you want some laughs
55.07 ms total
21.09 ms list1 (2232 items)
32.18 ms list2 (4016 items)
1.79 ms list3 (10 items)
Aaaand Firefox takes 46ms total

Is the append you're using from jQuery?
I don't have Edge on this machine, but I do have IE, so I'll use that as a reasonable equivalent. I tried several 4000 select-option builds:
String concat followed by single jQuery append
jQuery append per entry
appendChild per element (no jQuery)
add new Option(... to select.options (no jQuery)
For timing I'm using
console.time('build');
build();
console.timeEnd('build');
And the results, two runs - fastest time (ms):
IE11 805 2120 605 6033
FF80 48 190 26 21
CR84 95 342 52 214
With 4000 elements, this is comparison to your list2, while the first timing number should match your results (although note I made up the value/text per entry, since I don't have access to your list). Note Chrome is significantly slower than yours (95 vs 32 - 3x), and IE is significantly faster (805 vs 2117 - 26x). For my Chrome process I have many open tabs, but there's also an update waiting, for my IE process I have nothing else open, but it's also not exactly the same as Edge (assuming you're not using Webkit Edge - if you are, then it's a very different engine)
The jQuery based builds are both poor performers. This isn't really a fair fight, since this method has to parse a string and interpret it as HTML. Build 4 also did poorly on everything but FireFox. So, it looks like vanilla javascript with appendChild is the best approach.
To map this back to your data (which may have further object access delays):
function build(data, select) {
var n=data.Vendor.length;
for (var i=0; i<n; i++) {
var item=data.Vendor[i];
var opt=document.createElement("option");
opt.value=item.VendorID;
opt.innerText=item.Name;
select.appendChild(opt);
}
}
// Call egs:
// build(data, document.getElementById("itemList"));
// build(data, document.forms[0].itemSelect);

ended up doing this to at least make it 5-7 seconds total (5000-7000ms) instead of 28, which is 'ok' as this only affects a couple clients, rest use Chrome (50ms) and FF (40ms) and IE11+ (300ms)
document.getElementById("vendor").innerHTML = "";
var i = 0;
var loop_num = 0;
var vendorCount = data.Vendor.length;
var vendorPiece;
var k = 0;
var options = [];
if (isEdge) {
//add optgroups to select
var num_selects = Math.ceil(vendorCount / 1000); //say, 3 for 2323
for (var jj = 1; jj <= num_selects; jj++) {
var optgroup = document.createElement("optgroup");
optgroup.label = "";
optgroup.id = "vendor" + jj;
document.getElementById("vendor").appendChild(optgroup);
if (jj == 1) {
var blankopt = document.createElement("option");
blankopt.value = "";
blankopt.text = "";
document.getElementById("vendor" + jj).appendChild(blankopt);
}
}
} else {
var blankopt = document.createElement("option");
blankopt.value = "";
blankopt.text = "";
document.getElementById("vendor").appendChild(blankopt);
}
while (i < vendorCount) {
var pieceEndIndex =
Math.min(1000, vendorCount - i) == 1000 ? i + 1000 : vendorCount;
k = 0;
options = [];
vendorPiece = data.Vendor.slice(i, pieceEndIndex);
for (var j = 0; j < vendorPiece.length; j++) {
var vendor = vendorPiece[j];
options[k++] = "<option value='";
options[k++] = vendor.VendorID;
options[k++] = "'>";
options[k++] = vendor.Name;
options[k++] = "</option>";
}
var vendor_id = !isEdge ? "vendor" : "vendor" + (loop_num + 1);
document.getElementById(vendor_id).innerHTML += options.join("");
i += vendorPiece.length;
loop_num++;
}

Related

Whats the difference between creating a div as a string versus using createElement()?

I know there is some functions made available to creating a node div with createElement(), but is there any other differences? performance? usability? etc...
String div
$('body').append("<div>foo</div>");
versus
Creating a node div
var newDiv = document.createElement("div");
var divContent = document.createTextNode("foo");
newDiv.appendChild(divContent);
$('body').append(newDiv);
Using the string (example 1) is actually better performing than creating nodes (second example). This is because jQuery already does what is in your node example for you, so you're sort of doing double work in the second example.
Because people didn't seem to believe me, here's a simple benchmark I just made and ran, doing 10,000 operations of each method:
var start = performance.now();
for (var i = 0; i < 10000; i++) {
$('body').append("<div>foo</div>");
}
console.log('Took ' + (performance.now() - start) + ' ms');
Took 70 ms averaged over 10 tests.
var start = performance.now();
for (var i = 0; i < 10000; i++) {
var newDiv = document.createElement("div");
var divContent = document.createTextNode("foo");
newDiv.appendChild(divContent);
$('body').append(newDiv);
}
console.log('Took ' + (performance.now() - start) + ' ms');
Took 110 ms averaged over 10 tests.
Keep in mind that this is all dependent on browser, jquery version, other javascript on the page, etc. That said, if you're appending a number of elements to the dom where this performance actually matters I'd be very surprised.

How to add a large number of items to an HTML selectObject

I need to populate eight selectObject pulldown objects on a page with several thousand (8192) items each. I'm currently doing this in Javascript the only way I know how:
var iCount;
var option1;
var selectObject1 = document.getElementById('ifbchan');
for(iCount = 0; iCount < 8192; iCount++)
{
option1=document.createElement("option");
option1.text = "Out " + iCount;
option1.value=iCount;
try
{
selectObject1.add(option1, selectObject1.options[null]);
}
catch (e)
{
selectObject1.add(option1, null);
}
}
selectObject1.selectedIndex = 0;
This method works properly but is extremely slow! Each of these 8K loops takes something like 10 seconds to complete. Multiply by 8 different loops and the problem is obvious. Is there any other way to add large numbers of items to a drop down list that would be faster? Any faster alternatives to the drop down control for presenting a large list of items? Thanks for any ideas.
~Tim
I'd try the following:
var elements = ""
var i;
for(i= 0; i < 8192; i++){
elements += "<option value='"+ i + "'>Out " + i + "</option>";
}
document.getElementById("ifbchan").innerHTML = elements;
This way you only perform one action on the DOM per loop not 8000+.
Oh and here's one I prepared earlier: http://jsfiddle.net/3Ub4x/
Few things before the answer.
First of all I do not think that the best way to do this is a server side implementation. If you can do something on the client you should do this and not touch your server (if it is not security related).
Second thing - why exactly do you need 8000 elements in select list? Think as a user of your app, who would like to scroll through 8000 elements just to select his element? As it was mentioned before - autocomplete sounds much more suitable.
And right now is an answer:
Your original approach is here: it takes approximately 1724 miliseconds to complete for 10000 elements (You can see this by running the script and checking inspector).
var start = new Date();
var n = 10000;
var iCount;
var option1;
var selectObject1 = document.getElementById('ifbchan');
for(iCount = 0; iCount < n; iCount++)
{
option1=document.createElement("option");
option1.text = "Out " + iCount;
option1.value=iCount;
try
{
selectObject1.add(option1, selectObject1.options[null]);
}
catch (e)
{
selectObject1.add(option1, null);
}
}
selectObject1.selectedIndex = 0;
var time = new Date() - start;
console.log(time);
I do not like a lot of this code (it is too many lines) so I will rewrite it in jquery.
var start = new Date();
var n = 10000;
for (var i = 0; i<n; i++){
$("#ifbchan").append("<option value="+i+">"+i+"</option>")
}
var time = new Date() - start;
console.log(time);
The next fiddle is here. Much less lines, and some time improvement. Now it is 1312 milliseconds. But it append new element in every loop.
The next fiddle get rid of this.
var start = new Date();
var n = 10000;
var html = '';
for (var i = 0; i<n; i++){
html += "<option value="+i+">"+i+"</option>";
}
$("#ifbchan").append(html);
var time = new Date() - start;
console.log(time);
Wow, now it is only 140 milliseconds.
for (var i = 0; i<n; i++){
select.append('<option value='+i+'>'+i+'</option>');
}
Beware, this doesn't work in IE. See this link -
Using innerHTML to Update a SELECT – Differences between IE and FF

For-loop performance: storing array length in a variable

Consider two versions of the same loop iteration:
for (var i = 0; i < nodes.length; i++) {
...
}
and
var len = nodes.length;
for (var i = 0; i < len; i++) {
...
}
Is the latter version anyhow faster than the former one?
The accepted answer is not right because any decent engine should be able to hoist the property load out of the loop with so simple loop bodies.
See this jsperf - at least in V8 it is interesting to see how actually storing it in a variable changes the register allocation - in the code where variable is used the sum variable is stored on the stack whereas with the array.length-in-a-loop-code it is stored in a register. I assume something similar is happening in SpiderMonkey and Opera too.
According to the author, JSPerf is used incorrectly, 70% of the time. These broken jsperfs as given in all answers here give misleading results and people draw wrong conclusions from them.
Some red flags are putting code in the test cases instead of functions, not testing the result for correctness or using some mechanism of eliminating dead code elimination, defining function in setup or test cases instead of global.. For consistency you will want to warm-up the test functions before any benchmark too, so that compiling doesn't happen in the timed section.
Update: 16/12/2015
As this answer still seems to get a lot of views I wanted to re-examine the problem as browsers and JS engines continue to evolve.
Rather than using JSPerf I've put together some code to loop through arrays using both methods mentioned in the original question. I've put the code into functions to break down the functionality as would hopefully be done in a real world application:
function getTestArray(numEntries) {
var testArray = [];
for (var i = 0; i < numEntries; i++) {
testArray.push(Math.random());
}
return testArray;
}
function testInVariable(testArray) {
for (var i = 0; i < testArray.length; i++) {
doSomethingAwesome(testArray[i]);
}
}
function testInLoop(testArray) {
var len = testArray.length;
for (var i = 0; i < len; i++) {
doSomethingAwesome(testArray[i]);
}
}
function doSomethingAwesome(i) {
return i + 2;
}
function runAndAverageTest(testToRun, testArray, numTimesToRun) {
var totalTime = 0;
for (var i = 0; i < numTimesToRun; i++) {
var start = new Date();
testToRun(testArray);
var end = new Date();
totalTime += (end - start);
}
return totalTime / numTimesToRun;
}
function runTests() {
var smallTestArray = getTestArray(10000);
var largeTestArray = getTestArray(10000000);
var smallTestInLoop = runAndAverageTest(testInLoop, smallTestArray, 5);
var largeTestInLoop = runAndAverageTest(testInLoop, largeTestArray, 5);
var smallTestVariable = runAndAverageTest(testInVariable, smallTestArray, 5);
var largeTestVariable = runAndAverageTest(testInVariable, largeTestArray, 5);
console.log("Length in for statement (small array): " + smallTestInLoop + "ms");
console.log("Length in for statement (large array): " + largeTestInLoop + "ms");
console.log("Length in variable (small array): " + smallTestVariable + "ms");
console.log("Length in variable (large array): " + largeTestVariable + "ms");
}
console.log("Iteration 1");
runTests();
console.log("Iteration 2");
runTests();
console.log("Iteration 3");
runTests();
In order to achieve as fair a test as possible each test is run 5 times and the results averaged. I've also run the entire test including generation of the array 3 times. Testing on Chrome on my machine indicated that the time it took using each method was almost identical.
It's important to remember that this example is a bit of a toy example, in fact most examples taken out of the context of your application are likely to yield unreliable information because the other things your code is doing may be affecting the performance directly or indirectly.
The bottom line
The best way to determine what performs best for your application is to test it yourself! JS engines, browser technology and CPU technology are constantly evolving so it's imperative that you always test performance for yourself within the context of your application. It's also worth asking yourself whether you have a performance problem at all, if you don't then time spent making micro optimizations that are imperceptible to the user could be better spent fixing bugs and adding features, leading to happier users :).
Original Answer:
The latter one would be slightly faster. The length property does not iterate over the array to check the number of elements, but every time it is called on the array, that array must be dereferenced. By storing the length in a variable the array dereference is not necessary each iteration of the loop.
If you're interested in the performance of different ways of looping through an array in javascript then take a look at this jsperf
According to w3schools "Reduce Activity in Loops" the following is considered bad code:
for (i = 0; i < arr.length; i++) {
And the following is considered good code:
var arrLength = arr.length;
for (i = 0; i < arrLength; i++) {
Since accessing the DOM is slow, the following was written to test the theory:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>my test scripts</title>
</head>
<body>
<button onclick="initArray()">Init Large Array</button>
<button onclick="iterateArraySlowly()">Iterate Large Array Slowly</button>
<button onclick="iterateArrayQuickly()">Iterate Large Array Quickly</button>
<p id="slow">Slow Time: </p>
<p id="fast">Fast Time: </p>
<p id="access"></p>
<script>
var myArray = [];
function initArray(){
var length = 1e6;
var i;
for(i = 0; i < length; i++) {
myArray[i] = i;
}
console.log("array size: " + myArray.length);
}
function iterateArraySlowly() {
var t0 = new Date().getTime();
var slowText = "Slow Time: "
var i, t;
var elm = document.getElementById("slow");
for (i = 0; i < myArray.length; i++) {
document.getElementById("access").innerHTML = "Value: " + i;
}
t = new Date().getTime() - t0;
elm.innerHTML = slowText + t + "ms";
}
function iterateArrayQuickly() {
var t0 = new Date().getTime();
var fastText = "Fast Time: "
var i, t;
var elm = document.getElementById("fast");
var length = myArray.length;
for (i = 0; i < length; i++) {
document.getElementById("access").innerHTML = "Value: " + i;
}
t = new Date().getTime() - t0;
elm.innerHTML = fastText + t + "ms";
}
</script>
</body>
</html>
The interesting thing is that the iteration executed first always seems to win out over the other. But what is considered "bad code" seems to win the majority of the time after each have been executed a few times. Perhaps someone smarter than myself can explain why. But for now, syntax wise I'm sticking to what is more legible for me:
for (i = 0; i < arr.length; i++) {
if nodes is DOM nodeList then the second loop will be much much faster because in the first loop you lookup DOM (very costly) at each iteration. jsperf
This has always been the most performant on any benchmark test that I've used.
for (i = 0, val; val = nodes[i]; i++) {
doSomethingAwesome(val);
}
I believe that the nodes.length is already defined and is not being recalculated on each use. So the first example would be faster because it defined one less variable. Though the difference would be unnoticable.

Read and Write JQuery Statements in One Loop

I have the following jQuery statement in a loop. #MainContent_gvDemographic and #tblFreez are two tables in a page.
$("#MainContent_gvDemographic").find(str)
.css("height", $("#tblFreez")
.find(str)
.css("height"))
When there are many steps in the loop, it takes a very long time to complete. To fix the problem, I then use two loops, one for reading the height of $("#tblFreez").find(str), the other for writing the height into $("#MainContent_gvDemographic").find(str), and use an array to carry the height data between the two loops. It becomes much faster now. Does anyone know why the two solutions have such big difference in performance? The computational complexity looks the same to me.
All right, here are the two complete version.
Original:
function FixHeight() {
var rowCount = $('#tblFreez tr').length;
for (var i = 0; i < rowCount; i++) {
var str = "";
if ($.browser.msie) {
str = "tr:eq(" + i + ") td";
}
else {
str = "tr:eq(" + i + ")";
}
$("#MainContent_gvDemographic").find(str).css("height", $("#tblFreez").find(str).css("height"));
}
}
New:
function FixHeight() {
var rowCount = $('#tblFreez tr').length;
var hei = new Array();
for (var i = 0; i < rowCount; i++) {
var str = "";
if ($.browser.msie) {
str = "tr:eq(" + i + ") td";
}
else {
str = "tr:eq(" + i + ")";
}
hei[i] = $("#tblFreez").find(str).css("height");
}
for (var i = 0; i < rowCount; i++) {
var str = "";
if ($.browser.msie) {
str = "tr:eq(" + i + ") td";
}
else {
str = "tr:eq(" + i + ")";
}
$("#MainContent_gvDemographic").find(str).css("height", hei[i]);
}
}
Why not use only one loop and not for but jQuery .each(). I haven't tested code below, but should work.
function FixHeight() {
var $MainContent = $("#MainContent_gvDemographic");
var $tblFreezRows = $("#tblFreez tr");
var hei, $row;
$tblFreezRows.each(function(index, elem){
$row = $(this);
if ($.browser.msie) {
hei = $row.find('td').css("height");
$MainContent.find("tr:eq(" + index + ") td").css("height", hei);
}
else {
hei = $row.css("height");
$MainContent.find("tr:eq(" + index + ")").css("height", hei);
}
});
}
The DOM operations are usually the expensive operations.
Your first version has heavy DOM operation, but your second version has the count. It looks like load vs number.
Ideally, your first version should be faster as it is just one loop and the number of iteration is half than the second version.. but it is not always the case.
Assume your memory is like 1000M in which 300M can be garbage collected meaning it can be cleaned up. So the Operating systems memory model would call the garbage collector whenever your memory gets closer to 1000M. With those conditions in mind, lets say in your first version, every 5 iterate takes 1000M, which would end up calling garbage collector to clean up or free up resources for next iteration. So if you end up running for 100 iteration which is equals to 100 iteration + 20 times GC processing.
In your second case, assume it take 50 iteration to fill up 1000M, so you would end up calling 4 time GC processing which is basically 20 vs 4 times calling some other process inbetween each of your iterate.
Above is just a speculation and the actual memory modules are much smarter than what I have explained, but that is just to give an idea of load vs numbers.
Anyways..Try below code and see if it fixes your problem,
Setting height at TR level
var fTable = document.getElementById('#tblFreez');
$("#MainContent_gvDemographic tr").each(function(ridx) {
$(this).height($(fTable.rows[ridx]).height());
});
Setting height at TD level
var fTable = document.getElementById('#tblFreez');
$("#MainContent_gvDemographic tr").each(function(ridx) {
$(this).find('td').each(function (cidx) {
$(this).height($(fTable.rows[ridx].cols[cidx]).height());
});
});
Try detaching the elements before you alter/search them. Then re-append them. DOM-operations are costly.

efficient way to parse table to array object in IE

im having table with more than 1k rows and more than 7 columns, trying to parse into array object, i tried using jquery
$(tableSearch).each(function () {
$('tr', $(this)).each(function (key, tr) {
var self = this;
var obj = new Object();
var rowPos = 0;
$('td', tr).each(function (rowPos) {
obj[_self.colModel[rowPos].name] = $(this).html();
});
obj['Key'] = 'Rec-' + key;
});
});
in FF it takes 300 milli seconds, but in IE its taking 60 seconds :(
as u can compare its around 200 times slower.
is there any way to get performance in IE. i tried raw javascript methods also still in IE efficiency is not achieved.
help me!!!!!!!.. how can i get similar performance in all browsers.
THANKS in Advance
i got the solution IE can be faster if we implemented this way
table = document.getElementById("myGrid");
for (i = 0; i < table.rows.length; i += 1) {
var rowData = new Array();
row = table.rows[i];
for (j = 0; j < row.cells.length; j += 1) {
cell = row.cells[j];
rowData[j] = cell.innerHtml
}
obj.push(rowData);
}
Note: disable debug mode if ur using any debugger.
then IE seems to be reasonable but cant be faster than FF or safari

Categories