Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: Improve KML bitmap caching and fix icon scaling #609

Merged
merged 14 commits into from
Feb 14, 2020

Conversation

jeffdgr8
Copy link
Contributor

@jeffdgr8 jeffdgr8 commented Jan 29, 2020

Improve KML bitmap caching by caching BitmapDescriptors instead of Bitmaps. Every call to BitmapDescriptorFactory.fromBitmap() copies the Bitmap passed to it. So the current code ends up duplicating every Bitmap that is reused for multiple Markers or GroundOverlays for every usage.

Tested with this KML with 25 different marker icons and 25,000 random map points:
huge_kml.kml (remove .txt extension). Credit

On a Pixel 4 XL, the current code uses 0.9 GB RAM.
Screenshot 2020-01-28 at 7 59 36 PM

With these changes, only 294 MB RAM is used.
Screenshot 2020-01-28 at 9 26 02 PM

Note the caches use HashMaps and not LruCache, as was used previously. This is because only the BitmapDescriptors are cached indefinitely and the cache is only holding a reference to the same BitmapDescriptors used by the GoogleMap in its own view. So discarding any of these references wouldn't regain memory, as long as the BitmapDescriptors are still being used by the map view.

The original Bitmaps are only retained until all the images have been loaded, scaled, and cached as BitmapDescriptors.

Also fixes scaling for point marker icon images, taking into account screen density and scaling to a normalized minimum width of 32.

BitmapDescriptorFactory.fromBitmap() copies the Bitmap on every call. Avoid duplicating Bitmaps for each Marker by caching the scaled BitmapDescriptors.
@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Jan 29, 2020
@jeffdgr8 jeffdgr8 changed the title Improve KML bitmap caching Improve KML bitmap caching and fix icon scaling Jan 29, 2020
*/
public LruCache<String, Bitmap> getImagesCache() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is now a breaking change to other clients, can you describe the steps to migrate to this new API? At first glance, this seems like an internal API that should've been scoped as protected but it would be good to have in case developers are using this function.

Copy link
Contributor Author

@jeffdgr8 jeffdgr8 Feb 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I don't believe anyone would actually be using the Renderer API directly. Many of these methods should be protected, as they're used only by the Renderer subclasses, KmlRenderer and GeoJsonRenderer. Many of the public methods can't be package-private or protected only because they're used both in Layer (same package) and one of the subclasses, which are in different packages.

Code that utilizes the KML and GeoJSON functionality of the library will mostly be interacting with the Layer subclasses, KmlLayer and GeoJsonLayer, which don't even expose their Renderer objects directly in order to access those public methods. Renderer could almost be an abstract class if the code was refactored some. There's quite a bit of code that's specific to KML and GeoJSON that could also be moved to one or the other subclasses.

I went ahead and reduced the access scope on all the Renderer methods that could be, to either package-private or protected. 084c0ca In this particular method's case, a Renderer subclass, which I don't expect to exist outside of KmlRenderer and GeoJsonRenderer, would follow the pattern in these subclasses and call getCachedMarkerImage() or getCachedGroundOverlayImage() instead of accessing a cache directly as was previously done through this getImagesCache() method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that scoping should be reduced in that class. Thanks for addressing that!

* Holds initial references to bitmaps so they can be scaled and BitmapDescriptors cached.
* This cache is cleared once all icon URLs are loaded, scaled, and cached as BitmapDescriptors.
*/
private Map<String, Bitmap> mBitmapCache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: thoughts on wrapping mMarkerImagesCache, mGroundOverlayImagesCache and mBitmapCache into a class? I think this would help with readability and simplify the API a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. Done: e9d8f24

*/
public ArrayList<String> getMarkerIconUrls() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that this is another breaking change. I do think this is a break API though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As before, I don't believe anyone would actually be using the Renderer API directly. I've made this particular method protected now under this assumption. The previous ArrayList was always only adding unique icon URLs, but using a set makes this much more efficient, avoiding full list scanning for contains() each time one is added.

* BitmapDescriptors are cached to avoid creating new BitmapDescriptors for each individual
* usage of a Bitmap. Each BitmapDescriptor copies the Bitmap it's created from.
*/
private Map<String, Map<Double, BitmapDescriptor>> mMarkerImagesCache;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The probability of missing the cache can be pretty high having Double as a key for Map. I suggest storing this as String instead and have up to a certain precision level (e.g. 4 decimal places?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought about this briefly and concluded that while a Double may not be able to perfectly represent all parsed decimal strings, 0.1 for example, that at least the parsed string double value will be equal for the scale value used to cache the image and scale value used to access the cached scaled image. In other words, a double representation of 0.1 will equal another double representation of 0.1, both obtained from the same parse routine. In fact, the same double mScale variable from a KmlStyle is often being used to both cache and retrieve the cached images.

However, while I made this conclusion based on the current implementation, I do see how a bug could possibly be introduced in the future, if style scales were introduced from another source somehow, possibly with a lower float precision.

I made this change to use the string formatted scale value: f9bd8ba

*/
public LruCache<String, Bitmap> getImagesCache() {
return mImagesCache;
public BitmapDescriptor getCachedMarkerImage(String url, double scale) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd expect that getting a cached image would be pretty fast just from this method name. However, since this method will also perform image scaling, which is time consuming, it would be good to add a note that this is the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added more details to the doc comment: f99a787

@arriolac arriolac self-assigned this Feb 10, 2020
@arriolac arriolac changed the title Improve KML bitmap caching and fix icon scaling perf: Improve KML bitmap caching and fix icon scaling Feb 14, 2020
@arriolac arriolac merged commit 1f2a2dd into googlemaps:master Feb 14, 2020
@jeffdgr8 jeffdgr8 deleted the kml-bitmap-caching branch February 14, 2020 19:00
@arriolac arriolac added this to the 1.0 milestone Feb 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes This human has signed the Contributor License Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants