Is there a more efficient way to return arrays from C++ to javascript?

生来就可爱ヽ(ⅴ<●) 提交于 2021-02-10 18:15:08

问题


To pass typed array from emscripten'ed C++ to javascript I came up with this code

#include <emscripten/bind.h>
#include <emscripten/val.h>

auto test(const emscripten::val &input) {
  const auto data = emscripten::convertJSArrayToNumberVector<float>(input); // copies data

  // generate output in some form
  std::vector<float> output = { 1, 2, 3 };
  // make a typed array view of the output
  emscripten::val view{ emscripten::typed_memory_view(output.size(), output.data()) };
  // create new typed array to return
  auto result = emscripten::val::global("Float32Array").new_(output.size());
  // copy data from generated output to return object
  result.call<void>("set", view);

  return result;
}

EMSCRIPTEN_BINDINGS(KissFft) {
  emscripten::function("test", &test);
}

(build with em++ test.cpp -o test.html --bind)

In this case there are two extra copies:

  • the copy from input array to wasm memory, as far as I understand it's unavoidable;
    const auto data = emscripten::convertJSArrayToNumberVector<float>(input);
    
  • the copy from wasm memory to javascript object:
    emscripten::val view{ emscripten::typed_memory_view(output.size(), output.data()) };
    auto result = emscripten::val::global("Float32Array").new_(output.size());
    result.call<void>("set", view);
    
    return result;
    

Is there a way to avoid extra copy from generated output to javascript object in the second case?

I'm aware of the possibility to return memory view like this:

std::vector<float> output;

auto test(const emscripten::val &input) {
  const auto data = emscripten::convertJSArrayToNumberVector<float>(input);

  //generate output
  return emscripten::val{ emscripten::typed_memory_view(output.size(), output.data()) };
}

EMSCRIPTEN_BINDINGS(KissFft) {
  emscripten::function("test", &test);
}

But in this case the returned object refers to the underlying memory owned by output static object with all the consequences, like modifying the memory on the C++ side, or even deallocating it.


回答1:


I was thinking to go lower a bit. Putting here (instead of comment) to paste snippets (taken from embind examples from emscripten, here in C but you may do the same with C++).

// quick_example.cpp
#include <emscripten/bind.h>
#include <stdio.h>

using namespace emscripten;

struct buffer {
  unsigned int pointer;
  unsigned int size;
};

buffer lerp() {
    buffer myBuffer;
    unsigned int size = 10;

    float * myTab = (float*)malloc(size * sizeof(float));

    for (int i = 0; i < size; i++) {
      myTab[i] = 2.5 * i;
      printf(" Native side index: %d value: %f address: %p\n", i, myTab[i], &myTab[i]);
    }

    myBuffer.pointer = (unsigned int) myTab; 
    myBuffer.size = size;

    printf(" Native side pointer: %p size: %d\n", myTab, size);
    return myBuffer;
}

EMSCRIPTEN_BINDINGS(my_module) {
    value_array<buffer>("buffer")
        .element(&buffer::pointer)
        .element(&buffer::size)
        ;

    function("lerp", &lerp);
}

index.html - here in js you may copy, make a view and finally free the memory what (as I understood was one of the problems from above?)

   <!doctype html>
    <html>
      <script>
        var Module = {
          onRuntimeInitialized: function() {
            var result =  Module.lerp();
            console.log(" JS side pointer: " + result[0] + " size: " + result[1]);
     
            for (var i = 0; i < result[1]; i++) {
              console.log("index: " + i + " value: " + Module.HEAPF32[(result[0] + i * 4) / 4] + " pointerInc: " + (result[0] + i * 4));
            }
    
            Module._free(result[0]);
          }
        };
      </script>
      <script src="lerp.js"></script>
    </html>

build command

emcc --bind -o lerp.js lerp.cpp


来源:https://stackoverflow.com/questions/65566923/is-there-a-more-efficient-way-to-return-arrays-from-c-to-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!