Reaching and surpassing the limits of JavaScript BigData with WebAssembly
Tags: webdev, javascript, assembly, beginners
Published: 2024-04-05 10:12:23

<!-- wp:paragraph -->
<p>We're working on an enhancement to SciChart.js - our WebAssembly &amp; WebGL Big Data <a href="https://www.scichart.com/javascript-chart-features/">JavaScript Chart library</a> to push the boundaries of performance. Recently we blogged that an internal build of SciChart.js can now <a href="/javascript-chart-performance-plotting-10-million-datapoints/">plot 10 Million Datapoints</a> in under 25ms. That's pretty astonishing for a JavaScript chart control! 👉</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We wanted to see if SciChart could go any further and plot even bigger data (100 Million Datapoints in a Line Chart), but we hit a browser limit...</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you allocate an array of size 100,000,000 (100 Million) in JavaScript, you get a strange error: <a href="https://stackoverflow.com/questions/70198991/javascript-pre-allocated-array-uncaught-rangeerror-invalid-array-length">Uncaught RangeError: Invalid Array Length</a>. Here's the code to reproduce it. Try it out, in the developer console in Chrome:</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-js">const COUNT = 100_000_000;
const xValues = new Array(COUNT);
const yValues = new Array(COUNT);
for (let i = 0; i &lt; COUNT; i++) {
    xValues[i] = i;
    yValues[i] = Math.sin(i * 0.000001);
}
console.log(`count: ${yValues.length}`);</pre></div>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>This will throw an exception in JavaScript pretty consistently.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33170,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/image-8.png" alt="" class="wp-image-33170"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>This puzzled me as I expected this to work easily, so I turned to StackOverflow to see if the JavaScript development community had an explanation. <a href="https://stackoverflow.com/q/70198991/303612">Here's the Question &amp; Answer</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="the-limits-of-javascript-memory-numerics-in-browser">The Limits of JavaScript Memory &amp; Numerics in Browser</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>It turns out, according to <a href="https://stackoverflow.com/a/70202993/303612">this answer</a>, JavaScript stores numbers as objects instead of double-precision (8-bytes per value). So an array of numbers is an array of objects on the heap. An <a href="https://stackoverflow.com/a/70202993/303612">object takes 50 bytes or more</a> on the JS heap, so 100M numbers requires ~5GB of memory. The browser heap limit is 4GB so with just 100 Million numbers in an array we're running out of memory in the browser.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We'll never know if SciChart.js can plot 100 Million datapoints 😢 Or will we?</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="how-webassembly-manages-memory">How WebAssembly manages memory</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We've built our <a href="https://www.scichart.com/javascript-chart-features/">High Performance JavaScript Charts</a> on a tried &amp; tested C++ codebase that we also use in our <a href="https://www.scichart.com/wpf-chart-features">WPF Charts</a>, <a href="https://www.scichart.com/ios-chart-features/">iOS Charts</a> &amp; <a href="https://www.scichart.com/android-chart-features/">Android Charts</a>. This code-sharing allowed us to become truly cross-platform, truly native, and truly high performance for our 2D &amp; 3D chart controls.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>With <a href="https://webassembly.org/">WebAssembly</a> we can compile our C++ codebase into a wasm module for the browser. So when you look at a SciChart.js chart you're actually seeing our C++ graphics engine wrapped for JavaScript.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Let's see if we can work around this memory limit in JavaScript using SciChart.js DataSeries, which uses WebAssembly.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>First up the boilerplate code for setting up a SciChartSurface in SciChart.js:</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"// Setup the SciChartSurfacen//nn// Create a SciChartSurface with X,Y Axisnconst { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId);nsciChartSurface.xAxes.add(new NumericAxis(wasmContext));nsciChartSurface.yAxes.add(new NumericAxis(wasmContext));nn// Create a Line RenderableSeries / DataSeries pairnconst dataSeries = new XyDataSeries(wasmContext);nsciChartSurface.renderableSeries.add(new FastLineRenderableSeries(wasmContext, { n   dataSeries n}));n   "} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-js">// Setup the SciChartSurface
//

// Create a SciChartSurface with X,Y Axis
const { wasmContext, sciChartSurface } = await SciChartSurface.create(divElementId);
sciChartSurface.xAxes.add(new NumericAxis(wasmContext));
sciChartSurface.yAxes.add(new NumericAxis(wasmContext));

// Create a Line RenderableSeries / DataSeries pair
const dataSeries = new XyDataSeries(wasmContext);
sciChartSurface.renderableSeries.add(new FastLineRenderableSeries(wasmContext, { 
   dataSeries 
}));
   </pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>Now appending the data. If we try 100 Million points in JavaScript arrays it fails for reasons mentioned before</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"// Create some x u0026 y values in JavaScript Arraysnconst COUNT = 100_000_000;nconst xValues = new Array(COUNT);nconst yValues = new Array(COUNT);nlet prevYValue = 0;nfor (let i = 0; i u003c COUNT; i++) {ntconst curYValue = Math.random() - .5;ntxValues[i] = i;n    // CRASH here with Uncaught RangeError: Invalid Array LengthntyValues[i] = prevYValue + curYValue; ntprevYValue += curYValue;n}nn// Append Js Arrays to the SciChart DataSeries. n// SciChart will draw the seriesndataSeries.appendRange(xValues, yValues);"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-js">// Create some x &amp; y values in JavaScript Arrays
const COUNT = 100_000_000;
const xValues = new Array(COUNT);
const yValues = new Array(COUNT);
let prevYValue = 0;
for (let i = 0; i &lt; COUNT; i++) {
const curYValue = Math.random() - .5;
xValues[i] = i;
    // CRASH here with Uncaught RangeError: Invalid Array Length
yValues[i] = prevYValue + curYValue; 
prevYValue += curYValue;
}

// Append Js Arrays to the SciChart DataSeries. 
// SciChart will draw the series
dataSeries.appendRange(xValues, yValues);</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>Instead of creating a 100 Million element JavaScript array, let's create 10 blocks of 10 Million datapoints and append them to a SciChart.js DataSeries, which uses the WebAssembly heap to hold double-precision numbers:</p>
<!-- /wp:paragraph -->

<!-- wp:scichart/code-snippet {"code":"// Using SciChart's DataSeries, reserve memory for 100 Million elementsndataSeries.getNativeXValues().reserve(100_000_000);ndataSeries.getNativeYValues().reserve(100_000_000);nlet prevYValue = 0;nlet x = 0;nfor (let i = 0; i u003c 10; i++) {ntconsole.log(`${i}: Building block. Total ${10_000_000 * (i + 1)}`);ntconst xValues = new Array(10_000_000);ntconst yValues = new Array(10_000_000);ntfor (let j = 0; j u003c BlockSize; j++) {nttconst curYValue = Math.random() - .5;nttxValues[j] = x++;nttyValues[j] = prevYValue + curYValue;nttprevYValue += curYValue;nt}n    // Append each 10 Million block to the DataSeriesntdataSeries.appendRange(xValues, yValues);n}"} -->
<div class="wp-block-scichart-code-snippet"><pre class="line-numbers code-toolbar language-js">// Using SciChart's DataSeries, reserve memory for 100 Million elements
dataSeries.getNativeXValues().reserve(100_000_000);
dataSeries.getNativeYValues().reserve(100_000_000);
let prevYValue = 0;
let x = 0;
for (let i = 0; i &lt; 10; i++) {
console.log(`${i}: Building block. Total ${10_000_000 * (i + 1)}`);
const xValues = new Array(10_000_000);
const yValues = new Array(10_000_000);
for (let j = 0; j &lt; BlockSize; j++) {
const curYValue = Math.random() - .5;
xValues[j] = x++;
yValues[j] = prevYValue + curYValue;
prevYValue += curYValue;
}
    // Append each 10 Million block to the DataSeries
dataSeries.appendRange(xValues, yValues);
}</pre></div>
<!-- /wp:scichart/code-snippet -->

<!-- wp:paragraph -->
<p>Does this work? <strong>Can SciChart.js plot 100 Million datapoints </strong>using Wasm memory?</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><!--kg-card-begin: html--><iframe title="YouTube video player" src="https://www.youtube.com/embed/RNhMBWhcPVU?rel=0&amp;loop=1&amp;playlist=RNhMBWhcPVU&amp;autoplay=1" width="100%" height="450" frameborder="0" allowfullscreen="allowfullscreen"></iframe><!--kg-card-end: html--></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Why yes it can 😁</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":33172,"sizeSlug":"full","linkDestination":"none"} -->
<figure class="wp-block-image size-full"><img src="https://www.scichart.com/wp-content/uploads/2022/05/mic-drop.gif" alt="" class="wp-image-33172"/></figure>
<!-- /wp:image -->

<!-- wp:heading -->
<h2 id="wen-release">When Release?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We'll be releasing an update to SciChart.js soon which will enable <strong>very big data</strong>. At the moment you can get SciChart.js version 2 from <a href="https://www.npmjs.com/search?q=scichart">npmjs</a> or from a CDN and render millions of datapoints with ease in the browser. SciChart enables the next generation of demanding Fintech, Medical, Scientific and Engineering applications that demand high performance. Try it out if you haven't already!</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><!--kg-card-begin: html--><a class="button" title="SciChart.js JavaScript Chart Library" href="https://www.scichart.com/javascript-chart-features/" target="_blank" rel="noopener noreferrer">Learn more about SciChart.js</a><!--kg-card-end: html--></p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 id="about-scichart-high-performance-realtime-charts"><strong><strong><strong><em><em><em><em><em><em><em><em>About SciChart: High Performance Realtime Charts</em></em></em></em></em></em></em></em></strong></strong></strong></h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><em><em><em><em><em><em><em><em>SciChart provides high performance real</em></em></em></em></em></em>-<em><em><em><em><em><em>time chart components on the WPF (Windows), iOS, Android and </em></em></em></em>JavaScript <em><em><em><em>platforms. It is our goal to create the best cross-platform Native WPF, iOS, Android and JavaScript 2D &amp; 3D Charts in the world, focusing on performance, developer productivity, ease of use, depth of features and enterprise-grade tech support.</em></em></em></em></em></em></em></em></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><em>If you have a question about what SciChart's WebGL / WebAssembly Charts can offer you, or you have a challenging or impossible data-visualization project, then <a href="https://www.scichart.com/contact-us/">contact us</a> and we will do our best to help!</em></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><!--kg-card-begin: html--><a class="button" title="Contact SciChart" href="https://www.scichart.com/contact-us/" target="_blank" rel="noopener noreferrer">Contact Us</a><!--kg-card-end: html--></p>
<!-- /wp:paragraph -->