4

I have an Emscripten C++ Web Worker, which design is more efficient to transfer large data to a JavaScript program?

Since a web worker does clone() and serialise, to transfer through the web worker message system, there is some overhead here. Also some code is needed to translate the resulting data on the C++ side, from HEAP32 into JavaScript arrays ( C -> JS ).

By efficient, I mean which design is faster, i.e. which design leads to triggering less new and gc() (constructing and destructing JS objects). My Web Worker uses a core function written in C++which returns large arrays (two arrays of float[V][3] and int[N][3] with N=V=10000. It will be used to update a ThreeJS Geometry, and will be called tens of thousands of times over a long period on a web page. Apart being slow, this also may cause the browser to slow down, freeze or crash.

  1. Write a Web Worker using JS which imports a JS code compiles using Emscripten. Cons: This option seems not possible, as the web-worker side needs to import the compiles JS file. Data exchange: C++ -> JS -> message(serialise) -> JS. Design: (C++)JS <-WW-> JS. Files: core_mc_algorithm.cpp, worker.js, main.js .
  2. Use a C++ Web Worker compiled using -s BUILD_AS_WORKER=1, write some other C++ code on the main side that received the data, and convert the results from HEAP to JS on the main side: (WebWorker data traser handled by Emscripten): Pros: efficient transfer, but required two conversions. Risk: on C++ side, it requires multiple copying from vector to array, etc. Data exchange: C++ -> message(serialise) -> C++ -> JS, Design: (C++) <-WW-> C++(JS) . Files: worker.cpp, main.cpp, main.js .
  3. Again a C++ Web Worker, but the web worker function are contacted directly by the main program which is in JavaScript. Pros: The conversions/exchanges are not done twice. There is no separate exchange between C++ and JS, this conversion is done at the same time with WW serialisation. Risks: The decoding may be difficult and messy (the protocol will need to be reimplemented, which itself requires multiple conversions, which may not be very efficient). Also, the exchange may be not actually efficient and may not actually improve performance. Data exchange: C++ -> message(serialise) -> JS, Design: (C++) <-WW-> JS. Files: worker.cpp, main.js .

I have this code in C++, I want to run it as a Web Worker:

void produce_object (
     REAL* verts_output, int number_of_vertices,
     int* faces_output, int number_of_triangles ) {
    // Run Marching cubes, which produces a vector<int> and a vector<float>.
    // fills in the arrays verts_output[] with coordinates (size: 3*number_of_vertices), 
    // fill in faces_output[] with triangle vertex indices (size: 3*number_of_triangles ), using some numerical code which includes the Marching Cubes algorithm.
}

I need the following JavaScript callback function to get called with the right parameters. It is defined in an HTML file:

function update_mesh_geometry_callback (verts, faces) {
   /* verts and faces are of type Float32Array and Int32Array of size (3*N) and (3*V). In this function they are used to create the following object, which is added to the scene.*/
   var geo = new THREE.Geometry(verts, faces); // a subclass
   scene.add(new THREE.Mesh(gro, mat, etc));
}

Typical size at least: number_of_vertices == 90000 = N, number_of_triangles == 8000 = V.

Philipp
  • 2,376
  • 5
  • 29
  • 47
Sohail Si
  • 2,750
  • 2
  • 22
  • 36
  • 1
    Are those arrays of yours in the format of TypedArray? Because javascript TypedArray implementations contain byte stream that can be transfered instead of copying. – Tomáš Zato Jun 16 '16 at 19:34
  • Some rules of thumb for efficiency: 1) refrain from reallocating memory; reuse memory buffers. 2) Maximize data sizes when transferring data. 3) Design and code for optimal usage of processor data cache. – Thomas Matthews Jun 16 '16 at 19:35
  • @SohailSi I think your question lacks sufficient info to get meaningful answer. Narrow down what data are you going to transfer, how are they generated. – Tomáš Zato Jun 16 '16 at 19:37
  • Yes emscripten stored them in a HEAP array which is either Float32Array or other types. But when the typed array is serialised/cloned in WebWorker, is it efficient? Under the hood, does WebWorders transfer the content or the reference? – Sohail Si Jun 16 '16 at 19:37
  • I added the code for the declaration of C++ and JavaScript functions. – Sohail Si Jun 16 '16 at 19:59

1 Answers1

2

I believe you are after transferables. Worker has an extra parameter in postMessage method: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage

Transferables only work with ArrayBuffers, they also unset buffer from messaging thread, you have to keep that in mind, but for your case this seems like a perfect fit as it will avoid copying entirely.

here's a toy code example for what you would have in your worker

var vertices = new Float32Array(100000);
var faceIndices = new Uint32Array(50000);
postMessage({vertices: vertices, faceIndices: faceIndices}, [vertices.buffer, faceIndices.buffer]);

You can read more on transferrables here: Using transferable objects from a Web Worker

Community
  • 1
  • 1
travnik
  • 690
  • 7
  • 18
  • 1
    "avoid copying entirely"... I don't think this is quite right if you're sending things in to / out of the Emscripten-managed heap. – Michal Charemza Jun 21 '16 at 16:31
  • If we talk about worker interface - it's zero-copy, but as you pointed out - data still needs to be obtained from emscripten context. But then, you are still going to end up with data being copied from RAM to L3, to L2 to L1, to registers, any suggestions on how to avoid that, @MichalCharemza? – travnik Jun 24 '16 at 00:20
  • I fear that is beyond my knowledge... I have wondered if it is possible to copy memory from one RAM location to another without going through the processor (/intermediate caches), but know of no method. – Michal Charemza Jun 24 '16 at 06:28
  • @MichalCharemza regarding "if it is possible to copy memory from one RAM location to another without going through the processor", there are multiple ways. DMA (Direct memory access) is a classical solution about this. – Sohail Si Jun 28 '16 at 09:56