Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Look for zero-copy optimizations using Nan::NewBuffer #531

Open
springmeyer opened this issue Sep 29, 2015 · 6 comments
Open

Look for zero-copy optimizations using Nan::NewBuffer #531

springmeyer opened this issue Sep 29, 2015 · 6 comments

Comments

@springmeyer
Copy link
Member

springmeyer commented Sep 29, 2015

Using Nan::NewBuffer allows the existing memory to be referenced rather than copied when creating a node::Buffer. Currently we are not using Nan::NewBuffer but there a likely a number of spots where we could safely benefit from this optimization.

@springmeyer springmeyer mentioned this issue Sep 29, 2015
5 tasks
@springmeyer
Copy link
Member Author

refs mapbox/node-cpp-skel#69

@talaj
Copy link
Member

talaj commented Sep 11, 2017

A problem in many cases is that node::Buffer is created from std::string which cannot handover its internal buffer ownership to non-std::string object.

@springmeyer
Copy link
Member Author

@talaj - one way I've found to handle this situation is to use std::make_unique<std::string>(); and then write data to the std::string inside the unique_ptr. When it comes to sending the data back to the threadpool I've done this like (where string_ptr is the unique_ptr on a baton passed to the threadpool):

baton->string_ptr.release();
v8::Local<v8::Value> argv[2] = { Nan::Null(),
                                 Nan::NewBuffer((char*)baton->string_ptr->data(),
                                    baton->string_ptr->size(),
                                    [](char *, void * hint) {
                                        delete reinterpret_cast<std::string*>(hint);
                                    },
                                    baton->string_ptr.get()
                                 ).ToLocalChecked()
                               };
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), Nan::New(baton->cb), 2, argv);

What do you think? Is that viable for node-mapnik / usecases you have in mind?

One downside of this approach is that it looks like the memory is leaked to leak checkers like https://clang.llvm.org/docs/LeakSanitizer.html

@talaj
Copy link
Member

talaj commented Sep 11, 2017

I think this is great solution. It can be a simple wrapper function around Nan::NewBuffer().

@springmeyer
Copy link
Member Author

I think this is great solution. It can be a simple wrapper function around Nan::NewBuffer().

Good idea.

@springmeyer
Copy link
Member Author

@talaj if you end of applying this in your work in node-mapnik, we should be fine without LeakSantizer issues since the LeakSantizer is currently disabled (refs #747).

But ideally we can enable it in the future. Anyway, wanted to follow through on what I see when applying this approach and testing with LeakSantizer:

=================================================================
==9980==ERROR: LeakSanitizer: detected memory leaks
Indirect leak of 432 byte(s) in 2 object(s) allocated from:
    #0 0x7f9e86cca00b in operator new(unsigned long) /home/travis/build/mapbox/mason/mason_packages/.build/llvm-4.0.1/projects/compiler-rt/lib/asan/asan_new_delete.cc:82:35
    #1 0x7f9e8657a628 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xc4628)
    #2 0x7f9e8657b40a in std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xc540a)
    #3 0x7f9e8657b4b3 in std::string::reserve(unsigned long) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xc54b3)
    #4 0x7f9e8657b732 in std::string::append(char const*, unsigned long) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xc5732)
    #5 0x7f9e7cf59054 in protozero::pbf_writer::add_bytes(unsigned int, char const*, unsigned long) /home/travis/build/mapbox/vt-shaver-cpp/mason_packages/.link/include/protozero/pbf_writer.hpp:481:17
    #6 0x7f9e7cf43ae9 in AsyncShave(uv_work_s*) /home/travis/build/mapbox/vt-shaver-cpp/build/../src/shave.cpp:541:40
    #7 0xfa5f40 in worker /home/iojs/build/ws/out/../deps/uv/src/threadpool.c:95
    #8 0xfb5078 in uv__thread_start /home/iojs/build/ws/out/../deps/uv/src/unix/thread.c:52
    #9 0x7f9e85d8de99 in start_thread (/lib/x86_64-linux-gnu/libpthread.so.0+0x7e99)
    #10 0x7f9e85abb2ec in clone (/lib/x86_64-linux-gnu/libc.so.6+0xf62ec)

I figure LeakSanitizer is correct that the memory is not being cleaned up right away, but that is by design: by transferring ownership of the string to the node::Buffer the delete of the std::string gets deferred to when v8 decides to garbage collect the node::Buffer. But I've tried forcing gc with node --expose-gc and calling gc() in the code after testing and the LeakSanitizer still complains. So I'm not quite sure how best to fix this indirect leak/suppress it yet. Curious if you have ideas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants