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

Documentation: Should have Addons documentation easily findable, should explain how to use a Typed Array in C++ that is given as a parameter #883

Closed
metabench opened this issue Feb 18, 2015 · 32 comments
Labels
doc Issues and PRs related to the documentations.

Comments

@metabench
Copy link

I also posted an issue at nodejs/node-v0.x-archive#9247

It would be useful if writing of addons was shown at a much higher level within the documentation. I don't even know where it exists within the io.js documentation (or node either). I've used Google to find information on writing addons.

Specifically, I'd like to make use of a Float32Array that has been given to a C++ function in an addon. Advice on this would be appreciated, but it's worth having in the docs too.

@bnoordhuis
Copy link
Member

-0.5 from me for the reason that doc/api/addons.markdown is not a V8 primer.

Interacting with typed arrays is not in any way specific to io.js. Adding a section on typed arrays would be incongruous because we don't explain how objects or object templates work either.

@metabench
Copy link
Author

I'm not asking for an explanation of how V8 works (although links to V8 documentation helps with that). More like a simple how-to, with some example code that shows use of a typed array within a C++ plugin.

The addon documentation does have code that features objects and object templates, but I'm looking at this from the perspective of how to get something specific done using io.js rather than explaining V8.

It would help myself and others get started with higher performance code that's usable within the io.js framework, enabling better plugins to be written.

@vkurchatkin
Copy link
Contributor

At least 2 things we can do to make documentation better:

  1. mention that using Nan is a good idea
  2. remove link to v8 documentation on @isaacs site, because it is 3 year old

@Qard
Copy link
Member

Qard commented Feb 18, 2015

Maybe swap the v8 docs link to point to v8-dox from @thlorenz? https://thlorenz.github.io/v8-dox/build/v8-3.25.30/html/

@mscdex
Copy link
Contributor

mscdex commented Feb 18, 2015

@Qard I've often used this link which seems to be updated fairly often (1/7/2015 as of this writing). It doesn't have the v8 version number anywhere that I could find though...

@thlorenz
Copy link
Contributor

@Qard I can release a version that only shows public API.

The one linked is for understanding v8 internals and pretty much includes everything. So quite daunting when getting started.

We could then run an update script similar to this one regularly every time a new v8 version is used in io.js.

@metabench
Copy link
Author

@bnoordhuis Do you know how a Typed Array would be accessed from a V8 plugin? For me it's still mysterious after having looked further at the V8 documentation. The following code does not work - can you please advise on how it should be done?

Float32Array arr = args[0]->Float32ArrayValue();

I understand your concern about not having the io.js documentation cover things that are not strictly part of the io.js project. It occurred to me that a more interactive documentation page could help solve it. There would be a code sample on one side of the page, with mouse-overs that explain parts of the code in more detail, and present references to the V8 documentation.

@metabench
Copy link
Author

I'm interested in making some tutorial pages that explain in more detail about writing addons with C++, I'm a beginner at C++ but good at front-end development. Would anyone here be interested in helping me write such a tutorial?

It will be different to the existing tutorials in that the screen will have 2 parts:

Code on the left: User can click or mouse over certain parts of the code
Information on the right: Further explanations of the code, and references are provided on the right. This may include information which strictly speaking is not part of the iojs project, yet is still useful in using it to make addons.

At first glance the tutorials would be quick to read through for advanced users, while beginners to the V8 and iojs addon system will have access to a lot more information.

Also, what would people think about hosting such a tutorial on iojs, if it were to be done? If anyone wants to help make such a tutorial, it could be considered by more iojs stakeholders for inclusion on the iojs website once it's made.

If I were to get help with the C++ (ie providing content for the tutorial) I would expect a fairly short turnaround in making the page(s) - maybe 3 days or so.

@bnoordhuis
Copy link
Member

@metabench

if (args[0]->IsFloat32Array()) {
  Local<Float32Array> array = args[0].As<Float32Array>();
  for (size_t i = 0, n = array->Length(); i < n; i += 1)
    array->Set(i, Number::New(array->GetIsolate(), 1.0f / i));
  // or:
  Local<ArrayBuffer> buffer = array->Buffer();
  ArrayBuffer::Contents contents = buffer->Externalize();
  float* data = static_cast<float*>(contents.Data());
  for (size_t i = 0, n = array->Length(); i < n; i += 1)
    data[i] = 1.0f / i;
  // ...
  delete[] reinterpret_cast<char*>(data);
}

Note that when you externalize the ArrayBuffer, you're responsible for freeing it. Sadly, the externalization API is currently rather awkward in that it forces you to know about the implementation detail of how the memory is allocated (with new char[...]).

@metabench
Copy link
Author

@bnoordhuis Thanks, that's very interesting. I'll see what I can do with it. There is quite a lot that I don't understand at first glance. Of course that's got to do with the limitations of my knowledge rather than the quality of what you provided.

What do you think about helping me to write a tutorial that explains this stuff?

@vkurchatkin
Copy link
Contributor

@metabench you can use smalloc with Float type instead. This way you don't have to manage memory manually

@metabench
Copy link
Author

@bnoordhuis I managed to read the values of the Float32Array with the following code:

void ReadTypedArray(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);
    cout << "args.length " << (args.Length()) << endl;

    if (args.Length() == 1) {
        Local<Float32Array> array = args[0].As<Float32Array>();
        Local<ArrayBuffer> buffer = array->Buffer();
        ArrayBuffer::Contents contents = buffer->Externalize();
        float* data = static_cast<float*>(contents.Data());
            for (size_t i = 0, n = array->Length(); i < n; i += 1) {
                //data[i] = 1.0f / i;
                cout << "arr[i] " << data[i] << endl;
            }
        delete[] static_cast<float*>(data);
    }
}
delete[] static_cast<char*>(data);

would not compile, so I changed it to float*, is that correct? Also, are the two lines at the top of the function (isolate and handle scope) necessary?

@metabench
Copy link
Author

@vkurchatkin would you provide a code sample please?

@vkurchatkin
Copy link
Contributor

Create you array in js like this:

var smalloc = require('smalloc');
var data = smalloc.alloc(100, smalloc.Types.Float);

Then you can use data as a container. It doesn't have length property so you need to keep track of it.

In C++:

Local<Object> obj = args[0].As<Object>();

if (obj->HasIndexedPropertiesInExternalArrayData()) {
  int length = obj->GetIndexedPropertiesExternalArrayDataLength();
  float* data = static_cast<float*>(obj->GetIndexedPropertiesExternalArrayData())
}

P.S. this also should work if data (in JS) is a typed array, but it is undocumented

@metabench
Copy link
Author

I now have a version that can modify a Float32Array. It's required removing a line from the @bnoordhuis example. Without that line removed,
console.log('ta_test', ta_test);
would not show anything (even an error). Now I have kept that line, it logs the modified Float32Array.I now use this code:

void ReadTypedArray(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = Isolate::GetCurrent();
    HandleScope scope(isolate);
    cout << "args.length " << (args.Length()) << endl;
    if (args.Length() == 1) {
        Local<Float32Array> array = args[0].As<Float32Array>();
        Local<ArrayBuffer> buffer = array->Buffer();
        ArrayBuffer::Contents contents = buffer->Externalize();
        float* data = static_cast<float*>(contents.Data());
            for (size_t i = 0, n = array->Length(); i < n; i += 1) {
                data[i] = data[i] + 1;
            }
        //delete[] static_cast<float*>(data);
    }
}

If I still plan on using the Float32Array in JavaScript, is there still anything I need to do to clear up anything done within the addon?

Also, I'm interested in the most efficient ways of doing this, so if anyone has any input on what is the fastest code to use then please let me know.

@bnoordhuis
Copy link
Member

delete[] static_cast<char*>(data); would not compile, so I changed it to float*, is that correct?

Sorry, that should have been a reinterpret_cast. I'll update the example.

delete[] static_cast<float*>(data) is decidedly unsafe, by the way. :-)

@metabench
Copy link
Author

@vkurchatkin, smalloc definitely looks like a promising way of going about things. It would avoid having to use V8 typed arrays in the C++ part at all. It's rare that someone avoids answering a question yet still answers it very well! My goal is really to get high performance access to data structures within JavaScript and C++, I had assumed typed arrays would be the way to go about it because that's what I had been using in JavaScript.

I have a question about it though - you say that it does not keep track of the length so that needs to be done separately. However, in the C++ code I see you use GetIndexedPropertiesExternalArrayDataLength in C++ to get the length. The length would be computable from the size of the allocated structure divided by the size of each object. I presume it would still be keeping track of it when it's running in JavaScript, even if behind the scenes. Maybe it's that the JavaScript is deliberately limited for the best performance. Can you tell me any more about this please?

@metabench
Copy link
Author

@bnoordhuis using reinterpret_cast still causes the TypedArray, which was given as a parameter within JavaScript, to be deleted (or harmed somehow, deleted may not be exactly what's going on). I use the following test code:

var ta_test = new Float32Array(4);
ta_test[0] = 1;
ta_test[1] = 2;
ta_test[2] = 3;
ta_test[3] = 4;
addon.read_typed_array(ta_test)

console.log('ta_test', ta_test);

Is delete[] reinterpret_cast<char*>(data); really necessary? If it is, what problems would there be if it's not used here? Do you have a recommendation for how the array should be kept intact if it's not by deleting that line?

@vkurchatkin
Copy link
Contributor

However, in the C++ code I see you use GetIndexedPropertiesExternalArrayDataLength in C++ to get the length. The length would be computable from the size of the allocated structure divided by the size of each object. I presume it would still be keeping track of it when it's running in JavaScript, even if behind the scenes. Maybe it's that the JavaScript is deliberately limited for the best performance. Can you tell me any more about this please?

What I mean is the length is not exposed to JS. Internally V8 keeps track of it. If you need to know length in JS, you can simply attach length property to the object.

@vkurchatkin
Copy link
Contributor

Is delete[] reinterpret_cast<char*>(data); really necessary?

It's necessary to have this SOMEWHERE, otherwise memory would leak.

@metabench
Copy link
Author

It's necessary to have this SOMEWHERE, otherwise memory would leak.

That does sound difficult, in that it would need to know when the JavaScript part is done with it. I would prefer all memory management to be done in C++ and not have to use JavaScript to tell C++ when it's finished. How could that best be written in C++ to take place automatically?

@metabench
Copy link
Author

you can simply attach length property to the object

I suspect keeping track of the length as a separate property would be at least as efficient. I'd need to see some benchmarks that attaching the length property to the object would be the right way of doing it. It seems likely to be slower as it would be rearranging an object's structure.

@rvagg
Copy link
Member

rvagg commented Feb 19, 2015

@thlorenz and I were just discussing today the need to update https://v8docs.nodesource.com/ to include V8 docs for the io.js version(s)

@trevnorris
Copy link
Contributor

@metabench

My goal is really to get high performance access to data structures within JavaScript and C++, I had assumed typed arrays would be the way to go about it because that's what I had been using in JavaScript.

That's one common usage of 'smalloc'. By attaching a uint32 external data array to an object you can use that memory to track status by flags. Retrieving that information in C++ is fast, and faster still if you pull it out and store a pointer to it for future reference (though you have to be aware of when the object is no longer in scope and could be cleaned up by gc).

I suspect keeping track of the length as a separate property would be at least as efficient. I'd need to see some benchmarks that attaching the length property to the object would be the right way of doing it.

I've benchmarked this ad nauseam. The constructor incurs a small (meaning, measurable in nanoseconds) overhead, but it doesn't have any effect afterwards. Just make sure you attach the object properties before attaching the memory. Like so:

var smalloc = require('smalloc');
var foo = {};

foo.length = 16;
smalloc.alloc(16, foo, smalloc.Types.Uint32);

@metabench
Copy link
Author

@trevnorris It's very good to know that adding the length property does not get in the way of V8 optimizations. With that code sample you just provided, is foo then usable as an array of Uint32?

I've been having trouble getting this to work with the much appreciated example from @bnoordhuis and my own research. I've realised that execution stops when it does delete[] reinterpret_cast<char*>(data);, and if I did not use it I would get FATAL ERROR: v8::ArrayBuffer::Externalize ArrayBuffer already externalized, which I found very little information about using Google.

@metabench
Copy link
Author

@trevnorris, also do you know if smalloc would work with NAN?

@trevnorris
Copy link
Contributor

@metabench Don't cleanup the memory yourself. GC will take care of it. And yes, it then becomes a usable uint32 external array.

@metabench
Copy link
Author

@trevnorris I'm having success reading from input smalloc arrays using the code @vkurchatkin provided. I've also found using NAN convenient with casting to a type that's simple in C++.

I've succeeded in returning normal JavaScript arrays using NAN.

I'm now trying to return a smalloc object. I've looked at the smalloc source code and have noticed changes between node and iojs... do you think that's going to make a difference for making a smalloc object?

@trevnorris
Copy link
Contributor

@metabench Don't believe so.

@metabench
Copy link
Author

@trevnorris how would I make the smalloc in iojs addon C++? I can't yet understand it from the source code unfortunately.

@thlorenz
Copy link
Contributor

So the v8 doxygen docs have been updated.

Here is the io.js 1.2 one documenting v8 4.1.0.14.
If you want we could link to those from the addons documentation.
We'll make sure to update with each io.js release (our build script was updated to make that easier).

@trevnorris
Copy link
Contributor

@metabench Until #905 lands it can only be done from JS. When it does land look at test/addons/smalloc-alloc/binding.cc for an example of how to use it.

@brendanashworth brendanashworth added the doc Issues and PRs related to the documentations. label Feb 22, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
doc Issues and PRs related to the documentations.
Projects
None yet
Development

No branches or pull requests

10 participants