mathjax / MathJax

Beautiful and accessible math in all browsers
http://www.mathjax.org/
Apache License 2.0
10.21k stars 1.16k forks source link

Rendering performance in IE8 degrades rapidly as more math is processed #160

Closed shogun70 closed 12 years ago

shogun70 commented 13 years ago

In all versions of IE, rendering performance degrades as more math is processed. In most versions this seems somewhat linear. However, in IE8 it is exponential, meaning that MathJax is practically unusable for pages with more than a few equations.

This is a known issue.

shogun70 commented 13 years ago

Note: in MathJax, rendering speed is dominated by time spent looking up element dimensions - offsetWidth, etc - which is related to how long the browser takes to reflow the page - recalculate the size of the layout boxes that represent the page.

I have found that adding the style setting overflow: hidden to the .MathJax_Display class (the outer-container class for display-math) provides a modest boost to rendering speed for IE9, and a significant boost in IE8 browser mode of IE9. Obviously this only applies to display-math, and I couldn't replicate this in IE8 on XP.

I can't think of any reason this SHOULD improve rendering speed - because the height of the div isn't specified, its size is determined by its content. It is almost like MathJax HTML-CSS content triggers a very inefficient code-path in IE8, and this is one of the things that partially rectifies it.

I also discovered that adding the style setting display: inline-block to the .MathJax class (the container class for ALL math content) complements the former setting to give a significant performance boost in IE8 on XP.

I'm not too surprised that this allows for some browser optimization over the current setting of `display: inline".

There is a build containing both these tweaks at http://devel.mathjax.org/mathjax/shogun70/issue160

There is another build that also contains the mods from the getScales work at http://devel.mathjax.org/mathjax/shogun70/IE8

My testing indicates that together these mods reduce the overhead of display-math rendering by one-third on IE9, and two-thirds on IE8. The getScales also contributes to improving inline-math rendering.

I haven't tested the changes (but rendering still looks okay in the browsers I have tried).

It's hard to believe, but even with these improvements MathJax in IE8 is still a bit of a dead loss.

Sean

shogun70 commented 13 years ago

It is surprising that these seemingly innocuous styling tweaks on the math container elements have such a large effect on rendering performance. It makes me wonder if there are a few styling tweaks that could be applied to the internals to give another similar boost.

shogun70 commented 13 years ago

I tried switching various bug work-around settings for IE8, and found that a significant performance boost can be found by switching off negativeSkipBug and msiePaddingWidthBug. zeroWidthBug also needs to be switched off in combination with negativeSkipBug.

You can easily try this out by in the perf-analysis test-page. (After loading the test-page, make sure that the browser is in IE8 or IE9 browser mode)

The default settings could be tested with: http://playground.meekostuff.net/MathJax-test/shogun70/master/perf-analysis.html?equation=maxwell&count=10&run=Run&script=http://devel.mathjax.org/mathjax/shogun70/issue160/MathJax.js&doctype=strict

The optimized settings can be tried with: http://playground.meekostuff.net/MathJax-test/shogun70/master/perf-analysis.html?equation=maxwell&count=10&run=Run&script=http://devel.mathjax.org/mathjax/shogun70/issue160/MathJax.js&doctype=strict&negativeSkipBug=off&zeroWidthBug=off&msiePaddingWidthBug=off

Comparing the suggested tests above I see a one-third reduction in rendering time in IE8 on WinXP. I also see a significant reduction in IE9 on Vista.

I don't see the difference in rendered output between these two, but I don't know what to look for. Switching these settings off may not be an option, but there might be less expensive ways of implementing the work-arounds.

Another performance boost can be achieved by not displaying any rendered math until all math has been rendered. That is, setting display: none on the math container. This can be tested with: http://playground.meekostuff.net/MathJax-test/shogun70/master/perf-analysis.html?equation=maxwell&count=10&run=Run&script=http://devel.mathjax.org/mathjax/shogun70/issue160/MathJax.js&doctype=strict&negativeSkipBug=off&zeroWidthBug=off&msiePaddingWidthBug=off&hidden=on

This method significantly improves performance on all versions of IE. Usually rendering on IE slows down as more math is added to the page, but with this approach there is little slow-down. Whether this approach can be part of a real solution to IE's performance problem is a different matter.

Sean

dpvc commented 13 years ago

Sean:

I didn't get the notifications when you made your comments above (only the original message), so I didn't see your messages until just now.

Thanks for the information. This is interesting, and I'll look into your results for what I can include in v1.2.

In terms of overflow:hidden, there are some situations where that will be a problem (it is possible that math can extend outside of the bounding box, e.g., with things like \smash, \llap, \rlap and so on). I will have to check into whether this gets clipped or not. I don't think the display: inline-block will be a problem.

In terms of the flags that you mention, most of them have to do with problems that IE has with spacing at the beginning of a block element. As I recall, I did most of the development for this in IE7, and it may be that IE8 and IE9 don't need all of them. But their need may not show up in the expressions that you are using. I should go through them in IE9 and IE8 to see if there are document modes where they can be avoided.

The msiePaddingWidthBug flag causes an extra span to be inserted at the beginning of some positioned elements and my recollection is that this had to do with preserving the correct space at the left. I'll have to see if I can find the cases that require it.

The zeroWidthBug is only used when combining diactritical marks are used as accents. I think the only character that this will affect from TeX input is the vector arrow for \vec, so unless you have lots of vectors, this should not make any difference. (I see that you have used Maxwell's equations as your test, and that does use vectors, so that accounts for the effect you are seeing.)

The negativeSkipBug has to do with correctly detecting the widths of expressions that might have negative widths (or that start with a negative skip). This involves adding and removing elements from the expression and measuring widths (this is part of getW, which is one of the current bottlenecks). It may be that IE9 doesn't need this any more, but I think IE8 still does. But perhaps it is used too agressively, and it may be possible to determine whether the expression is likely to need it and only use it then.

Finally, as for display:none, my experience is that the contents of such a block will not have its offsetHeight and offsetWidth computed, and so MathJax can't determine the size of any of the sub-expressions (I haven't verified this in IE8 or IE9, but that's how it works in other browsers). MathJax handles typesetting of math within a block that is display:none by typesetting it in a special hidden div instead, and inserting the result into the undisplayed block afterward. I didn't check your code, but I suspect that your display:none is triggering that action (since a check is made to see if MathJax can determine the size of elements within the container). So I suspect it is not actually the display:none that is helping, but the use of MathJax's hidden div instead. The problem with using this div is that it may not have the same CSS styles as those in use at the location where the math will eventually be placed. That can affect the quality of the rendering, and can cause the positioning to be off.

While these are all interesting possibilities, they all have some difficulties that still need to be worked out. But they do suggest some good directions to work harder. I'll have to look into just how much IE8 and IE9 make some of these flags unnecessary.

Davide

shogun70 commented 13 years ago

Hi Davide,

Thanks for the insight.

RE: display: none styling, I do understand that MathJax is using the hidden div to calculate measurements. The performance boost is not due to the hidden div, but rather to the simpler page layout the browser has to deal with when none of the math is displayed. This means that performance doesn't degrade when more math is added to the page. Another way to demonstrate this effect is to set display: none on .MathJax classed elements immediately after the HTML-CSS Translate() method is called. Either way produces the same boost.

Sean

shogun70 commented 13 years ago

You can get a primitive idea of the impact of using the display: none styling by loading a page, and then at the console running the following snippet:

MathJax.Ajax.Styles({
    ".MathJax_Working .MathJax_Ready": { display: "none" }
});     
MathJax.Hub.config.messageStyle = null; 
MathJax.Hub.Register.MessageHook("Begin Math", function() { 
    document.body.className = "MathJax_Working";
});
MathJax.Hub.Register.MessageHook("End Math", function() { 
    document.body.className = "";
});     
MathJax.Hub.Register.MessageHook("New Math", function(message) {
    document.getElementById(message[1]).previousSibling.className += " MathJax_Ready";
});     
MathJax.Hub.Reprocess();

For instance, the page http://math.stackexchange.com/questions/47285/fundamental-theorem-of-calculus/ normally takes around 3 minutes to render in IE8 on WinXP. The above reprocessing step takes around 20 seconds.

Presumably, using this trick would be a configurable option, maybe even user configurable.

Another approach would be to hide off-screen math until all math in the page is rendered. This would incur a small performance degradation as a trade-off for being able to display rendered math immediately in the current view-port.

shogun70 commented 13 years ago

By the way, if you incorporate any of these techniques I would ask that my copyright notice be retained in the source. The time taken to find and test them is not insignificant.

Sean

shogun70 commented 13 years ago

This code at the top of getW()

getW: function (span) {
  var W = span.offsetWidth, w = (span.bbox ? span.bbox.w: -1), start = span;
  if ((w < 0 || this.negativeSkipBug) && W >= 0) {

is confusing because W = span.offsetWidth means that W >= 0 is always true. This means that whenever negativeSkipBug is true the first offsetWidth lookup is redundant. Since it triggers a page reflow it is also a significant waste of time.

I have refactored getW() to only make one offsetWidth lookup and it gives a noticeable boost to rendering performance on IE. I've added this to the build at http://devel.mathjax.org/mathjax/shogun70/issue160

Sean

dpvc commented 13 years ago

This code at the top of getW()

getW: function (span) {
var W = span.offsetWidth, w = (span.bbox ? span.bbox.w: -1),  start = span;
if ((w < 0 || this.negativeSkipBug) && W >= 0) {

is confusing because W = span.offsetWidth means that W >= 0 is always true.

This is false. In WebKit-based browsers, offsetWidth can be negative (e.g., from the result of $x\kern -3em y$). The complicated lookup in getW is basically to handle offset widths that should be negative but aren't.

This means that whenever negativeSkipBug is true the first offsetWidth lookup is redundant. Since it triggers a page reflow it is also a significant waste of time.

I've already redesigned getW to avoid the second offsetWidth prior to this (and to avoid the offsetHeight in placeBox() entirely, and to replace getScales() completely with a routine that runs ahead of time and only causes a single reflow, and to reduce the offset lookups in several other ways). It's just not quite in shape to push to GitHub at this point.

Davide

dpvc commented 12 years ago

Im closing this issue since v2.0 has been optimized for IE.