Angular-google-maps: Map CPU utilization is extremely high

Created on 18 Jul 2016  路  10Comments  路  Source: SebastianM/angular-google-maps

I am placing ~600 markers on a map with info windows. I do this with an *ngFor that gets filled by a subscription.

If I don't update the markers, it works at a reasonable rate. If I do add in markers, it takes 21 seconds to load the page.

urgent performance

Most helpful comment

I did some profiling today to find out where's the bottleneck. The one thing that @otodockal described here (instantiating the google.Map inside the zone) is not a real performance problem.

The real problem is the huge amount of mouse and position listeners. But you can get a huge performance improvement with this method:

Profiling

Here's a quick comparison between the two ChangeDetection modes for the following map component - it creates 15,000 polylines with each two points:

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'ad-comp-1',
  template: `
  <sebm-google-map [latitude]="5" [longitude]="32" [zoom]="3">
    <sebm-google-map-polyline *ngFor="let a of numbers" [attr.id]="a">
        <sebm-google-map-polyline-point [latitude]="5+(a/100)+0.3" [longitude]="32+(a/100)">
        </sebm-google-map-polyline-point>
        <sebm-google-map-polyline-point [latitude]="5+(a/100)+0.5" [longitude]="32+(a/100)+0.3">
        </sebm-google-map-polyline-point>
     </sebm-google-map-polyline>
  </sebm-google-map>
  `,
  styleUrls: ['./comp-1.component.css']
})
export class Comp1Component {
  lat = 4;
  lng = 30;
  checks = 0;
  numbers = [];
  constructor() { 
    for(let i = 10; i < 15000; i++) {
      this.numbers.push(i);
    }

  }
}

1) ChangeDetectionStrategy.Default for the component that has the map:
When you drag the map, you will recognize some little lags.
Here are the profiling stats of ng.profiler.timeChangeDetection({record: true});:

ran 5 change detection cycles
2016-11-18 14:19:51.557 common_tools.js:84 192.82 ms per check

default
This is of course really slow :)

2) Now let's change the ChangeDetectionStrategy to OnPush:
Here are the stats:

ran 28305 change detection cycles
2016-11-18 14:26:43.070 common_tools.js:84 0.02 ms per check

onpush

This is a huge improvement in performance (because the change detection doesn't run on every event outside the component). So this is quick win if you have thousands of components

Possible improvements

  1. I think InfoWindows is a special case. Maybe can initialize the DOM elements when the info window needs to be visible. I have to dig deeper if this is possible.
  2. Currently it's not possible to detect if an EventEmitter is in use or not. So we have to register a Google Maps event listener for _every possible event_*. A possible improvement for mouse and dragging events could be an opt-in mechanism. So the developer has to say that he want's to listen to these changes (because we can't detect it via the Eventemitters):
<-- for example--->
<sebm-google-map [listenForDragEvents]="true" [listenForMouseEvents]="true">
</sebm-google-map>

Long story short: Use ChangeDetectionStrategy.OnPush for your components that use <sebm-google-map>

// cc @haburka @alexweber @Tolomeo @otodockal

All 10 comments

@haburka Would you mind please forking this plunk and reproducing the problem there so we can better assist you? Thanks!

@haburka @alexweber Hi. I have the same problem but my case is 5,000 markers on a map. I created the plunk page that place 600 markers and there is no problem.

I think this problem has something to do with contents of info windows.

When I run the plunk, it takes about 6-7 seconds to fully load, which is incredibly slow compared to vanilla google maps. At your scale, I'm sure it takes much longer.

When I profile, it tells me that the app is mostly spending CPU time detecting changes. Perhaps this is an issue with angular 2 itself? When you have so many data bindings I'm sure just about anything can get very slow.

Hi I have the same problem here trying to visualise ~7000 markers,
I'think @TSHiYK is right, the problem comes up when using info windows.

I'll try to get deeper into the problem spec as soon as I can

@haburka @Tolomeo I set a custom component instead of sebm-google-map-marker and use the GoogleMapsAPIWrapper to get the instance of it as the one used by the map. Then I use a setMap method of original google map API and It takes 1-2 seconds to load 5,000 markers at my condition.

hey guys, check out this issue

https://github.com/angular/angular/issues/10883#issuecomment-240423378

I think some events should be run outside of Angular. E.g. you probably don't need to run change detection on scroll evt. etc.

Personally, I use Leafletjs and there is the same problem so I isolate the whole lib and only trigger change detection on particular events.

But for library authors it's pain! Maybe good documentation can help, but...

@otodockal cool, wrote the same about Zones a few minutes ago here 馃樃 https://github.com/SebastianM/angular2-google-maps/issues/579#issuecomment-242479924

I have three features in my mind that we should add:

1) Switch to ChangeDetectionStrategy.OnPush - this should be simple

2) Refactor the *Manager Services that they work with an Interface rather than a certain Component (for example SebmGoogleMapMarker). Then everyone can add a custom marker implementation (for example) and use the existing managers).

3) Introduce new directives for rendering a huge amount of markers/infoWindows/etc.

<sebm-google-map-markers [markers]="myMarkersArray"></sebm-google-map-markers>

I did some profiling today to find out where's the bottleneck. The one thing that @otodockal described here (instantiating the google.Map inside the zone) is not a real performance problem.

The real problem is the huge amount of mouse and position listeners. But you can get a huge performance improvement with this method:

Profiling

Here's a quick comparison between the two ChangeDetection modes for the following map component - it creates 15,000 polylines with each two points:

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'ad-comp-1',
  template: `
  <sebm-google-map [latitude]="5" [longitude]="32" [zoom]="3">
    <sebm-google-map-polyline *ngFor="let a of numbers" [attr.id]="a">
        <sebm-google-map-polyline-point [latitude]="5+(a/100)+0.3" [longitude]="32+(a/100)">
        </sebm-google-map-polyline-point>
        <sebm-google-map-polyline-point [latitude]="5+(a/100)+0.5" [longitude]="32+(a/100)+0.3">
        </sebm-google-map-polyline-point>
     </sebm-google-map-polyline>
  </sebm-google-map>
  `,
  styleUrls: ['./comp-1.component.css']
})
export class Comp1Component {
  lat = 4;
  lng = 30;
  checks = 0;
  numbers = [];
  constructor() { 
    for(let i = 10; i < 15000; i++) {
      this.numbers.push(i);
    }

  }
}

1) ChangeDetectionStrategy.Default for the component that has the map:
When you drag the map, you will recognize some little lags.
Here are the profiling stats of ng.profiler.timeChangeDetection({record: true});:

ran 5 change detection cycles
2016-11-18 14:19:51.557 common_tools.js:84 192.82 ms per check

default
This is of course really slow :)

2) Now let's change the ChangeDetectionStrategy to OnPush:
Here are the stats:

ran 28305 change detection cycles
2016-11-18 14:26:43.070 common_tools.js:84 0.02 ms per check

onpush

This is a huge improvement in performance (because the change detection doesn't run on every event outside the component). So this is quick win if you have thousands of components

Possible improvements

  1. I think InfoWindows is a special case. Maybe can initialize the DOM elements when the info window needs to be visible. I have to dig deeper if this is possible.
  2. Currently it's not possible to detect if an EventEmitter is in use or not. So we have to register a Google Maps event listener for _every possible event_*. A possible improvement for mouse and dragging events could be an opt-in mechanism. So the developer has to say that he want's to listen to these changes (because we can't detect it via the Eventemitters):
<-- for example--->
<sebm-google-map [listenForDragEvents]="true" [listenForMouseEvents]="true">
</sebm-google-map>

Long story short: Use ChangeDetectionStrategy.OnPush for your components that use <sebm-google-map>

// cc @haburka @alexweber @Tolomeo @otodockal

up

I only have ~200 points, and with ChangeDetectionStrategy.OnPush the map loads and runs smooth until I start clicking on info windows. I'm using the [isOpen] binding to keep only one info window open at a time and with each new info window I click on the CPU continues to increase. After about 6-7 it's around 80% and stays there even with all info windows closed.

Is that what you were referring to in your point 1 that Info Windows were a special case? As in, OnPush doesn't help here?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DeveloperAdd007 picture DeveloperAdd007  路  3Comments

ChrisDevinePimss picture ChrisDevinePimss  路  3Comments

Subhojit1992 picture Subhojit1992  路  3Comments

n1t3w0lf picture n1t3w0lf  路  3Comments

marcelinobadin picture marcelinobadin  路  3Comments