Bug 142238 - Extremely low performance layer compositing with Google Maps API v3
Summary: Extremely low performance layer compositing with Google Maps API v3
Status: RESOLVED WORKSFORME
Alias: None
Product: WebKit
Classification: Unclassified
Component: Layout and Rendering (show other bugs)
Version: 528+ (Nightly build)
Hardware: Mac (Intel) OS X 10.10
: P2 Major
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2015-03-03 15:02 PST by Nick Dolezal
Modified: 2015-03-16 19:28 PDT (History)
4 users (show)

See Also:


Attachments
Sample project and screenshots demonstrating Safari 8 performance / compositing issues with Google Maps API v3 and CSS fix injector. (5.42 MB, application/octet-stream)
2015-03-03 15:02 PST, Nick Dolezal
no flags Details
Test case .html only, v1.1. Button to remove zero transform hacks. Probes Chrome's squashing. (6.71 KB, text/html)
2015-03-03 19:48 PST, Nick Dolezal
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Nick Dolezal 2015-03-03 15:02:11 PST
Created attachment 247800 [details]
Sample project and screenshots demonstrating Safari 8 performance / compositing issues with Google Maps API v3 and CSS fix injector.

Summary:
Safari 8 (OS X) displays a profound performance regression in comparison to Safari 7 for viewing Google Maps API v3 pages, resulting in an unexpectedly low framerate and UI freezing.

Expected behavior:
"1080p 60fps".  In other words, performance on par with other browsers and Safari 7.

Actual behavior:
<15 FPS (roughly) with frequent UI freezing when multiple raster layers are used.

Steps to Reproduce:
See bottom of this text and attached sample project.

Additional Notes:

Further investigation revealed this is likely due to a layer compositing issue of some kind that affects both Safari 7 and 8, and furthermore that the differentiating factor is a navigator.useragent based workaround from the Google Maps API, which inserts "style:-webkit-transform:translateZ(0px);" to all raster map tile container divs for Safari 7 and Safari 8 Mobile, but *not* Safari 8 OS X.

Testing showed that Safari 7 and 8 performed very similar based entirely on the user agent setting via Developer -> User Agent.  Furthermore, applying a similar workaround as used by the Google Maps API v3 for Safari only restored Safari 8 framerates to Safari 7 levels and resolved excessive UI freezing/stuttering.

Setting the user agent to IE10, Chrome or Firefox resulted in the same behavior as Safari 8; low performance.

From these results, I believe it can be concluded there is significant layer compositing performance issue that has been present in Safari/WebKit since at least Safari version 7, and that other browsers do not have this issue. It also seems reasonable to conclude this is a known issue, at least to the Google Maps API team, due to the IE6-style workaround fix employed and successful testing results of an analog of that.


Nonetheless, I was unable to find any bug referencing this, nor anything on Google, Stack Overflow, or the Apple Dev Forums.  So I began looking for things that could explain the differences between WebKit and Blink after the err uh, trial separation.

The closest reference I could find was a Chromium bug: https://code.google.com/p/chromium/issues/detail?id=356734&can=1&q=google%20maps%20performance&colspec=ID%20Pri%20M%20Week%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified

Which describes a process called "layer squashing."  Searching the WebKit bugs for layer squashing showed this bug marked as "won't fix": https://bugs.webkit.org/show_bug.cgi?id=109992 from the same authors of the layer squashing now part of Chrome / Blink.

From the dates on those bugs, and the fact the behavior can be reproduced in Chrome 7, I am dubious that Safari/WebKit implements "layer squashing" as described in the aborted proposal.  Yet, in the attached screenshots, you can see a similar behavior occurring -- each tile is composited individually when it loads, then is no longer shown by the Safari "show compositing borders".

As this seemed a known issue to the Google Maps API team (the Safari 7 workaround), I contacted the authors of the layer squashing in Blink and the WebKit attempt.  They recommended submitting it as a bug cc:sfr with a sample project.  If there is a bug for this already, I was unable to determine it.

I can only guess as to the cause.  Some ancestral, cave-dwelling missing link of modern layer squashing?  Transfer of GPU textures to RAM after the CSS3 "ease in" forces GPU acceleration?  I'll probably never know.



Attached are screenshots of this behavior and a sample project demonstrating it.  It is 100% reproducible in my experience, and more noticeable with multiple raster tile layers.  Apologies for the zip file but it seems I can only attach a single file.

chrome40.png -- this is the sample project after clicking the button to add the sample "Safecast" layers.  Compositing borders have been enabled in Chrome.

safari8.png -- sample project, Safecast layers, Safari 8.  Taken while panning the map such that you can see borders for tiles as they are initially loaded.

safari8fix.png -- the above, after clicking "Inject Safari 8 Performance Hack Fix".  Note that the tiles have been ascended to their own compositing layers, as is the case for default behavior in Chrome, Firefox, IE, Safari 8 Mobile, and Safari 7, the latter of which is again, merely due to Google Maps employing a similar fix.  This is also more or less how Safari 7 looks.

Steps to Reproduce:
1.  Open sample .html page in Safari 8
2.  Enable compositing borders
3.  Click the button "Add Safecast layers" for the easiest reproduction (multiple raster layers)
4.  Hold down the arrow keys to pan around.  Note the low framerate and UI freezing.
5.  Click the button "Inject Safari 8 Performance Hack Fix".
6.  Note the compositing borders immediately change.
7.  Now hold down the arrow keys again to pan around.  30-60 FPS.

Differential Diagnosis: useragent
1.  Open sample .html page in Safari 8
2.  Enable compositing borders
3.  Click the button "Add Safecast layers" for the easiest reproduction (multiple raster layers)
4.  Hold down the arrow keys to pan around.  Note the low framerate and UI freezing.
5.  Click Developer -> User Agent -> Safari 7
6.  After reload, click "Add Safecast layers" again.
7.  Note the compositing borders now.
8.  Now hold down the arrow keys again to pan around.  30-60 FPS.

Differential Diagnosis: Safari 7 + useragent
1.  Open sample .html page in Safari 7
2.  Enable compositing borders
3.  Click the button "Add Safecast layers" for the easiest reproduction (multiple raster layers)
4.  Hold down the arrow keys to pan around.  Note 30-60 FPS as expected.
5.  Click Developer -> User Agent -> Safari 8
6.  After reload, click "Add Safecast layers" again.
7.  Note the compositing borders now.
8.  Now hold down the arrow keys again to pan around.  Note the low framerate and UI freezing.


NOTE: I am recommending arrow keys over mouse panning to make it more obvious and consistent, but it's also quite easily seen when click-and-dragging the mouse.  Just hold the arrow key down firmly and pretend it's native code.

I was uncertain how to implement an accurate framerate counter in Safari and did not see an option to enable one, thus reproducing this is somewhat manual.  Sorry.

Testing hardware:
MPB, 15", Late 2014, i7 4870HQ (Haswell), 16GB, SSD, dGPU.  OS X 10.10.2, Safari 8.0.3.  Forcibly enabling dGPU made no difference.
MPB, 13", Mid 2011, Core2Duo (meh), 8GB, 7200 RPM, ATI iGPU, OS X 9.latest, Safari 7.latest.

All extensions were disabled, cache cleared, machines rebooted, etc.


Final note (really!):  Google Maps API v3 is the framework you can use as a developer on your own page.  It is to maps.google.com as MKMapKit is to Maps.app.  In other words, maps.google.com is more like version 9003, hates developers, and uses WebGL.  I didn't do much testing on maps.google.com as I can't develop off it.  Safari 8 doesn't have exactly the same problem with UI freezing and useragent Safari 7 did nothing, but it nonetheless seemed to have a lower framerate than Chrome 40's 60 FPS.  Perhaps that warrants further research, but it does not seem to be related to the compositing issue for the v3 API here.
Comment 1 Radar WebKit Bug Importer 2015-03-03 15:22:39 PST
<rdar://problem/20029380>
Comment 2 Simon Fraser (smfr) 2015-03-03 16:34:35 PST
Safari 8 and Chrome have different compositing layer configurations for the map tiles. Chrome tiles are:

<div style="width: 256px; height: 256px; overflow: hidden; transform: translateZ(0px); position: absolute; left: 152px; top: 443px;"></div>

but Safari 8 gets:
<div style="width: 256px; height: 256px; position: absolute; left: 151px; top: 178px;"></div>

Note the lack of transform property.

(Another thing to note: Safari 8 still needs -webkit-transform).

If you fix that does the story change?
Comment 3 Nick Dolezal 2015-03-03 19:38:03 PST
Hmmm, indeed.  I made a methodological error in my testing.  I confused the DOM styles from Safari's Develop -> User Agent -> Chrome with Chrome.app itself.

The useragent Safari reports for Develop -> User Agent -> Chrome is quite old, so I retested with a custom useragent string for Chrome 41.  ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.76 Safari/537.36")

Plot twist: Under Chrome 41's user agent, Safari 8's performance was as expected.  Moreover, -webkit-transform *is* getting set here as well, at least on some of the elements.


Further Tests: Firefox

Chrome 8 was set to the current stable Firefox UA ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:36.0) Gecko/20100101 Firefox/36.0"), and retested.

Results: Chrome: Low FPS with recent Firefox UA set.  Firefox: 30+ FPS.

The DOM in both Firefox.app and Safari with UA=FF, showed no zero-transforms getting set, nor any apparent analogs.  I did not find a way to show compositing boundaries in Firefox, but the 3D view did show boundaries around the tiles.  Unknown if compositing or just element.


Further Tests: Breaking Zero Transforms For Everyone

To better compare the underlying compositing behavior, it would be useful to disable the useragent-related zero transform hacks.  I very crudely removed all the zero-transforms for both Chrome and Safari UA=Chrome 41.  This was effective in causing both Chrome and Safari to revert to their instinctive layer-squashing behaviors.

Results:

Performance: Decreased performance for both; Chrome: 30-60 FPS, Safari: < 15 FPS and  UI freezing.  Difficult to separate out polling / DOM traversal overhead, but CPU use increased in Chrome to almost 100% across all cores, whereas Safari bottlenecked on a single core at 100%.

Squashing: 
Chrome:  Would not squash further than the cyan gridlines seen in "chrome40.png"; these appeared to be 128x128 point (256x256 px actual) tiles, and were unaligned on a block level with Web Mercator tiles.
Safari:  Went all the way and squashed everything, looking identical to "safari8.png" -- no tile compositing boundaries.

The DOMs appeared similar, with Safari getting the vendor prefix (despite Chrome's UA...), and only transform-origin and transform-matrix were present on container divs I can see.  Those are required for panning the map AFAIK, and I don't think can be reduced further.


Conclusions:
1. Google Maps API v3 is making the zero transform hacks for pretty much everyone who needs them... except Chrome 8 OS X.
2. The zero transform hack has a much more significant effect upon Safari than Chrome, though quantifying that for Chrome is difficult due to the testing performed.
3. Without the hack, both Safari and Chrome squash layers.  Chrome will not squash as extensively as Safari.  This may be related to the logic changes in the linked bugs.
4. A sparsity heuristic may potentially improve Safari's performance here, assuming it is doing so for Chrome.  Unfortunately as long as Gmaps API v3 inserts stuff like this into the DOM for Chrome, the world will never know if the sparsity heuristic improved performance or not because it will never get used:

<img src="http://safecast.media.mit.edu/tilemap/TileGriddata512/9/452/197.png" draggable="false" style="width: 256px; height: 256px; -webkit-user-select: none; border: 0px; padding: 0px; margin: 0px; transform: translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px) translateZ(0px);">

Sidenote: Result of natural behavior in Chrome after panning a while.  Zombies with a Z.  pool.pop() and style += I'm guessing.  That's 1KB as UTF-16.  The map tile .png is 3KB.  This is why hardcoded hacks are bad.

Will attach updated test case with the translateZ removal for Chrome.
Comment 4 Nick Dolezal 2015-03-03 19:48:30 PST
Created attachment 247827 [details]
Test case .html only, v1.1.  Button to remove zero transform hacks.  Probes Chrome's squashing.
Comment 5 Simon Fraser (smfr) 2015-03-04 09:24:46 PST
I think this discussion would be more productive if it included Google Maps engineers.
Comment 6 Nick Dolezal 2015-03-04 20:53:59 PST
Re #5: Made a request but you may need to follow-up. (I'm just some random person on the internets)


Erratum to #3: All instances of "Chrome 8" should be "Safari 8".


Two more things to add if I may; I tested other major web map APIs, and finally managed to test Chrome properly without translateZ(0).


Scope: Other Map APIs

I briefly tested every other major tiled web map API I could think of offhand.  OpenLayers 1/2/3/Cs, Leaflet (MapBox), Bing Maps, and ArcGIS.  Either these issues didn't apply (OL; giant canvas element) or more commonly, an analog of translateZ(0) was already getting set for both Chrome and Safari (Leaflet, Bing Maps).

To be clear, the same core "layer smushing -> low tile FPS" issue is present.  But because the workarounds normally prevent that, Safari 8 was merely the first to get a the-real-dark-souls-starts-here experience for any length of time.

As the test below shows, Chrome also suffers when the useragent string stops matching.  Potential risk exists for both browsers.


Test: Chrome: No translateZ(0):

Chrome's layering performance was isolated from the effects of translateZ(0) with a useragent override extension[1] which was set to Firefox's UA string.


Results: Performance:

Significant decrease in performance, though not as much as Safari under the same conditions.  This was mostly a result of minimum framerate changes.

Holding down left mouse and rapidly spinning the map in small-radius circles with Chrome's UA set to Firefox measured 30 FPS consistently.  With Chrome's default UA it measured 55 FPS.


Conclusion:

This test provides further evidence both Chrome and Safari display suboptimal Gmaps API v3 tiled map performance without translateZ(0px), and that this has a more significant impact upon Safari than Chrome.

Chromium issue 356734 [2] states "we don't create big squashing layers on maps".  Unfortunately, internal layer squashing heuristics alone were unable to provide expected performance in this test.


[1]: https://chrome.google.com/webstore/detail/user-agent-switcher/ffhkkpnppgnfaobgihpdblnhmmbodake
[2]: https://code.google.com/p/chromium/issues/detail?id=356734
Comment 7 Nick Dolezal 2015-03-09 17:41:20 PDT
Google Maps bug 7756 created[1] per request.  Likely nothing actionable for WebKit here?  If so, this can be closed.

[1] https://code.google.com/p/gmaps-api-issues/issues/detail?id=7756
Comment 8 Nick Dolezal 2015-03-16 17:54:14 PDT
Update: Reported as fixed by Google[1].

"This should now be fixed in the experimental (3.20) version and will be patched into release (3.19) tomorrow."

[1] https://code.google.com/p/gmaps-api-issues/issues/detail?id=7756
Comment 9 Nick Dolezal 2015-03-16 19:27:24 PDT
Can confirm the fix, appears to be set as it was for the Safari 7 UA.

Native Gmaps basemap:

<div style="width: 256px; height: 256px; -webkit-transform: translateZ(0px); position: absolute; left: 1712px; top: -2389px; opacity: 1; transition: opacity 200ms ease-out; -webkit-transition: opacity 200ms ease-out;">
<img src="https://mts1.googleapis.com/vt?pb=!1m4!1m3!1i10!2i913!3i388!2m3!1e0!2sm!3i295000000!3m9!2sen-US!3sUS!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!4e0!5m1!5f2" draggable="false" style="width: 256px; height: 256px; -webkit-user-select: none; border: 0px; padding: 0px; margin: 0px;">
</div>

User raster layer:

<div style="width: 256px; height: 256px; -webkit-transform: translateZ(0px); position: absolute; left: 944px; top: -2133px; opacity: 0.5;">
<img src="http://safecast.media.mit.edu/tilemap/TileGriddata/10/910/389.png" draggable="false" style="width: 256px; height: 256px; -webkit-user-select: none; border: 0px; padding: 0px; margin: 0px;">
</div>