Page MenuHome

Geometry Nodes: Refactor virtual array system.
ClosedPublic

Authored by Jacques Lucke (JacquesLucke) on Oct 25 2021, 12:16 AM.

Details

Summary

Goals of this refactor:

  • Simplify creating virtual arrays.
  • Simplify passing virtual arrays around.
  • Simplify converting between typed and generic virtual arrays.
  • Reduce memory allocations.

As a quick reminder, a virtual array is a data structure that behaves like an array (i.e. it can be accessed using an index), however it may not actually be stored as array internally. The two most important implementations of virtual arrays are those that correspond to an actual plain array and those that have the same value for every index. However, many more implementations exist for various reasons (interfacing with legacy attributes, unified iterator over all points in multiple splines, ...).

With this refactor the core types (VArray, GVArray, VMutableArray and GVMutableArray) can now be used like "normal values". They typically live on the stack. Before, they were usually inside a std::unique_ptr. This makes passing them around much easier. Creation of new virtual arrays is also much simpler now. Memory allocations are reduced by making use of small object optimization inside the core types.

Diff Detail

Repository
rB Blender
Branch
virtual-array-value-type (branched from master)
Build Status
Buildable 18175
Build 18175: arc lint + arc unit

Event Timeline

Jacques Lucke (JacquesLucke) requested review of this revision.Oct 25 2021, 12:16 AM
Jacques Lucke (JacquesLucke) created this revision.
Jacques Lucke (JacquesLucke) retitled this revision from Geometry Nodes: Refactor virtual array system. to Geometry Nodes: Refactor virtual array system (WIP)..Oct 25 2021, 12:28 AM
Jacques Lucke (JacquesLucke) edited the summary of this revision. (Show Details)
Jacques Lucke (JacquesLucke) edited the summary of this revision. (Show Details)
  • Named constructors are a huge improvement!
  • Separating virtual arrays and their implementation to allow storing on the stack seems very nice. No idea if it will actually result in better performance though.
  • I don't feel completely qualified to review BLI_any.hh, but it mostly made sense when reading over it. What I'm wondering about is how it compares to a version of CPPType that can hold a single value of its type.
  • I'd like to consider removing the _For_ from the implementation type names. It doesn't feel necessary not that they're used more, and sounds redundant with "Impl". So I'd suggest VArrayImpl_For_Span -> VArrayImplSpan. Best to consider that separately though, to keep unnecessary renaming out of this patch.
source/blender/blenkernel/intern/geometry_component_curve.cc
232

One thing I don't understand is why VArrayImpl<bool> & is used here instead of const VArray<bool> &.
It's similar in other places. I think it's to reduce the overhead of going through VArray first for every element?

If so, that information could become a comment somewhere.

source/blender/blenkernel/intern/spline_poly.cc
25

I would prefer to keep using blender;:fn::GVArray; here, same with the other spline files, unless there is a good reason not to.

118–122

Unnecessary change to comment formatting.

source/blender/blenlib/BLI_virtual_array.hh
158

This ought to have a comment explaining its purpose, it's not as clear like the other methods otherwise.

329

Add a comment explaining why this exists. (My guess is to enable compiler optimizations?)

391–483

Could you move these to the bottom of the file in a separate commit? It would be much easier to see the changes that way.

715

Add a comment explaining this assert and/or a comment explaining how this function is meant to be used.

789

What about adding more of the functions like materialize and others directly to VArray? Do you think that's reasonable? That way the Impl types wouldn't have to be used as much in external code.

source/blender/functions/FN_generic_virtual_array.hh
127–141

The use of Any here and the method for storing arbitrary virtual array types really deserves some explanation. It's very abstract, and takes a long time for someone not very familiar with this sort of C++ code to understand without that.

source/blender/functions/tests/FN_field_test.cc
37–39

This can be a separate change, but I think we ought to rethink the use of ResourceScope here now. It probably makes more sense to allow the function to optionally request a buffer from the evaluator, which the evaluator would know how to reuse later on in the process. But we can think more about that later I think.

Hans Goudey (HooglyBoogly) requested changes to this revision.Oct 28 2021, 6:11 PM
Hans Goudey (HooglyBoogly) added inline comments.
source/blender/functions/intern/generic_virtual_array.cc
101

As most readers of this comment will be wondering, the safe side of what? The reader won't have nearly the amount of context you had when you wrote the comment ;P

This revision now requires changes to proceed.Oct 28 2021, 6:11 PM

No idea if it will actually result in better performance though.

Sometimes we will have fewer allocations, however I'm not entirely sure if element access will be exactly as fast as before. Still have to look at the assembly for that. Even if there is e.g. one assembly instruction more per access, that's still fine with me because the main cost is still the virtual method call which we should optimize away where it matters anyway.

What I'm wondering about is how it compares to a version of CPPType that can hold a single value of its type.

There is certainly some similarity. One difference is that Any can be used with every type without creating a CPPType for the type first explicitly (which is necessary, because sometimes we want to add more info to a CPPType, e.g. with FieldCPPType). In that way it is very similar to std::any. I do see that there may be some overlap though. Not entirely sure if that's good or bad yet.

What I'm wondering about is how it compares to a version of CPPType that can hold a single value of its type.

Sounds good to me.

source/blender/blenkernel/intern/geometry_component_curve.cc
232

It may reduce overhead (still have to look at the optimized assembly).
The main reason however is that I didn't update it yet. VArray was changed to VArrayImpl here using a global find-replace operation.

source/blender/blenlib/BLI_virtual_array.hh
329

Will add a comment (didn't manage to finish everything last weekend).
The reason is actually that I don't want people to subclass this, because then the has_ownership_impl below may become wrong (e.g. if the subclass owns some data, which is common).

391–483

Could move them down in a separate commit before (the need to be at the bottom, because they use on VArray). Generally, I'm not sure if it is feasible to separate these kinds of changes from the refactor, would make it much harder in some cases. If you think there is a big benefit, then I'll do it though.

789

Thought about that, but currently I don't think we should do it. Mainly because it would just add more duplicated code.
Although, at a second thought, maybe I can remove from from VArray, so that VArray only contains the virtual methods. Will think about it.

source/blender/functions/tests/FN_field_test.cc
37–39

I agree. Can be done afterwards.

source/blender/blenlib/BLI_virtual_array.hh
391–483

That's fine if it's a pain to do. I don't think the benefit is that big.

789

Your second thought sounds like a good idea!

  • Various improvements, cleanups and comments.

Looks great, just a couple of minor things/questions.

I also see that noexcepts never used. Could lead to better codegen, but it's extremely minor and there's probably a reason why ya'll don't use it.

source/blender/blenlib/BLI_any.hh
133

Any() = default` MSVC triggers a warning for unitialized buffer_ for this default constructor (even though it doesn't matter for the usecase of any since it's just a buffer), but MSVC doesn't warn if you in-class default initialize: AlignedBuffer ... buffer_{}

https://docs.microsoft.com/en-us/cpp/code-quality/c26495?f1url=%3FappId%3DDev16IDEF1%26l%3DEN-US%26k%3Dk(C26495)%26rd%3Dtrue&view=msvc-160

290

Get a reference ... ?

300

Get a reference...?

source/blender/blenlib/tests/BLI_any_test.cc
14

Up to you of course, but I typically find conversions to bool like this harder to read especially if you're not immediately aware of the object's type when you're reading the code. Something like has_value seems like a more illustrative way for the same intent.

if (a) { ... }

vs

if (a.has_value()) { ... }
19

does not compile for me on MSVC 2019.

Paste of errors: https://developer.blender.org/P2560 (nevermind the pointcloud.cc file stuff--I was too lazy to compile w/ tests so I just copy and pasted the meat of the functions to this file)

83

Might just be for testing purposes, but I think having developers use the std::in_place_type constructor directly should be avoided when possible. Instead, it'd be easier to use and read make_any which does this under the hood, and many non-expert developers are familiar with the make_XXX API through std::make_unique, std::make_optional, etc... rather than the in_place_type tag versions.

template<typename T, typename... TArgs> auto make_any(TArgs &&...args)
{
  return blender::Any(std::in_place_type<T>, std::forward<TArgs>(args)...);
}

// You can remove the auto if you want but MSVC seems to have a hard time trying to deduce its template arguments. See my other comment.

so instead of

z = Any(std::in_place_type<Any<>>, a);

it becomes

z = make_any<Any<>>(a);
  • Merge branch 'master' into virtual-array-value-type
  • cleanups
  • add some noexcepts
  • add test

I also see that noexcepts never used. Could lead to better codegen, but it's extremely minor and there's probably a reason why ya'll don't use it.

Good point. I added noexcept to a couple of move constructors/assignment operators.

source/blender/blenlib/tests/BLI_any_test.cc
14

I'm still a bit ambivalent when it comes to that currently. I changed it in a couple of places now, but probably won't change it in more places unless we define a general guideline for Blender at some point.

83

Generally makes sense. I'll probably not add this for now because it's not really necessary and there are some complications that I'd rather solve when there is a real need.

  • What should the name of the new function be. Using the same make_any name may be confusing, even when it is in the blender namespace. Alternatives are make_Any or Any<>::Make, ...
  • There is not just one type of Any. So make_any would have to get the template parameters of Any as well.
  • Merge branch 'master' into temp-virtual-array-value-type
  • fixes after merge
Jacques Lucke (JacquesLucke) retitled this revision from Geometry Nodes: Refactor virtual array system (WIP). to Geometry Nodes: Refactor virtual array system..Nov 15 2021, 12:39 PM

This looks good to go as far as I'm concerned (and tests pass ;). A great improvement in readability! I may propose some renaming as a separate step.

source/blender/blenlib/BLI_any.hh
183

Small thing, and I realize we're not totally consistent about this, but generally it's used without the colon.

source/blender/functions/FN_generic_virtual_array.hh
686

A comment about why may_have_ownership needs to come first here is probably a good idea.

This revision is now accepted and ready to land.Nov 15 2021, 5:57 PM