6

I'm currently working on a little site for my family. One of the things I wanted to do was to make a basic 'making of' stop-motion video. I could assemble it and upload it to Vimeo or something but I thought it was a perfect opportunity to use nothing but HTML, CSS, and Javascript.

I've got everything styled and my JS is working, etc. except that it performs atrociously in Chrome and Safari. Interestingly, it works great in Firefox and I'm not supporting it yet in IE. I'm hoping for 8 to 12 frames per second, with music playing, which I haven't bothered trying yet due to this. Bad performance is anything less than that. Currently I'm getting roughly 3 fps in Firefox (acceptable, but not what I was looking for) and in Chrome and Safari I'm getting roughly .6795 fps.

When running the Chrome Profiler, I get the following (relevant) output.

99.96%   99.96%     (program)
0.03%    0.03%      (garbage collector)
0.01%    0.01%      script.js:5:nextSlide

I've never used the Profiler before but I believe this is showing me that my JS is not what's hitting the performance so hard.

I've published a test page that documents the performance differences that you can visit with Chrome and Firefox.

I've also discovered that this seems to be related to the images cycled. Cycling different, simpler images seems to work just fine in both Chrome and Firefox, despite the fact that Chrome is still a little more power hungry than Firefox.

As further proof of at least this conclusion, though it's entirely unacceptable, is demonstrated here, after running the images through convert -compress JPEG -quality 1. They cycle much more efficiently, but of course the quality is terrible.

I have run these test pages in Chrome (16.0.912.63), Safari (5.1.2 (6534.52.7)), WebKit nightly (Version 5.1.2 (6534.52.7, r102985)), and Mobile Safari (latest as of 2011/12/28) and only Mobile Safari performs as well as FireFox. The desktop browsers were tested on a MacBook Pro.

2.7 GHz Intel Core i7
8 GB 1333 MHz DDR3

Interestingly, Mobile Safari on an iPad 2 performs as well as FireFox when rendering the test page. Though Mobile Safari is based on WebKit, in this instance it performs entirely different.

Decreasing the setTimeout call to 144 from 244 also seems to not do anything. I've arrived at 244 entirely arbitrarily at this point as it became clear early on that the timing of the display compared to the call didn't seem to correspond nearly directly. This leads me to believe that I'm rendering the slide show as quickly as I can on each browser.

So my question is, how can I make this performant in WebKit?

Tim Visher
  • 12,786
  • 16
  • 58
  • 66
  • If we visit your test page, what is good performance and what is bad performance? How many frames per second are you expecting? Also, you should specify what version of Chrome that you have tested and on what operating system and CPU. – Brent Bradburn Dec 28 '11 at 20:28
  • I visited your site with both Chrome and Firefox on Windows 7 and on Linux using CPUs with similar specs to yours. I saw similar frames per second in all cases: 30 frames in about 8.5 seconds (3.5 FPS). It isn't clear to me whether what I'm seeing is what you consider bad or good, but it isn't different between Chrome and Firefox. Since it is so consistent for me, I'm assuming that what I am seeing is what you consider "good" (although it doesn't exactly agree with your goal of 8 to 12 FPS). Based on that assumption, I would guess that your problem with Chrome is specific to the Mac. – Brent Bradburn Dec 29 '11 at 08:21
  • Maybe you could further clarify your problem by describing exactly how bad the performance is when you run it with Chrome on Mac. You stated that it "performs atrociously" on Chrome and Safari, but I don't know how that translates into frames per second. – Brent Bradburn Dec 29 '11 at 08:24
  • @nobar: Thanks for the comments. Helped me to clarify things. Do my edits above help? – Tim Visher Jan 01 '12 at 13:59
  • Yes -- Having the actual performance numbers helps to narrow things down. Based on the speeds that you are seeing, I don't think that JPEG decompression could be the gating factor. Your i7 CPU can probably decompress your images at greater than 100 frames-per-second. – Brent Bradburn Jan 01 '12 at 22:41
  • It seems you're implementing video with a still image format. It seems a video format might be applicable here. – rsaxvc Jan 02 '12 at 09:15
  • Also, I get 3.6FPS in Chrome on Linux + Turion - maybe it is just Chrome on OSX? – rsaxvc Jan 02 '12 at 09:16

8 Answers8

12

You can debug the page performance in Chrome using the Timeline tab under the Chrome developer tools. The problem with your script is that your repaint cycle is simply too expensive, it currently takes 1.35s to repaint every frame.

enter image description here

The bad performance has nothing to do with the quality of the jpeg images (although the image quality also affects the page render time). The problem is that you are updating the z-index which causes the Chrome to repaint all images instead of just the next frame (You have a O(n) image slider website!).

The browsers try to do the minimal possible actions in response to a change e.g.: changes to an elements color will cause only repaint of the element.

Changing the element z-index property is basically the same as removing a node from the tree and adding another node to it. This will cause layout and repaint of the element, its children and possibly siblings. My guess is that in Chrome, the siblings are being repainted too, this explains the horrible performance.

A way to fix this problem is to update the opacity property instead of the z-index. Unlike the z-index, the opacity does not modifies the DOM tree. It only tells the render to ignore that element. The element is still 'physically' present in the DOM. That means that only one element gets repainted and not all siblings and children.

This simple changes in your CSS should do the trick:

.making-of .slide#slide-top {
   opacity: 1;
   /* z-index: 5000; */
}


.making-of .slide {
   position: fixed;
   /* z-index: 4000; */
   opacity: 0;
   ....
}

And this is the result, the repaint went from 1.35s to 1ms:

enter image description here

EDIT:

Here is a jsfiddle using the opacity solution, I also added CSS3 transitions (just for fun!)

http://jsfiddle.net/KN7Y5/3/

More info on how the browser rendering works:

http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/

Cesar Canassa
  • 18,659
  • 11
  • 66
  • 69
  • +1 for providing a graphical demonstration of how the z-index approach was performing and for showing a simpler way of fixing it. By the way, after you posted I tried using both "opacity" and "display" and didn't see any performance difference between the two. – Brent Bradburn Jan 02 '12 at 19:36
  • This is a fantastic answer and solves the problem of making the slide show performant. I'm in a quandary as between you and [nobar](http://stackoverflow.com/a/8699878/16562), you've provided everything I need, but nobar I think does a better job of explaining exactly why my solution in general was slow (z-index isn't optimized and thus re-paints every image) but your answer provides the jsfiddle example and the nice screen snaps of Chrome Profiler. I think I'm leaning towards this answer but I think it needs the detailed info about WebKit on Mac and the z-index information. – Tim Visher Jan 03 '12 at 15:57
  • 1
    I am sorry but I can only accept one answer. Wasn't anticipating getting 2 stellar ones. – Tim Visher Jan 03 '12 at 15:57
  • @TimVisher Just edited my answer and added a more in-depth explanation on the issue – Cesar Canassa Jan 03 '12 at 17:52
5

I took a look at the code on your site and found two things that are limiting the speed.

1) In the JavaScript, you have a timeout of approximately 1/4 second (244 milliseconds). This means that your best-cast frame-rate is about 4 FPS (frames-per-second). This can be fixed by simply reducing the delay to match the frame rate that you actually want. I see that your most recent edit addresses this point, but I didn't want to ignore it since it is ultimately critical to achieving the higher frame-rates that you want.

2) You are using z-index to control which image is visible. In the general case, z-index handling allows for objects that have different sizes and positions to be ordered so that you can control which object is visible at locations where two or more objects overlap. In your case, all of the objects overlap perfectly, and the z-index approach works fine except for one major problem: browsers don't optimize z-index processing for this case and therefore they are actually processing every image on every frame. I verified this by creating a modified version of your demo which used twice as many images -- the FPS was reduced by nearly a factor of 2 (in other words, it took 4 times as long to display the entire set).

I hacked together an alternative approach that achieved a much higher FPS (60 or more) under both Chrome and Firefox. The gist of it was that I used the display property instead of manipulating z-index:

.making-of .slide#other {
   display: none;
}

.making-of .slide#slide-top {
   display: inline;
}

and the JavaScript:

function nextSlide() {
  ...
  topSlide.id='other';
  nextTopSlide.id='slide-top';
  ...
  setTimeout(nextSlide, 1);
  ...
}

I made some changes in the HTML too, notably including id="other" in the tag for each image.


So why is WebKit so slow? As has been pointed out in other comments, the extra-poor performance that you are seeing on Webkit seems to be Mac specific. My best guess about this is that the Mac version of WebKit is not actually using the "turbo" version of libjpeg (despite the fact that it is listed in the credits). In your test, JPEG decompression could very well be the gating factor if it is actually decompressing every image on every frame (as is likely the case). Benchmarking of libjpeg-turbo has shown about a 5x improvement in decompression speed. This roughly matches the difference that you are seeing between Firefox and Chrome (3 FPS vs. 0.6795 FPS).

For more notes on libjpeg-turbo and how this hypothesis explains some of your other results, see my other answer.

Community
  • 1
  • 1
Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • Cesar Canassa demonstrates an easier modification than what I had done. His simple changes to use "opacity" instead of z-depth also work for the "display" property. In other words, it isn't necessary to add the "other" ID as I had originally done. – Brent Bradburn Jan 02 '12 at 19:26
  • Really fantastic answer. Between you and [Cesar](http://stackoverflow.com/a/8703436/16562) you completely solved my problem. I think you've done a better job at explaining what's going on in WebKit on the Mac and why Firefox may be performing better though not as good as it should. You also pointed out the delay, despite my edits, but I think that was still helpful. I also really appreciate your explanation of z-index optimization. I think the answer I accept should have all of your information and all of Cesar's. I wish I could accept both and give each of you 50 points, but I can't. :( – Tim Visher Jan 03 '12 at 15:59
  • Sorry I had to accept Cesars. This is really a fantastic answer and I wish I could award both of you 50 points as you really did it together. Let me know if I can buy you a beer or something. ;) – Tim Visher Jan 03 '12 at 19:32
  • @TimVisher: Yeah, we're quite a team. But seriously, I thought you might give it to me since I was first to clearly identify your real problem and provide a working solution (although it was previously suggested by Paweł Hajdan) -- or maybe because I also provided a plausible hypothesis that explained all of the curious performance measurements which were the focus of your original question. – Brent Bradburn Jan 03 '12 at 21:33
  • Like I said, I wish I could give you both 50 points. I think Cesar's answer should be at the top for future people who find this question, by a very slim margin. If there's any way I can make it up to you, let me know. :\ – Tim Visher Jan 04 '12 at 01:02
  • @TimVisher: Not a problem. My main purpose in working on this was to see what I could learn, which turned out to be quite a bit -- especially because of Cesar's contributions. – Brent Bradburn Jan 04 '12 at 04:22
1

Key in my experience is to keep as less as possible images in the DOM and in javascript arrays, so don't load all of the at once, keep it to a minimum. Also make sure you destroyed already used DOM elements as well as javascript objects holding images, manual garbage collection. This will improve performance.

Stan Wiechers
  • 1,962
  • 27
  • 45
1

Random guess: GPU acceleration. It is device-dependent, and there is a big race among browsers now.

You could try with a more recent Chrome like the canaries, http://tools.google.com/dlpage/chromesxs (it's 18.x now), just to get more data.

about:version in Chrome should give you version of WebKit.

Also, have you tried existing slideshow solutions like http://jquery.malsup.com/cycle/ ? I wonder if playing with the z-index is the bottleneck here... maybe having only 1-2 images displayed (all the rest using display:none) would help. This is again a guess.

Paweł Hajdan
  • 18,074
  • 9
  • 49
  • 65
0

There has been some relatively recent work on the JPEG image compression library that is used in many applications including browsers such as Firefox and Chrome. This new library achieves a significant speed increase by using special media-processing instructions available in modern CPUs. It may simply be that your version of Chrome doesn't use the new library.

Your question requests a way to fix your images, but that shouldn't be necessary -- after all, some other browsers work fine. Therefore, the fix should be in the browser (and browsers are constantly being improved).

You said that you improved Chrome's speed by dramatically reducing the quality or complexity of your images. This could be explained by the fact that for areas of very low detail, the JPEG decompression algorithm can bypass a lot of the work that it would normally need to perform. If an 8x8 tile of pixels can be reduced to a single color, then decompression of that tile becomes a very simple matter.

This Wikipedia article provides some additional info and sources. It says that Chrome version 11 has the new library. You can enter "chrome://credits" in your location bar and see if it references "libjpeg-turbo". "libjpeg" is the original library and "libjpeg-turbo" is the optimized version.

One other possibility is that libjpeg-turbo isn't supported in Webkit on the Mac (although I don't specifically know that). There is a hint as to why that might be the case posted here.

P.S. You may get better decompression speed by compressing with a different algorithm, such as PNG (although your compression ratios will likely suffer). On the other hand, maybe you should use HTML5 video, probably with the WebM format.

Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • My version of chrome running on a mac does have libjpeg-turbo. I tried converting everything to PNG and got similar performance profiles. Is it possible to programmatically, in browser, construct an HTML5 video like you can construct SVG graphics? That might be intriguing. The whole point was avoiding actually creating a video, which is what I ended up doing on the [real site](http://thevishers.info/merry-christmas-2011/) – Tim Visher Dec 29 '11 at 05:07
  • Your question states: "How can I optimize images to render efficiently in WebKit" and you mention "As further proof of at least this conclusion, though it's entirely unacceptable, is demonstrated here, after running the images through convert -compress JPEG -quality 1. They cycle much more efficiently, but of course the quality is terrible." So I was giving an answer that preverses quality and renders just as fast in Desktop Webkit. Maybe your means of compression are doing something the converters I mentioned will not do. – TryTryAgain Dec 29 '11 at 06:50
  • @TimVisher: WebM will compress and stream MUCH more efficiently (compared to image flipping) for typical video at normal video frame rates. However, if you are really satisfied with 3 fps, just flipping JPEGs is probably fine. You might want to take into account, though, that startup time (while it is loading all of the images) will depend on the client bandwidth. – Brent Bradburn Jan 02 '12 at 09:07
0

The best way to achieve better performance when it comes to graphics is to compress them, but like you want, but keep

If you are using Linux, I have used JPEG compression tool http://linuxpoison.blogspot.com/2011/01/utility-to-optimize-compress-jpeg-files.html before. It doesn't hurt quality as much as the ImageMagick example you gave.

Also http://trimage.org/ has JPG support, and would be my first recommendation!

If you are on Windows, maybe something like this: http://www.trans4mind.com/personal_development/convertImage/index.html

I have not tested the Windows method, and I'm not even sure it supports batch

Hope that helps!

P.S. For PNGs I use sometimes use http://pmt.sourceforge.net/pngcrush/ along with or without http://trimage.org/

TryTryAgain
  • 7,632
  • 11
  • 46
  • 82
  • This answer doesn't really address my question. I'm getting ok performance in Firefox and Mobile Safari. I'm trying to understand A) Why WebKit on the desktop seems to suffer so hard and B) How I can make it not hurt so bad. I understand that it's good practice to compress your graphics and I've already done that to the point I'm comfortable with. Gecko likes it, Mobile WebKit likes it, Desktop WebKit doesn't. – Tim Visher Dec 29 '11 at 05:10
0

I tested it in opera and it ran slow as hell, i noticed that opera had queued 150+ images to download it could be worth a try to download ~20 at a time?

Peter
  • 37,042
  • 39
  • 142
  • 198
  • Only if it would improve performance. In the real site I built, the images all load off screen and should have plenty of time to get retrieved before the slideshow plays, so I don't think that's the issue. – Tim Visher Dec 29 '11 at 05:04
  • Do you wait for all the images to load before running the slideshow? – Tim Visher Jan 01 '12 at 14:00
  • No i did not i started it as they where downloading. – Peter Jan 02 '12 at 14:41
0

An alternative approach would be to render this content as a video - it is ideal for this kind of thing and can easily contain audio and subtitles. You can access each pixel from each frame using JavaScript if you want to get funky.

Fenton
  • 241,084
  • 71
  • 387
  • 401