xieziyu / ngx-echarts

An angular (ver >= 2.x) directive for ECharts (ver >= 3.x)
https://xieziyu.github.io/ngx-echarts/
MIT License
1.1k stars 197 forks source link

Charts constantly using CPU resources #15

Closed RozennK closed 7 years ago

RozennK commented 7 years ago

As soon as a chart is initialized, even if it contains no data otherwise, it uses CPU resources even though it seems that if it's doing nothing, it shouldn't. How high the CPU usage is depends on the application but there always seems to be some.

This includes the ngx-echarts demo page, where I get 96% CPU usage from one Chrome process (as opposed to 0 everywhere on the Baidu echarts demo page).

Looking at the performance in Chrome's DevTools it seems a timer is triggered every 18ms or so that leads to various function calls.

Is this expected behavior? It doesn't seem so given the difference between the ngx-echarts and Baidu echarts demo pages, and it is causing problems on slower computers.

xieziyu commented 7 years ago

@RozennK Good point! But, this issue is not caused by ngx-echarts directive itself. It happens because my demo page is using the DEFAULT ChangeDetectionStrategy of angular.

By default, the change detection strategy on any component or directive is CheckAlways. There is another strategy, OnPush, which can be much more efficient if I build my demo page carefully.

The timer you noticed is started by angular framework itself. It needs to empirically determine that each and every time, for every single piece of update-able DOM in the application.

You can use changeDetection: ChangeDetectionStrategy.OnPush in the demo page component to avoid this issue. Codes:

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

import * as demo from './demo';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush    // use OnPush change detection strategy.
})
export class AppComponent {
  // ...
}
RozennK commented 7 years ago

Thank you for your quick reply; changing the detection strategy does work so I hope I can use that.

Having said that I'm still not sure where that behavior comes from: AFAICT, Angular2 change detection is supposed to happen every time a task is executed (via the tick function called in zone.js/NgZone), not every 16ms. And in fact I don't have the issue on other components in my application. Putting a listener on NgZone's "onStable" event, which fires every time a a zone "turn" is done, shows that indeed the event is triggered all the time on pages with a chart in them, but is only triggered on other pages when something actually happens.

It looks like echarts is performing some task every few ms, which causes Angular to check for changes in the DOM at that interval, unless the change detection strategy is modified. Is that task necessary?

Changing the detection strategy has big impacts on the rest of the application. It's probably something I'd need to do anyway but if there's an issue with the charts themselves I would like to solve that too.

EDIT: Checking again I see that indeed, when I use "ChangeDetectionStrategy.onPush" even though the CPU usage goes back down to zero, looking closely at the performance in DevTools I still have an "Animation Frame Fired" task happen every 16 or so milliseconds.

EDIT AGAIN: ... but this time the same event does show up in the echarts examples on Baidu's website, such as here: http://echarts.baidu.com/echarts2/doc/example/line1.html#-en

so it does seem like an intrinsic aspect of echarts. There is no way the impacts on Angular could be limited, by using something like NgZone's "runOutsideAngular" ?

xieziyu commented 7 years ago

@RozennK I agree with your concern about the intrinsic aspect of echarts itself. I'm too busy to investigate the source codes of echarts, so I could not know why it's performing a periodic task.

I would appreciate it if you found any better solution

RozennK commented 7 years ago

It does seem a bit challening to find out what's doing on in Echarts. There's this line that's tantalizing:

this._throttledZrFlush = throttle.throttle(zrUtil.bind(zr.flush, zr), 17);

but the function doesn't seem to be called in ways that would make a difference, not in a standard browser at least. The same call to animationFrame seems to happen in the ZRender example, so that's probably where it starts.

As far as hiding the timer from Angular's change detection goes however, it seems that replacing the following lines in AngularEchartsDirective's function "createChart":

return echarts.init(this.el.nativeElement, this.theme) return echarts.init(this.el.nativeElement)

with

return this._ngZone.runOutsideAngular(() => {return echarts.init(this.el.nativeElement, this.theme)}); return this._ngZone.runOutsideAngular(() => {return echarts.init(this.el.nativeElement)});

(and declaring _ngZone in the constructor as type NgZone), actually seems to work so far. The chart is displayed, it seems to behave as it should, and NgZone's "onStable" event is only triggered on window resizes and other actual changes to the chart.

xieziyu commented 7 years ago

@RozennK It's awesome! Thanks for your great help.