JavaScript performance in Internet Explorer sucks. No news there. However there are some tips and tricks to speed it up. For example, there is this three part series. Still I find myself unable to squeeze decent performance out of it. Perhaps some of you have an idea what else to do so it was speedier?
What I want to do is create a medium-size table from scratch in Javascript. Say, 300 rows, 10 cells each. It takes at about 5-6 seconds on my computer to do this. OK, granted, it's a 5 year old rig, but that's still too much. Here's my dummy code:
<html>
<body>
<script type="text/javascript">
function MakeTable(parent)
{
var i, j;
var table = document.createElement('table');
var insertRow = table.insertRow;
for ( i = 0; i < 300; i++ )
{
var row = insertRow(-1);
for ( j = 0; j < 10; j++ )
{
var cell = row.insertCell(-1);
cell.innerHTML = i + ' - ' + j;
}
}
parent.appendChild(table);
}
</script>
<div onclick="MakeTable(this);">Click Me!</div>
</body>
</html>
Added: Hmm, apparently string-concatenation (with array.join) is the only way to go. Well, sad, of course. Was hoping to do it the "proper" DOM-way. :)
Here is an interesting link I found when looking for an answer on this:
The page uses five different scripts / methods to generate a table.
According to their tests, using strings is by far faster than using DOM / Table elements.
http://www.quirksmode.org/dom/innerhtml.html
One of the main reason's for IE's performance issues are DOM operations. You want to do your DOM operations as efficiently as possible. This can include, depending on your situation (benchmark!):
Offline creation of your DOM structure; keep the top level element out of the document (create, but not append) then appending it to the document when it's ready, instead of appending every element into the DOM as you create it
write innerHTML instead of DOM manipulation
You could try 'Duff's Device': Unwinding a loop by repeating the code a number of times:
for (var i = 0; i < count / 4; i++) {
doSomething();
doSomething();
doSomething();
doSomething();
}
Obviously this leaves the remainder when divided by 4, the original Duff's Device had a clever way of jumping to the middle of the loop using a switch statement mixed in with the loop. Javascript does not support this, but you could manually process the rest of your rows. Also the number 4 is random, the number itself can be derived by performance testing.
See also: http://www.websiteoptimization.com/speed/10/10-3.html
Related
I am currently trying to implement a small fluid simulation on P5js. I tried to render 20K squares with a random colour. I got a frame rate of 2.xxx.
var sim;
var xdim = 200; var xLength;
var ydim = 100; var yLength;
function setup() {
createCanvas(800,400);
sim = new Sim(xdim, ydim);
}
function draw() {
xLength = width/xdim;
yLength = height/ydim;
for (var i = 0; i < xdim; ++i) for (var j = 0; j < ydim; ++j) {
fill(100);
rect(i*xLength, j*yLength, xLength, yLength);
}
console.log(frameRate());
}
What is the problem behind? Is the library not good enough? Or, the poor configuration of my computer? Or, javascript is not suitable for these kinds of implementation?
We can't help debug your code without an MCVE. Specifically, you haven't provided the Sim class, so we can't run your code at all.
But you need to take a step back and ask yourself this: what performance do you expect? You can't really complain about performance if you didn't have any expectations going in.
Also, you might want to figure out how many squares you can display before seeing a performance hit.
From there it's a game of looking for optimizations. You're going to have to do some profiling to understand exactly where your performance hit is. Maybe you display fewer squares, or maybe you lower the framerate, or maybe you do some pre-rendering. Again, what you do depends on exactly what your expectations and goals are.
I will say that you should take that call to console.log() out of your draw() loop. You should only use that for debugging, and it's not going to improve your performance to call that every single frame.
If I add an element to the DOM, are the changes immediate? If I remove the same element in the next line of my code, will the element appear on the screen (for a short period of time)? Or does the display get updated when the current browser cycle ends?
It will NEVER show no matter how fast your machine is. Javascript will run to completion blocking the UI until its done.
Try this
HTML
<div id='d'></div>
JS
var d = document.getElementById('d');
var p = document.createElement('p');
p.innerText = "One";
d.appendChild(p);
for (i = 0; i < 1000000; i++) {
for (z = 0; z < 10; z++){
// this is nonsense that runs for a sec or two to block the JS thread
// please never do this in production
}
}
p.innerText = "Two"
will pause your browser and then show Two ... never One
Obviously appearance of elements depends on the power of CPU, browser algorithms, graphic card render time, monitor frequency and many other factors. However The programs (e.g JavaScript) may continue the actions by considering virtual elements without errors.
On the other hand the browser algorithm may decide to render the code line by line or not. As an experience if you run a heavy loop to append items to the body, the Opera browser displays the items one by one however the Chrome will render the page at the end of loop. However if you do the loop using the JavaScript setTimeout, in all browsers you will see the elements appearing one by one.
Currently I am trying to expand a d3.js tree which contains over 100,000 nodes. Many leafs exist under multiple parents, as they fit multiple sections/items/regions. Searches performed by the user results in the tree opening up to all leafs with that node ID. This can result in the graph trying to open up, on rare occasions, up to 2000 leaf nodes at once. Currently, I have found the only way to do this without crashing Chrome is to use the following setInterval code.
var timeout = setInterval(function(){
for(var j = i; j < i + 10 ; j++){
makeEl(d[j]);
Search.rules += (j+1) + ") " + Graph.findNode(d[j]) + "<br><br>";
highlightPathTo(d[j].id);
if(j >= d.length - 1){
//When all of the elements have been itterated through.
clearInterval(timeout);
highlight.selected = d;
$('#highlights').removeClass('empty');
break;
}
}
i+=10;
}, 500);
However, this takes minutes and is very laggy. I there any other way to accomplish opening this number of nodes in one go that would result in a quicker completion?
Not likely. JavaScript and Browsers can't magically transcend the limits of physics (or your computer). Browsers have come a long way handling huge documents. But there are limits. I don't know for sure how much memory each tree node needs but if each of them needs just 1 KB, we're talking about 100 MB raw data which needs to be rendered on the screen. If each nodes just takes 10 ms to render, simply drawing the page will take 1000 seconds.
So my guess without looking more closely at the problem is that you can't do it. And you probably shouldn't: The human brain isn't able to process that much information nor is there a computer screen which could display it all. Think harder about what you really want to achieve and find a better representation.
You can dump a ton of data on your poor user but that will just drown them. Find a way to present just the important bits, the few gems under the ton of garbage.
I have a double-sided document as two separate pdf files — front-facing pages in one document and rear-facing pages in the second.
front.pdf
rear.pdf
I have also combined them into a single document with all the pages but with all the front-facing pages before the rear-facing pages. The page ordering is of the form, {1,3,5,7,...,[n],2,4,6,8,...,[n-1 OR n+1]}
all.pdf
I wish to write a simple javascript that can be run from inside Adobe Abrobat X Pro. Ideally, it would count the pages of the document all.pdf, handle both occasion when there are either an odd or even number of total pages and then reorder them such that they are in their original order:
page [1>3>4>2] => page [1>2>3>4]
The tiny leading code snippet above is from the answer by user171577 on SuperUser in this question: https://superuser.com/questions/181596/software-that-merges-pdf-every-other-page
I was able to accomplish this following advice from NullUserException :
This script requires a document composed of all the odd pages followed by all the even pages. It will cope with cases where there are n even pages and n+1 odd pages.
I entered a 'Document JavaScript' called InterleavePages, with the following code:
function InterleavePages() {
var n = this.numPages;
var nOdd = Math.floor(n / 2);
var nEven = n - nOdd;
var x;
var y;
var i;
for(i = 0; i < nEven; i++) {
// movePage x, toAfterPage y
// note page numbers are 0-indexed
x = nOdd + (i); //
y = i * 2 ; //
this.movePage(x,y);
}
}
InterleavePages();
Thanks, this was a great help. Just wanted to point out the limitation I found is it only works as written with an even number of pages. Although it's probably possible to write a more sophisticated calculation script, I took the easy way out and just added a blank page to the end of my 17-page test document. I did, however, add an alert so I wouldn't forget to add an extra page when necessary...
function InterleavePages() {
var n = this.numPages;
var nOdd = Math.floor(n / 2);
var nEven = n - nOdd;
var x;
var y;
var i;
app.alert({
cMsg: "Total pages must be an even number",
cTitle: "Even Number of Pages Required!",
nIcon: 1, nType: 1
});
this.pageNum = 0;
for(i = 0; i < nEven; i++) {
// movePage x, toAfterPage y
// note page numbers are 0-indexed
x = nOdd + (i); //
y = i * 2 ; //
this.movePage(x,y);
}
}
InterleavePages();
As mentioned in some other exchanges, to interweave the pages two pdfs, you can use the Java console.
The first step is to combine the pdfs into a single document. I would do this by highlighting both files, and then right-clicking on one of them. There should be an option to "Combine supported files in Acrobat".
Then, once they are combined, open the combined file, where you want to run the code
for (i = 0; i <= this.numPages/2-1; i++) this.movePage(this.numPages-1,this.numPages/2-i-1);
The step-by-step details for running such code are:
1) Open the pdf.
2) Go to the second page. Doing this way will allow you to notice whether the change has taken place. You don't have to do this step, but it helps.
2) Press Control + J
3) In the window that pops up, I always go to the "View" Dropdown menu, and set it to "Script and Console".
4) In the bottom window, replace the text that should read something like
"Acrobat EScript Built-in Functions Version 10.0
Acrobat SOAP 10.0"
with
for (i = 0; i <= this.numPages/2-1; i++) this.movePage(this.numPages-1,this.numPages/2-i-1);
5) Press enter once. Pressing twice might run the code again (which you do not want).
6) Check you pdf to see if the pages have been interlaced. If not, try step 5 again.
7) You are now a Java wizard. Congratulations.
I had the same issue, my scanner was single sided only, and the scanner software, after being done with the auto-feeder, asked if there's more to scan. If you grab the stack, turn it over and feed it again, you'll end up with a single PDF where the n-page document is arranged as 1f, 2f, 3f ... nb, (n-1)b, (n-2)b ... 1b (f=front, b=back, 1-based page numbers). By definition you'd have an even number of scanned pages, the javascript for re-arranging the whole thing (careful, only works with even number of pages in this context!) is:
// rearrange the pages from single-side scanner, 0-based page# where
// pages 0 .. n/2-1 are front, pages n/2 .. n-1 are back
function Rearrange() {
var tpn=0; // target page number
for (count = 0; count < this.numPages/2; count++) {
this.movePage(this.numPages-1,tpn);
tpn=tpn+2;
}
}
Rearrange();
Background:
I asked this question yesterday:
How to modify the orientation of a <ul> spanning multiple columns?
asking how to convert a list like this:
a b c
d e f
g h i
j k l
into a list like this:
a e i
b f j
c g k
d h l
and I got this awesome response by beeflavor: http://jsfiddle.net/H4FPw/12/
Problem:
Unfortunately I didn't specify that there could be any number of list items, so his response is hard-coded to 4 rows, and uses a tricky matrices algorithm (read: black magic) that I can't wrap my head around.
I'm poking and prodding at this, trying to add variability but unfortunately it's not coming together for me and today's the deadline for this stuff.
This is an updated example of the problem I'm having: http://jsfiddle.net/H4FPw/13/
Is there anyone out there with a better head for this stuff who can give me a steer in the right direction?
I know it's not as "elegant" as your accepted answer in that question, but the code I linked to in my answer yesterday does work perfectly for you:
http://jsfiddle.net/AcdcD/
If you don't need to handle resizing, it can be simplified slightly:
http://jsfiddle.net/AcdcD/1/
Maybe you can use this if you run out of time?
jQuery plugin .transpose()
Exactly what you want it to do. I needed the same thing so I've written a general jQuery plugin that transposes any floated or inline-blocked elements that seem to be in columns but their order goes in rows.
Check out my detailed blog post with an example for transposing US states, and then head over to GitHub where the plugin is maintained and you can get a minified version (915 bytes as of version 1.2) of it as well.
All you need to do is:
$("yourSelector").transpose();
In your case that would be
$("li").transpose();
The good thing is that plugin checks how many columns are there originally (before transposition) and transposes to the same amount of columns afterwards. It doesn't add any additional HTML elements into DOM (like floated column wrappers or similar) it just rearranges existing elements. This is very good when it comes to CSS settings of the page because it doesn't interfere with them in any way shape or form.
And it also distinguishes between different lists of elements (items that are contained within different containers) as if you'd have two UL elements that need transposition. You don't have to call .transpose() on each because plugin will do that for you. You'd still just use the same selector as previously written.
What about this code:
jsfiddle
// I add a number of new elements to the <ul>, in this case, 'm', 'n', 'o' &'p'
var rowcount = 6; // I increase rowcount from 4 to 6 to accommodate the new elements
var colcount = 3;
var ichild=$('.directory-result-list-bottom').children();
var wf= "<li class='directory-result-text'>";
var wb= "</li>"
var k="";
var u;
var v;
var carryover=0;
$('.directory-result-list-bottom').empty();
for(var i = 0; i<rowcount; i++){
carryover=0;
for(var j=0; j<colcount; j ++){
k=k+wf+(ichild[(j*rowcount)+i].innerHTML)+wb;
}
}
document.getElementById('place').innerHTML=k;
I believe empty LI's are needed to fill the empty space.
This should work:
for(var i = 0; i<rowcount; i++){
for(var j=0; j<colcount; j ++){
var n = (j*rowcount)+i;
if (ichild[n])
k=k+wf+(ichild[n].innerHTML)+wb;
else
k = k + wf + wb
}
}
I think it's simpler to represent the items as a 1-D list (a,b,c...) and think about how to 1) get that from the original array and 2) transform it into the output array.
Step 1 is easy, because the 1-D list is the same order as the <li> elements in the HTML source.
So, suppose we have n elements and we want to put them into R rows and C columns but filling columns first. The element that goes in position (r,c) (0 based) is just element #R*c + r. And, as stated before, the order in the source file is the row-first order, or (0,0), (0,1), (0,2), ...
If the # of elements doesn't fill the RxC grid exactly, then you have to add checks that the computed element number doesn't exceed n (easiest way is to pad the list with empty elements beforehand).
Depending on the design requirements, you can also adapt this to minimize the number of blank elements given a fixed number of rows or columns.