diff options
author | Alon Zakai <alonzakai@gmail.com> | 2013-05-21 12:39:07 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2013-05-21 12:39:07 -0700 |
commit | abe634001c34f74fabc660d61c37cbe44f44a443 (patch) | |
tree | 7005e3c219fe78ab606542c2b661a7c8efcaad5f /tests | |
parent | f46a8ba8cadd98ddfb6e13a6c34aa890f3355bfd (diff) | |
parent | 954f70274ed3b85b9d3885c6087df96376ca07d3 (diff) |
Merge pull request #1185 from imvu/embind-performance-and-code-size-improvements
Embind performance and code size improvements
Diffstat (limited to 'tests')
-rw-r--r-- | tests/embind/build_benchmark | 2 | ||||
-rw-r--r-- | tests/embind/embind.benchmark.js | 35 | ||||
-rw-r--r-- | tests/embind/embind.test.js | 153 | ||||
-rw-r--r-- | tests/embind/embind_benchmark.cpp | 71 | ||||
-rw-r--r-- | tests/embind/embind_test.cpp | 65 |
5 files changed, 321 insertions, 5 deletions
diff --git a/tests/embind/build_benchmark b/tests/embind/build_benchmark index 6faad18b..3d5d816b 100644 --- a/tests/embind/build_benchmark +++ b/tests/embind/build_benchmark @@ -1,2 +1,2 @@ #!/bin/bash -EMCC_LLVM_TARGET=le32-unknown-nacl ~/projects/emscripten/emcc --minify 0 --bind --post-js embind.benchmark.js -O2 --shell-file shell.html -o embind_benchmark.html embind_benchmark.cpp +EMCC_LLVM_TARGET=le32-unknown-nacl ../../emcc --minify 0 --bind --post-js embind.benchmark.js -O2 --shell-file shell.html -o embind_benchmark.html embind_benchmark.cpp diff --git a/tests/embind/embind.benchmark.js b/tests/embind/embind.benchmark.js index 7b20db88..3669bc28 100644 --- a/tests/embind/embind.benchmark.js +++ b/tests/embind/embind.benchmark.js @@ -248,3 +248,38 @@ function _call_through_interface1() { Module.print("C++ -> JS std::wstring through interface " + N + " iters: " + elapsed + " msecs."); obj.delete(); } + +function _call_through_interface2() { + var N = 1000000; + var total = 0; + var obj = Module['Interface'].implement({ + call_with_typed_array: function(ta) { + total += ta.length; + }, + call_with_memory_view: function(ta) { + total += ta.length; + }, + }); + + var start = _emscripten_get_now(); + Module['callInterface2'](N, obj); + var elapsed = _emscripten_get_now() - start; + Module.print("C++ -> JS typed array instantiation " + N + " iters: " + elapsed + " msecs."); + + var start = _emscripten_get_now(); + Module['callInterface3'](N, obj); + var elapsed = _emscripten_get_now() - start; + Module.print("C++ -> JS memory_view instantiation" + N + " iters: " + elapsed + " msecs."); + obj.delete(); +} + +function _returns_val_benchmark() { + var N = 1000000; + var v = 1; + var start = _emscripten_get_now(); + for(var i = 0; i < N; ++i) { + v = Module['returns_val'](v); + } + var elapsed = _emscripten_get_now() - start; + Module.print("returns_val " + N + " iters: " + elapsed + " msecs"); +} diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 52b2cad8..e60e1ab3 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -5,6 +5,8 @@ module({ var CheckForLeaks = fixture("check for leaks", function() { this.setUp(function() { + cm.setDelayFunction(undefined); + if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! cm._mallocDebug(2); assert.equal(0, cm.count_emval_handles()); @@ -12,6 +14,7 @@ module({ } }); this.tearDown(function() { + cm.flushPendingDeletes(); if (typeof INVOKED_FROM_EMSCRIPTEN_TEST_RUNNER === "undefined") { // TODO: Enable this to work in Emscripten runner as well! cm._mallocAssertAllMemoryFree(); assert.equal(0, cm.count_emval_handles()); @@ -490,6 +493,15 @@ module({ assert.equal(true, cm.emval_test_not(false)); }); + test("can pass booleans as integers", function() { + assert.equal(1, cm.emval_test_as_unsigned(true)); + assert.equal(0, cm.emval_test_as_unsigned(false)); + }); + + test("can pass booleans as floats", function() { + assert.equal(2, cm.const_ref_adder(true, true)); + }); + test("convert double to unsigned", function() { var rv = cm.emval_test_as_unsigned(1.5); assert.equal('number', typeof rv); @@ -672,6 +684,15 @@ module({ c.delete(); }); + test("access multiple smart ptr ctors", function() { + var a = new cm.MultipleSmartCtors(10); + assert.equal(a.WhichCtorCalled(), 1); + var b = new cm.MultipleCtors(20, 20); + assert.equal(b.WhichCtorCalled(), 2); + a.delete(); + b.delete(); + }); + test("wrong number of constructor arguments throws", function() { assert.throws(cm.BindingError, function() { new cm.MultipleCtors(); }); assert.throws(cm.BindingError, function() { new cm.MultipleCtors(1,2,3,4); }); @@ -1559,6 +1580,28 @@ module({ impl.delete(); }); + test("returning null shared pointer from interfaces implemented in JS code does not leak", function() { + var impl = cm.AbstractClass.implement({ + returnsSharedPtr: function() { + return null; + } + }); + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("returning a new shared pointer from interfaces implemented in JS code does not leak", function() { + var impl = cm.AbstractClass.implement({ + returnsSharedPtr: function() { + return cm.embind_test_return_smart_derived_ptr(); + } + }); + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + test("void methods work", function() { var saved = {}; var impl = cm.AbstractClass.implement({ @@ -1728,6 +1771,116 @@ module({ e.delete(); f.delete(); }); + + BaseFixture.extend("memory view", function() { + test("can pass memory view from C++ to JS", function() { + var views = []; + cm.callWithMemoryView(function(view) { + views.push(view); + }); + assert.equal(3, views.length); + + assert.instanceof(views[0], Uint8Array); + assert.equal(8, views[0].length); + assert.deepEqual([0, 1, 2, 3, 4, 5, 6, 7], [].slice.call(new Uint8Array(views[0]))); + + assert.instanceof(views[1], Float32Array); + assert.equal(4, views[1].length); + assert.deepEqual([1.5, 2.5, 3.5, 4.5], [].slice.call(views[1])); + + assert.instanceof(views[2], Int16Array); + assert.equal(4, views[2].length); + assert.deepEqual([1000, 100, 10, 1], [].slice.call(views[2])); + }); + }); + + BaseFixture.extend("delete pool", function() { + test("can delete objects later", function() { + var v = new cm.ValHolder({}); + v.deleteLater(); + assert.deepEqual({}, v.getVal()); + cm.flushPendingDeletes(); + assert.throws(cm.BindingError, function() { + v.getVal(); + }); + }); + + test("calling deleteLater twice is an error", function() { + var v = new cm.ValHolder({}); + v.deleteLater(); + assert.throws(cm.BindingError, function() { + v.deleteLater(); + }); + }); + + test("deleteLater returns the object", function() { + var v = (new cm.ValHolder({})).deleteLater(); + assert.deepEqual({}, v.getVal()); + }); + + test("deleteLater throws if object is already deleted", function() { + var v = new cm.ValHolder({}); + v.delete(); + assert.throws(cm.BindingError, function() { + v.deleteLater(); + }); + }); + + test("delete throws if object is already scheduled for deletion", function() { + var v = new cm.ValHolder({}); + v.deleteLater(); + assert.throws(cm.BindingError, function() { + v.delete(); + }); + }); + + test("deleteLater invokes delay function", function() { + var runLater; + cm.setDelayFunction(function(fn) { + runLater = fn; + }); + + var v = new cm.ValHolder({}); + assert.false(runLater); + v.deleteLater(); + assert.true(runLater); + assert.false(v.isDeleted()); + runLater(); + assert.true(v.isDeleted()); + }); + + test("deleteLater twice invokes delay function once", function() { + var count = 0; + var runLater; + cm.setDelayFunction(function(fn) { + ++count; + runLater = fn; + }); + + (new cm.ValHolder({})).deleteLater(); + (new cm.ValHolder({})).deleteLater(); + assert.equal(1, count); + runLater(); + (new cm.ValHolder({})).deleteLater(); + assert.equal(2, count); + }); + + test('The delay function is immediately invoked if the deletion queue is not empty', function() { + (new cm.ValHolder({})).deleteLater(); + var count = 0; + cm.setDelayFunction(function(fn) { + ++count; + }); + assert.equal(1, count); + }); + + // The idea is that an interactive application would + // periodically flush the deleteLater queue by calling + // + // setDelayFunction(function(fn) { + // setTimeout(fn, 0); + // }); + }); }); /* global run_all_tests */ diff --git a/tests/embind/embind_benchmark.cpp b/tests/embind/embind_benchmark.cpp index b6a834c9..5ae9a6be 100644 --- a/tests/embind/embind_benchmark.cpp +++ b/tests/embind/embind_benchmark.cpp @@ -50,6 +50,14 @@ extern void pass_gameobject_ptr_benchmark_embind_js(); extern void call_through_interface0(); extern void call_through_interface1(); +extern void call_through_interface2(); + +extern void returns_val_benchmark(); +} + +emscripten::val returns_val(emscripten::val value) +{ + return emscripten::val(value.as<unsigned>() + 1); } class Vec3 @@ -135,19 +143,45 @@ class Interface public: virtual void call0() = 0; virtual std::wstring call1(const std::wstring& str1, const std::wstring& str2) = 0; + virtual void call_with_typed_array(size_t size, const void*) = 0; + virtual void call_with_memory_view(size_t size, const void*) = 0; }; +EMSCRIPTEN_SYMBOL(HEAP8); +EMSCRIPTEN_SYMBOL(buffer); + +EMSCRIPTEN_SYMBOL(call0); +EMSCRIPTEN_SYMBOL(call1); +EMSCRIPTEN_SYMBOL(call_with_typed_array); +EMSCRIPTEN_SYMBOL(call_with_memory_view); +EMSCRIPTEN_SYMBOL(Uint8Array); + class InterfaceWrapper : public emscripten::wrapper<Interface> { public: EMSCRIPTEN_WRAPPER(InterfaceWrapper); void call0() override { - return call<void>("call0"); + return call<void>(call0_symbol); } std::wstring call1(const std::wstring& str1, const std::wstring& str2) { - return call<std::wstring>("call1", str1, str2); + return call<std::wstring>(call1_symbol, str1, str2); + } + + void call_with_typed_array(size_t size, const void* data) { + return call<void>( + call_with_typed_array_symbol, + emscripten::val::global(Uint8Array_symbol).new_( + emscripten::val::module_property(HEAP8_symbol)[buffer_symbol], + reinterpret_cast<uintptr_t>(data), + size)); + } + + void call_with_memory_view(size_t size, const void* data) { + return call<void>( + call_with_memory_view_symbol, + emscripten::memory_view(size, data)); } }; @@ -180,6 +214,33 @@ void callInterface1(unsigned N, Interface& o) { } } +void callInterface2(unsigned N, Interface& o) { + int i = 0; + for (unsigned i = 0; i < N; i += 8) { + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + o.call_with_typed_array(sizeof(int), &i); + } +} + +void callInterface3(unsigned N, Interface& o) { + for (unsigned i = 0; i < N; i += 8) { + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + o.call_with_memory_view(sizeof(int), &i); + } +} + EMSCRIPTEN_BINDINGS(benchmark) { using namespace emscripten; @@ -225,6 +286,10 @@ EMSCRIPTEN_BINDINGS(benchmark) function("callInterface0", &callInterface0); function("callInterface1", &callInterface1); + function("callInterface2", &callInterface2); + function("callInterface3", &callInterface3); + + function("returns_val", &returns_val); } void __attribute__((noinline)) emscripten_get_now_benchmark(int N) @@ -435,4 +500,6 @@ int main() emscripten_get_now(); call_through_interface0(); call_through_interface1(); + call_through_interface2(); + returns_val_benchmark(); } diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 23761efc..3561b8a1 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -1084,6 +1084,7 @@ public: return "optional" + s; } + virtual std::shared_ptr<Derived> returnsSharedPtr() = 0; virtual void differentArguments(int i, double d, unsigned char f, double q, std::string) = 0; }; @@ -1103,6 +1104,10 @@ public: }, s); } + std::shared_ptr<Derived> returnsSharedPtr() { + return call<std::shared_ptr<Derived> >("returnsSharedPtr"); + } + void differentArguments(int i, double d, unsigned char f, double q, std::string s) { return call<void>("differentArguments", i, d, f, q, s); } @@ -1116,6 +1121,10 @@ class ConcreteClass : public AbstractClass { void differentArguments(int i, double d, unsigned char f, double q, std::string s) { } + + std::shared_ptr<Derived> returnsSharedPtr() { + return std::shared_ptr<Derived>(); + } }; std::shared_ptr<AbstractClass> getAbstractClass() { @@ -1130,6 +1139,11 @@ std::string callOptionalMethod(AbstractClass& ac, std::string s) { return ac.optionalMethod(s); } +void callReturnsSharedPtrMethod(AbstractClass& ac) { + std::shared_ptr<Derived> sp = ac.returnsSharedPtr(); + // unused: sp +} + void callDifferentArguments(AbstractClass& ac, int i, double d, unsigned char f, double q, std::string s) { return ac.differentArguments(i, d, f, q, s); } @@ -1145,9 +1159,29 @@ EMSCRIPTEN_BINDINGS(interface_tests) { function("getAbstractClass", &getAbstractClass); function("callAbstractMethod", &callAbstractMethod); function("callOptionalMethod", &callOptionalMethod); + function("callReturnsSharedPtrMethod", &callReturnsSharedPtrMethod); function("callDifferentArguments", &callDifferentArguments); } +template<typename T, size_t sizeOfArray> +constexpr size_t getElementCount(T (&)[sizeOfArray]) { + return sizeOfArray; +} + +static void callWithMemoryView(val v) { + // static so the JS test can read the memory after callTakeMemoryView runs + static unsigned char data[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + v(memory_view(getElementCount(data), data)); + static float f[] = { 1.5f, 2.5f, 3.5f, 4.5f }; + v(typed_memory_view(getElementCount(f), f)); + static short s[] = { 1000, 100, 10, 1 }; + v(typed_memory_view(getElementCount(s), s)); +} + +EMSCRIPTEN_BINDINGS(memory_view_tests) { + function("callWithMemoryView", &callWithMemoryView); +} + class HasExternalConstructor { public: HasExternalConstructor(const std::string& str) @@ -1831,7 +1865,7 @@ int overloaded_function(int i, int j) { class MultipleCtors { public: - int value; + int value = 0; MultipleCtors(int i) { value = 1; @@ -1854,6 +1888,25 @@ public: } }; +class MultipleSmartCtors { +public: + int value = 0; + + MultipleSmartCtors(int i) { + value = 1; + assert(i == 10); + } + MultipleSmartCtors(int i, int j) { + value = 2; + assert(i == 20); + assert(j == 20); + } + + int WhichCtorCalled() const { + return value; + } +}; + class MultipleOverloads { public: MultipleOverloads() {} @@ -1960,7 +2013,15 @@ EMSCRIPTEN_BINDINGS(overloads) { .constructor<int>() .constructor<int, int>() .constructor<int, int, int>() - .function("WhichCtorCalled", &MultipleCtors::WhichCtorCalled); + .function("WhichCtorCalled", &MultipleCtors::WhichCtorCalled) + ; + + class_<MultipleSmartCtors>("MultipleSmartCtors") + .smart_ptr<std::shared_ptr<MultipleSmartCtors>>() + .constructor(&std::make_shared<MultipleSmartCtors, int>) + .constructor(&std::make_shared<MultipleSmartCtors, int, int>) + .function("WhichCtorCalled", &MultipleSmartCtors::WhichCtorCalled) + ; class_<MultipleOverloads>("MultipleOverloads") .constructor<>() |