ProEssentialsJS 的数据处理

浏览器中的零拷贝:为什么在 Win32 上很容易,为什么浏览器打破了它,以及我们如何解决

JavaScript chart zero-copy data loading
WebAssembly heap data transfer
typed array chart data
real time JavaScript chart data
zero copy WebAssembly charting
oscilloscope data acquisition chart
high volume chart data handling
JavaScript chart memory efficiency

Data handling in ProEssentialsJS
Zero-copy, in a browser

Most charting libraries spend more time moving your data than drawing it. ProEssentialsJS does not move it at all. The chart allocates the memory, you write your values straight into that memory, and the engine renders from those exact bytes.

This page explains why that was easy on Windows, why the browser broke it, and what we did about it.

On Win32 this was never hard

A native chart and the application that hosts it share one address space. You hand the engine a pointer to your array and it reads your memory directly. There is no second copy, so there is nothing to keep in sync and nothing to pay for on each frame.

ProEssentials has worked this way since the 1990s. It is the reason the desktop product plots 100 million points without a staging buffer, and the reason a real-time application can replace its whole data set on every sweep without the chart noticing the difference.

The browser breaks that assumption

JavaScript has no pointers. The WebAssembly heap is a separate ArrayBuffer that your JavaScript objects do not live in. So the ordinary path for getting data into a web chart looks like this:

your data  >  array of objects  >  serialize  >  typed array  >  upload to the renderer

Four representations of the same numbers, and a real-time application pays for all four on every frame. That is the tax the JavaScript charting market has accepted. It is also why most web chart demos load a data set once and then pan around it, rather than replacing it.

What we did instead

We inverted the ownership. Instead of you allocating an array and handing it to the chart, the chart allocates a block in the WebAssembly heap and hands it to you. You write into that block. The engine reads the same bytes.

const block = Pesgo1.PeData.Yii.allocate(count)
block.array.set(myFloat32Data, 0)
Pesgo1.PeData.Yii.useDataAtLocation(block, count)
Pesgo1.PeFunction.ReinitializeResetImage()

Change a value in the block after you have handed it over and the next frame shows it, because there was never a second copy to fall out of step. The usual JavaScript path is an array of objects, serialized, converted to a typed array, then uploaded. This is none of those.

The API

Both the flat and the jagged forms are supported, on all four axes, in float and integer-indexed variants. Passing a null block releases the pointer.

These are called on the data array itself, not on the chart, and that is deliberate: the array decides the block width, so a block allocated from an array can never be the wrong type for it.

JavaScriptC#What it does
allocate(n)-Allocate a block sized and typed for this array.
useDataAtLocation(block, count)UseDataAtLocation(source, nBufferSize)Render from this block. No copy.
useDataAtLocation()UseDataAtLocation()Release the pointer.
useJaggedDataAtLocation(block, subset, count)UseJaggedDataAtLocation(source, nSubset)Same, for ragged subsets of unequal length.
useJaggedDataAtLocation()UseJaggedDataAtLocation()Release, per subset.
block.free()-Free the block, after releasing the pointer.

Every other way you have handed data to ProEssentials still works: fastCopyFrom, append, appendSubset, the jagged forms, initialize, clear and the length arrays. Zero-copy is the fast path, not the only path.

These array helpers are the one place the JavaScript spelling differs.Everything else carries the C# name exactly, methods included: PeFunction.ReinitializeResetImage, Reset, GetRectGraph and the rest are spelled as C# spells them. The data-array helpers are camelCase, and C#'s AppendData is simply append here.

Three rules that matter

The block must outlive the chart that uses it. The engine holds a pointer into the heap, not a copy. Release the pointer before you free the block, in that order.

Use block.array.set, not block.set. The second is a per-element loop and will dominate your frame. The first is a typed-array copy at memory speed.

Re-take the view after the heap grows. A typed-array view onto the WebAssembly heap detaches when the heap is resized. Take the view again each tick rather than holding one across allocations.

What it buys

Not raw speed. The copy you avoid runs at 8 to 14 GB/s and accounts for between 0.3 and 2.3 percent of a frame. Removing it does not make the chart measurably faster on any single frame.

What it buys is the ability to re-pass everything, every frame, at sizes where a staging copy would be the whole budget. GigaPrime2D replaces all 25,000,000 points across five signals on five stacked Y axes on every frame and holds 25 frames per second, and it does 100,000,000 points at 7 frames per second, on a 2020-era RTX 3090. That is the data-acquisition and oscilloscope case, not a static load you pan around.

The full ladder, from 400,000 points to 100 million, and how to reproduce it

It also buys predictable memory. There is no second copy of your data sitting in the renderer, so a 400 MB data set costs 400 MB, not 800.

Run it yourself rather than taking our word for it. The same demo is one of the three under Showcase, and the walkthrough builds a chart from nothing in about ten minutes. Frame rates at every size are on the fastest JavaScript chart page, and the full comparison against Highcharts, SciChart.js and LightningChart JS is on the JavaScript chart comparison page.