echarts icon indicating copy to clipboard operation
echarts copied to clipboard

[Feature] Optional immutableMode to bypass expensive cloning and improve performance

Open sjcobb opened this issue 1 year ago • 4 comments

What problem does this feature solve?

In complex applications using frameworks like React, it is difficult to achieve optimal performance due to expensive cloning operations that have no way to opt out of. If the goal of these cloning operations (example) is to protect ECharts internals from mutations from users of the library, then it may be unnecessary as mutations are an anti-pattern in these contexts.

Having an optional (and initially experimental) property to tell ECharts to bypass cloning would put more responsibility on users to not abuse setOption calls, but could have a high upside for power users struggling with memory / growing JS heap size / inefficient CPU. It may also help with open memory leak issues: #7002, #10130, #19984, #7125, #18312, #18686, echarts-for-react/issues/533 (these may not actually be memory leaks but are a symptom of lingering performance issues).

What does the proposed API look like?

Introduce an optional immutableMode property to setOption opts, aimed at optimizing performance for large datasets, where charts needs to re-render frequently. A very rough concept can be seen here: https://github.com/sjcobb/echarts/pull/1

Open to feedback on other ideas, my branch is far from a working prototype, but I want to see if this idea has any traction before diving in further.

sjcobb avatar Jun 12 '24 00:06 sjcobb

You can see below how heap snapshots steadily increase even after garbage collection kicks in, which eventually results in the browser tab crashing:

image

image

Line charts using 'time' axis are especially problematic or it's possible a variable in the Scheduler (maybe related to agentStubMap or an event handler?) needs to be cleaned up. Edit: I found a related time axis bug ticket #19984

sjcobb avatar Jun 12 '24 15:06 sjcobb

This issue is also present in my project.

idealy233 avatar Jul 12 '24 07:07 idealy233

Example of clone operation causing browser tab to crash: image

image crash_symbols_bug

sjcobb avatar Dec 11 '24 16:12 sjcobb

I found that I can prevent ECharts from cloning options by using the setPrimitive function from zrender (the utility and drawing library that ECharts uses). Any objects marked by setPrimitive will be skipped by the clone and merge functions that ECharts uses.

For example, in React with echarts-for-react:

import type { EChartsOption } from 'echarts';
import ReactECharts, { type EChartsReactProps } from 'echarts-for-react';
import * as echarts from 'echarts/core';
import { util } from 'zrender';

function MyComponent() {
  const option = useMemo(() => {
    const option: EChartsOption = {
      xAxis: {
        type: 'category',
        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
      },
      yAxis: {
        type: 'value',
      },
      series: [
        {
          data: [150, 230, 224, 218, 135, 147, 260],
          type: 'line',
        },
      ],
    };

    util.setAsPrimitive(option);

    return option;
  }, []);

  return <ReactECharts echarts={echarts} option={option} notMerge />;
}

I haven't had much opportunity to test this yet, but all of my manual and automated tests pass, and the charts render ~15% faster with much less memory usage.

Marking objects as primitive also prevents zrender's merge function from working on them. Since I use the notMerge option (which I believe is a better fit for immutable-style usage), I don't think this should be an issue; I've verified that charts still update as expected in response to data changes.

Note: Since this does involve using internal APIs, it's entirely possible that it introduces bugs or has unintended side effects. If I find any problems, I'll update here.

joshkel avatar Jun 10 '25 14:06 joshkel