-
-
Notifications
You must be signed in to change notification settings - Fork 22k
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
Add GDExtension variant_get_ptr_internal_getter
, facilitating VariantGetInternalPtr
-like behavior in GDExtension
#99201
Add GDExtension variant_get_ptr_internal_getter
, facilitating VariantGetInternalPtr
-like behavior in GDExtension
#99201
Conversation
Thanks! At a high-level, I think this makes sense. However, I'm not crazy about the name - there really isn't anything "validated" about it. With I'm also not sure this should be part of the I think I'd probably lean towards an approach that was only in the GDExtension code, and didn't add anything new to |
ade77a2
to
00ae00d
Compare
Thank you for the review! I did ponder if that wouldn't be a better approach, but now that I've heard it again in your words I agree the implementation would be better that way. I've changed up the PR, let me know if it looks better now.
Yeah, I think that would be best, given godot-cpp's ambitions to mimic godot's API. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Overall, this looks good to me - just a couple notes below.
00ae00d
to
745fc5d
Compare
Just noting here that the behavior differs to |
ec20639
to
b0600be
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! This looks good to me :-)
b0600be
to
3572ce0
Compare
3572ce0
to
f3f3d67
Compare
Sorry about the late change @dsnopek, for some reason I second guessed my decision to unify the function, to avoid branch misprediction in calls. Should be the same as before, just with function references to accelerate the actual calls by a few nanoseconds. I've probably been reading too much from Agner Fog, lol. Let me know if it still works for you. |
628c4e1
to
ef2fd34
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about the late change @dsnopek, for some reason I second guessed my decision to unify the function, to avoid branch misprediction in calls. Should be the same as before, just with function references to accelerate the actual calls by a few nanoseconds.
This is makes sense, and it matches some other Variant-related APIs.
However, I have a bunch of notes on the implementation :-)
ef2fd34
to
6a22b89
Compare
Just to double-check, the reason for the reversal (returning function pointers instead of the data pointers directly) is branch prediction? Was anything measured in that regard? This approach is only beneficial if:
Is this the intended use case? Because in the "I want to see what type the variant has, and get the internal pointer" scenario, the branching is still necessary, and this API adds indirection for no reason. If yes, could you document these assumptions in the GDExtension header? To make sure that people aren't using this for premature optimization when there's no benefit 🙂 |
Yep, i wanted to eliminate one source of potential indirection misprediction. Here's a breakdown of my call stack analysis: Old implementation // GDEXTENSION SIDE
switch (variant.get_type()){} // likely mispredicted static indirection to case PACKED_BYTE_ARRAY:
VariantInternal::get_byte_array(variant); // (likely) inlined call
// GODOT-CPP SIDE
get_internal_value_ptr(variant); // Predicable static indirection
// GODOT SIDE
switch (variant.get_type()){} // likely mispredicted static indirection
get_bool(variant); // (likely inlined call)
return &variant.bool; New implementation // GDEXTENSION SIDE
switch (variant.get_type()) // likely mispredicted indirection to case PACKED_BYTE_ARRAY:
VariantInternal::get_byte_array(variant); // (likely) inlined call
// GODOT-CPP SIDE
get_internal_value_functions[GDEXTENSION_BOOL_TYPE](variant); // Predicable virtual indirection
// GODOT SIDE
return &variant.bool ; The biggest cost factors in terms of performance are likely to be branch mispredictions, which may cost 12-25 clock cycles depending on architecture. The former implementation had 1, the new zero (leading to 2 total vs 1 total in most use-cases). The cost of a predictable virtual indirection is "a few clock cycles". Again, miniscule difference for most use cases. It may make a noticeable difference when acting on large quantities of variants though, which is something I actually may encounter - because I actually may call this function tens of thousands of times for object dtype tensor math.
Nope! Let me know if you want to see performance tests.
Type information has to be gleamed separately from
I agree this would be smart to do by GDExtension implementations. godot-cpp already does this for similar functions.
Will do! I was going to document it on godot-cpp side but it's good to do it here already too. |
6a22b89
to
26cdd9d
Compare
Thanks a lot for this detailed elaboration! ❤️ Some questions though, about the new implementation: // GDEXTENSION SIDE
switch (variant.get_type()) // likely mispredicted indirection to case PACKED_BYTE_ARRAY:
VariantInternal::get_byte_array(variant); // (likely) inlined call
// GODOT-CPP SIDE
get_internal_value_functions[GDEXTENSION_BOOL_TYPE](variant); // Predicable virtual indirection
// GODOT SIDE
return &variant.bool ; The "GDEXTENSION SIDE" part only needs to happen once for each type, and the function pointers are then cached in the However, in typical use cases, the "GODOT-CPP SIDE" part needs an additional You do benefit if you have something like Btw, I don't think array-of-function-pointers is generally faster than |
Sorry, i was being unclear. The GDEXTENSION side refers to some function a gdextension creates that operates on a Variant of unknown type (like this switch statement). It doesn't refer to creating the function table. You are correct that the function table is only created once, and that can be ignored in the performance analysis.
Yep. You either need a
Oh! That's right, i totally forgot about this use-case. Yep, this one should also (slightly) benefit from the new implementation, as only one switch will be needed (instead of one for each variant type).
Yep, I agree.
That's true. I don't know in which ways a switch may be optimized, so I can't guarantee that the new solution is faster (especially, faster with all compilers and arches). I just thought it more likely to be. |
c4c5008
to
4ab0a14
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! The reworked version (which is returning function pointers) is looking good to me now :-)
REGISTER_INTERFACE_FUNC(get_variant_to_type_constructor); | ||
REGISTER_INTERFACE_FUNC(variant_get_ptr_internal); | ||
REGISTER_INTERFACE_FUNC(variant_get_ptr_operator_evaluator); | ||
REGISTER_INTERFACE_FUNC(variant_get_ptr_builtin_method); | ||
REGISTER_INTERFACE_FUNC(variant_get_ptr_constructor); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Bikeshedding) the existing variant_get_ptr_*
names indicate that a function is returned, with names such as "evaluator", "method" and "constructor":
variant_get_ptr_operator_evaluator
variant_get_ptr_builtin_method
variant_get_ptr_constructor
The name "internal" doesn't seem to fit that scheme. Maybe variant_get_ptr_internal_accessor
or something?
Just something to indicate it doesn't return the internal pointer, but a function that retrieves it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be OK with variant_get_ptr_internal_accessor
or variant_get_ptr_internal_getter
or something like that.
(I'd also be OK with sticking with what is already in this PR, since all the other variant_get_ptr_*
functions return function pointers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've renamed the function to variant_get_ptr_internal_getter
. The use of 'getter' seems to already be somewhat established in the interface.
* Returns a function to retrieve a pointer to a variant's internal value. | ||
* Each function assumes the variant's type has already been determined and matches the function. | ||
* If any function is called with a variant of mismatching type, the behavior is undefined and may result in a segfault. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great paragraph, but as mentioned in my comments, I would maybe add something along:
This function is particularly useful in situations where you know the variant type upfront, and can thus directly fetch the pointer of the correct type. It can make sense to cache the returned function pointers.
I don't have a great wording, but it would convey why this function exists in spite of other similar APIs. People won't have access to the information discussed in this PR anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright. I've added some documentation to hopefully address your concerns, let me know if that works for you!
b92f612
to
012a1f7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the updates!
I guess the new @note
annotation shouldn't lead to any problems in parsers or so?
I hope not. It seems to be supported by CLion and doxygen, for what it's worth. I used it because it seemed like the most appropriate option from the ctrl-space context menu. |
variant_get_internal_ptr
, facilitating VariantGetInternalPtr
-like behavior in GDExtension
variant_get_internal_ptr
, facilitating VariantGetInternalPtr
-like behavior in GDExtensionvariant_get_ptr_internal_getter
, facilitating VariantGetInternalPtr
-like behavior in GDExtension
561069b
to
42ad800
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One style nitpick; otherwise looks good!
…unction returns functions to retrieve a pointer to a Variant's internal value. This enables GDExtensions to implement functionality similar to VariantGetInternalPtr, to access Variant internal values directly.
42ad800
to
ffd4de6
Compare
Thanks! |
The function returns a pointer to the value, which can be helpful to avoid copies of the content being made from normal conversions.
The godotengine equivalents of this function are
VariantInternalAccessor<>
as well asVariantGetInternalPtr<>
. With this PR, these definitions could be re-created in godot-cpp and other GDExtension projects - with the notable difference that the Object* is validated before being returned. The various use-cases of these two structs can be seen throughout the godot codebase itself.One example of a use-case for this function is mostly explained in GOP 10830: It allows for in-place modification of (wrapped ref-counted) COW variant arguments, i.e. arrays.
Another use-case example, this allows reading from the arrays without a copy being made from the conversion.
Tagging @dsnopek because we had talked about this kind of functionality before in #98392.
Supersedes #98392.
Related to #98373 (and GOP 10830) by addressing a similar, but not equivalent, concern.
Note that this is my first PR to propose an addition to the GDExtension interface, let me know if I missed anything :)