Integers vs Numbers
Even if javascript makes no difference, V8 can distinguish integers from double for performance reason (looping with a double is a huge loss because the looping variable can't be a cpu register). I could create numbers that are integers in C++ but now, with N-API, I see no way to create integers, but just doubles. Isn't this a performance problem? Is there any way to hint the API to use integers instead?
You have the following
-
napi_get_value_double -
napi_get_value_int32 -
napi_get_value_uint32 -
napi_get_value_int64 -
napi_create_double -
napi_create_int32 -
napi_create_uint32 -
napi_create_int64
for converting between JS numbers and C numbers. Do any of these look like what you want?
Thank you @addaleax
In the napi.h header I can't see the wrapper for the integer.
The class Number : public Value { ... } holds the value in a double so I expected another class for Integer.
Did I miss it?
@raffaeler That’s a thin wrapper around the C API – you can combine them.
The Number class there has Int32Value(), Uint32Value(), Int64Value() methods, so conversion in one direction should be easy enough. Currently using Number::New() would convert an integer to a double first, yes. The performance difference should really be negligible, but if you really want, you can use the napi_create_... methods manually, use the Value(env, raw_value) constructor to turn that into a Napi::Value, and then convert that to Napi::Number using value.As<Number>().
You could also submit a PR against https://github.com/nodejs/node-addon-api/blob/master/napi.h, if you want to add methods for creating values from integer types directly.
@addaleax my concerns are not about a nice constructor, but holding the value in a double instead of integer. I remember with V8 could tell me whether a number was really an integer and not a double (afaik no conversion occurred). So, if javascript creates an integer V8 is able to know it is not a double, and the underlying C++ API had this knowledge. With the C++ wrapper I now have just a conversion, which is not the same thing.
Basically you are telling me that I should use the raw api because the C++ wrapper is not able to distinguish the two types. Correct?
@raffaeler This might be wrong, but I’d personally estimate that the overhead of the check that was available in the V8 API would be greater than the overhead of one conversion from double to int, at least on modern CPUs. I think this is especially true if you’re going through N-API.
So, unless benchmarking tells you that this is a significant issue, I’d stick with the double versions.
If you do see that this is a real performance issue, experience from Node.js has shown that typed arrays are a vastly faster way of transferring numeric types between JS and C++ than manually managing JS values, so I’d recommend doing that.
I believe we are talking about different things. If you use a for loop using a double or an integer for the variable, you will see a huge difference in terms of performance. I believe V8 introduced the integers (even if they are hidden in JS) to address this problem. In other words, V8 is not going to use internally a double, but understands from the code whether an integer or a number should be used to get the best performance. Of course, once you write C++ code you see those differences, and it's up to the addon developer taking care to use the correct data type.
Again, I am not discussing the cast, but the absence of a differen data type that is able to provide the real nature of the type. The problem exists in two flavors:
- if the integer is created in javascript and V8 understand that is an integer, from C++ I need to know that is an integer.
- If the integer is created in C++, I want to inform V8 that it is really an integer so that it can get this hint to make JS code more performant.
Please tell me if there is something that I didn't explain well, and thanks for your help.
I believe we are talking about different things.
That might well be true. :)
If you use a for loop using a double or an integer for the variable, you will see a huge difference in terms of performance.
I agree.
I believe V8 introduced the integers (even if they are hidden in JS) to address this problem.
I don’t think this is true – it’s easy enough to convert between floating-point numbers and integers if you want to use them as loop variables? (That is, if you’re talking about loops in your C++ addon. If you’re talking about loops in JS, see below. :))
In other words, V8 is not going to use internally a double, but understands from the code whether an integer or a number should be used to get the best performance.
V8 does distinguish between small 31-bit integers and doubles, and I’d expect most operations using the former to be faster, yes.
However, if you are worried that using napi_create_double() will create a variable that is internally represented as a double even if it could be an integer, then be assured that that is not an issue:
https://github.com/v8/v8/blob/a8a45e41213d51590a1b9b0a629afece1751555c/src/heap/factory.cc#L2322-L2324
(This is what napi_create_number() ultimately ends up calling.)
Of course, once you write C++ code you see those differences, and it's up to the addon developer taking care to use the correct data type.
Yes.
Again, I am not discussing the cast, but the absence of a differen data type that is able to provide the real nature of the type.
I’m not sure I understand what you’re saying here?
The problem exists in two flavors:
- if the integer is created in javascript and V8 understand that is an integer, from C++ I need to know that is an integer.
I think it’s safe to check this in C++ by casting the double to your desired integer type, casting back to double, and seeing if the results are the same?
- If the integer is created in C++, I want to inform V8 that it is really an integer so that it can get this hint to make JS code more performant.
See above – V8 is clever enough to figure this out. :)
I meant js loops using variables coming from C++
The point here is that the old V8::Value knows if the value is an integer or not. In the N-API I don't see the way to preserve this info.
From the v8 sources, you can see that converting a double to an integer is not really just a cast, it can be a costly operation: https://github.com/v8/v8/blob/b8626ca445554b8376b5a01f651b70cb8c01b7dd/src/conversions-inl.h#L75 or https://github.com/v8/v8/blob/b8626ca445554b8376b5a01f651b70cb8c01b7dd/src/conversions-inl.h#L29 The ability to make just a cast or a slow conversion depends on the metadata information telling whether the number is really an int or not.
V8 knows: https://github.com/nodejs/node/blob/master/deps/v8/include/v8.h#L2200 N-API apparently not: https://github.com/nodejs/node/blob/11387e1454d8b8311dfdb1bac6a7ec1a1494946d/doc/api/n-api.md
napi_valuetype
typedef enum {
// ES6 types (corresponds to typeof)
napi_undefined,
napi_null,
napi_boolean,
napi_number,
napi_string,
napi_symbol,
napi_object,
napi_function,
napi_external,
napi_bigint,
} napi_valuetype;
I meant js loops using variables coming from C++
Okay, then you’re good here, because as I mentioned, V8 will implicitly create integers when using napi_create_number() with a double-that-represents-an-integer-value argument.
Or, to be explicit: napi_create_number(env, 42, &result); will create a JS value that is internally represented as an integer. It will not create a more expensive double on V8’s heap.
(That also means that you might be converting from an integer type to a floating-point type and back to an integer type during creation. It’s not perfect, but it’s also not super costly either when performed for individual numbers.)
Okay, then you’re good here, because as I mentioned, V8 will implicitly create integers when using
napi_create_number()with a double-that-represents-an-integer-value argument.
That is in line to what I saw in the past, great. But how N-API "marks" the number as integer, given that the enumeration I posted before does not include integer? Furthermore, I need to know if V8 is storing the number as integer because my addon will marshal that value to an external system that knows the difference between integers and doubles.
Thanks
But how N-API "marks" the number as integer, given that the enumeration I posted before does not include integer?
N-API doesn’t store the value itself in any way – it leaves that to V8, and V8 detects that the value as an integer during creation of the JS value.
Furthermore, I need to know if V8 is storing the number as integer because my addon will marshal that value to an external system that knows the difference between integers and doubles.
I think you’ll have to check this yourself in C++, once you have the number as a double.
I don't mean storing the value, but the type of the value.
V8 has it (as well as the 'old' node API), N-API apparently not (see napi_valuetype I posted before).
@raffaeler N-API doesn’t store the type of the value itself, either, it uses V8 APIs to find it out when requested. But yes, it does not provide information about whether a value is an integer (because JS itself does not provide that information, either – as part of the language, that is, just as Number.isInteger()).
I will make some test, but if I can't distinguish the type, I will sadly have to drop the N-API form my addon.
@raffaeler Of course you can distinguish between integers and non-integers, you just have to do it in your own code?
I will make some test to understand the issues. Believe me, I am at the second line of my addon, and I already lost 5 days because of missing (or non mappable) features. It's so frustrating. Thanks for your help