WIP: Core: use generic copy-on-write system to avoid redundant copies #104478
Closed
Jacques Lucke
wants to merge 66 commits from
When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
JacquesLucke/blender:temp-copy-on-write
into main
pull from: JacquesLucke/blender:temp-copy-on-write
merge into: blender:main
blender:main
blender:blender-v3.6-release
blender:universal-scene-description
blender:temp-sculpt-dyntopo
blender:blender-v3.3-release
blender:asset-browser-frontend-split
blender:brush-assets-project
blender:asset-shelf
blender:anim/armature-drawing-refactor-3
blender:temp-sculpt-dyntopo-hive-alloc
blender:tmp-usd-python-mtl
blender:tmp-usd-3.6
blender:blender-v3.5-release
blender:blender-projects-basics
blender:blender-v2.93-release
blender:temp-sculpt-attr-api
blender:realtime-clock
blender:sculpt-dev
blender:gpencil-next
blender:bevelv2
blender:microfacet_hair
blender:xr-dev
blender:principled-v2
When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
Reviewers
Request review
No reviewers
Labels
Clear labels
This issue affects/is about backward or forward compatibility
Issues relating to security: https://wiki.blender.org/wiki/Process/Vulnerability_Reports
Apply labels
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
This issue affects/is about backward or forward compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest: Wayland
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Issues relating to security: https://wiki.blender.org/wiki/Process/Vulnerability_Reports
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest: Wayland
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
Milestone
Set milestone
Clear milestone
No items
No Milestone
Projects
Set Project
Clear projects
No project
Assignees
Assign users
Clear assignees
No Assignees
4 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#104478
Reference in New Issue
There is no content yet.
Delete Branch "JacquesLucke/blender:temp-copy-on-write"
Deleting a branch is permanent. Although the deleted branch may exist for a short time before cleaning up, in most cases it CANNOT be undone. Continue?
This patch adds copy-on-write support in a few places. The main goal is to avoid copying data unnecessarily, improving memory usage and performance. For more details, see #95845. This patch is only the culmination of a series of patches mostly @HooglyBoogly worked on. We had to make accessing data in
CustomData
const-correct by introducing the_for_write
functions when accessing layers. Furthermore, the embedded pointers to some special layers likeMesh->mvert
had to be deprecated.This patch includes:
CustomData
refactor that allows sharing attribute layers. To properly implement this, the API had to be changed a bit as well.GeometryComponent
uses the same copy-on-write system, instead of what it used before.AnonymousAttributeID
also uses the system, but doesn't really have to (because the data is immutable anyway). It was just easier to use the same system for now.Possible future uses:
Attribute
class in cycles.GPUVertBuf
.Notes:
BLO_memfile_write_file
because the entire file is not serialized for an undo step anymore. Serializing the rest on demand is not really trivial (e.g. for vertex groups). I think it was generally a nice thing that an undo step could be written to disk, but it does not feel absolutely necessary if it gets in the way of better run-time performance. The consequence is thatquit.blend
is now written using normal file-save and thatUSE_WRITE_CRASH_BLEND
has been removed (never knew it existed before, was someone using that?).editmesh_undo.cc
work right now, but are a bit brittle because they make assumptions about where the data comes from. It should be possible to improve the situation there a bit when layers are not partially freed anymore (currentlyMEM_freeN(layer->data);
only frees the pointer array of e.g. vertex groups).Performance:
Object Info
node in geometry nodes retrieves the geometry from another object and modifies only the position. All other attributes are shared. The modifier evaluation on objectB
takes about 30ms inmain
and 3ms with this patch applied.WIP: use generic copy-on-write system to avoid redundant copiesto WIP: Core: use generic copy-on-write system to avoid redundant copies@blender-bot build
c358271b03
to0f9e2af3b0
@blender-bot build
@blender-bot build
@blender-bot build
@blender-bot build
WIP: Core: use generic copy-on-write system to avoid redundant copiesto Core: use generic copy-on-write system to avoid redundant copiesLooks very good! I have some comments about naming and comments and a couple other small things.
I did find a crash though. To reproduce, you can just use the grow/shrink brush on the "Random" curves from the add menu.
@ -170,0 +161,4 @@
* Initializes a CustomData object with the same layers as source. The data is not copied from the
* source. Instead, the new layers are initialized using the given `alloctype`.
*/
void CustomData_copy_new(const struct CustomData *source,
What about
CustomData_copy_construct
to make the naming consistent with typical RAII patterns? It would tell the reader that thedest
customdata struct doesn't have to be initialized a bit better.@ -2234,1 +2259,3 @@
if ((maxnumber != -1) && (number >= maxnumber)) {
if ((max_current_type_layer_count != -1) &&
(current_type_layer_count >= max_current_type_layer_count)) {
/* Don't merge this layer because the maximum amount of layers of this type is reached. */
Wow, great comments here :D
@ -2315,0 +2349,4 @@
* A #bCopyOnWrite that knows how to free the entire referenced custom data layer (including
* potentially separately allocated chunks like for vertex groups).
*/
class CustomDataLayerCOW : public bCopyOnWrite {
I think it wasn't obvious why this was necessary when you explained it to me at first. A sentence explaining that like "because layer data might be passed outside of the custom data system" would be helpful.
@ -638,3 +638,3 @@
CustomData_free(&pointcloud->pdata, pointcloud->totpoint);
pointcloud->totpoint = me->totvert;
CustomData_merge(&me->vdata, &pointcloud->pdata, CD_MASK_PROP_ALL, CD_DUPLICATE, me->totvert);
No need to add a blank line here IMO
@ -0,0 +14,4 @@
/**
* #bCopyOnWrite allows implementing copy-on-write behavior, i.e. it allows sharing read-only data
* between multiple independend systems (e.g. meshes). The data is only copied when it is shared
Typo:
independend
->independent
@ -0,0 +22,4 @@
* data only has a single owner and is mutable. If it is larger than 1, it is shared and must be
* logically const.
*
* On top of containing a reference count, #bCopyOnWrite also knows how to destruct the referenced
On top of containing a
->In addition to containing the
Just a bit clearer (there's only one reference count per item) and reads a bit more naturally
@ -0,0 +32,4 @@
* arrays (e.g. for mesh attributes).
* - It can be embedded into another struct. For that it's best to use #bCopyOnWriteMixin.
*/
struct bCopyOnWrite : blender::NonCopyable, blender::NonMovable {
This is a C++ header now, and only used in C++ code, so I think it would make sense to:
blender::
namespace like other blenlib classesb
prefix which becomes redundant with the namespace.@ -0,0 +10,4 @@
namespace blender {
template<typename T> class COWUser {
I guess
COWUser
should keep the old comment aboveUserCounter
and maybe say "this adds RAII semantics on top of the copy-on-write logic from #bCopyOnWrite."@ -1689,0 +1694,4 @@
return;
}
MemFile &memfile = *writer->wd->mem.written_memfile;
if (memfile.cow_storage == nullptr) {
Maybe it doesn't matter, but maybe it would be reasonable to just create this once at the start of the writing process? It's likely to be used, and it would save the check for every write call. OTOH I'm sure this isn't a bottleneck!
The goal sounds great!
There are some topics, without really particular order.
Performance
In the goal you states memory usage and performance improvements. But in the conclusion part where you show the improvements you are only focusing on the timing information.
How about memory usage?
It is quite weird to not have such metric measured in the context of copy-on-write (which is a resource-sharing technique, with a goal to save memory by avoiding creation of unneeded copies).
In the context topic raised in the #95845 one would expect actually measurable memory improvement. From the dependency graph point of view, the memory consumption is about 2x for meshes which do not have any modifiers. This is because the dependency graph "de-couples" the evaluated mesh from the original, so that the render thread can access it and modify, while artist can modify the original mesh in viewport, without causing any conflicts. Sharing the heavy CustomData arrays between the evaluated state and the original until they gets modified should give very good memory save.
Measuring the difference could be a bit more challenging, and might not be 2x when comparing against 2.79, but is still should be measurable improvement.
The biggest saving is expected to have on a scene with a lot of meshes with modifiers disabled. This is how the layout and some animation files are set up for the performance reasons. For the simplicity of memory tracking you can fake such setup by creating a mesh with few tens of million of vertices (do it as a base mesh, do not have modifiers).
Naming
The copy-on-write is a technique, which takes care of efficient copy/sharing of a modifiable resource. I do not think having it stated in types and variables really showing intent in the best possible way.
To me the more correct name for
bCopyOnWriteMixin
seems could bebShareableMixing
,bImplicitlyShareableMixin
,bShareableResourceMixin
. It more directly showing the intent of it.I know in a lot of places there is already
cow
all over the place. But this is already confusing, and even violating the initial design where the proper naming is to useorig_
andeval_
.API
I did not have time to check things in details yet, and was mainly looking into the
BKE_customdata.h
, as it is something that many developers interface with, and it was already hard to follow.Maybe some of the points belong to an inlined comment, but some of them affect multiple functions, so seems to make sense to come to a general consensus.
The
new
suffix is something that confuses me. Not sure how it is expected to behave ifCD_ASSIGN
is passes as analloctype
, and maybe naming could be better.If the
CD_ASSIGN
is purely internal-only use, maybe there are ways to make it more explicit like so. Maybe it can be moved away from header to an implementation file? Maybe renamed toCD_ASSIGN_INTERNAL
?For the
new
suffix maybe something likeconstruct_alike
? To kinda show it explicitly in the name that it is only "topology" of layers is considered, and not the content.Also not really sure the difference between
CustomData_merge_new
andCustomData_copy_new
. Some comment/information seems to be missing. Is it than the latter considers thedest
to be completely un-initialized?Another point is the
const struct bCopyOnWrite *cow
. Either the naming is to be changed, or the definition of the structure.In a more ideal world, you'd imagine that you pass buffer as a single argument, so that you are not risking "stripping" user-counter away by accident. In the terms of C this will be something like
and somehow pass
CustomDataLayerData
as a single argument to the_with_data
functions.File IO and Undo
It would indeed eventually be nice to improve memory usage of undo, doing such intrusive changed (as per the PR description) seems a bit odd to do so early on, and will only make the review even longer and more complicated.
What are the consequences of removing
BLO_memfile_write_file
on the auto-save?This is something that is not trivial to guess, and there is potential to cause huge usability problems.
On a quick investigation, it seems that before this change the memfile will always be used, even when in the sculpt mode (the
use_memfile
depends on the user setting, which does not change in the sculpt mode, and there is no warning printed in the console either).The benefit of this approach is that the auto-save is fast, and does not block the UI, disturbing artists. The downside is that the sculpt changes done in the current sculpt session are not saved in the autosave.
After this change, the
ED_editors_flush_edits
is forced to happen. This is not cheap operation when you sculpt on a high-res object. Locking UI for a few seconds at regular time intervals is not something artists will appreciate.Is there a way to split this change into smaller achivable and verifyable steps? For example, is it possible to get the memory optimization of the initial Mesh copy for the depsgraph's CoW without opening the story of the undo? Maybe even smaller pieces are possible (you mentioned making the CustomData const-correct, i.e.)?
Currently the scope seems to be too big, and there are already big topics which seems to be neglected (at least as per the patch description).
Thanks for the feedback!
You're totally right that this should be split up a bit more. The patch started out as an experiment to see where copy-on-write can be used, so I added everything at once and just didn't think of splitting it up later on.
I suggest the following steps:
GeometryComponent
andAnonymousAttributeID
).CustomData
integration.Does that sound reasonable to you? If yes, then I'll split things up this way. Will respond to your more specific comments in the respective pull requests then.
Renaming
bCopyOnWrite
toShareableResource
is fine with me (same for theMixin
type). Maybe something likeSharedResourceInfo
or so could work as well, the name makes it a bit more clear that the type is not necessarily the resource itself, but tracks a potentially separately allocated resource (like an attribute array).As a side note, I just tested this with a heavily subdivided cube. In
main
the memory usage is 2.5 GB and in the branch 1.13 GB as shown below (also approximately matches whathtop
shows me).Splitting this up would indeed help reviewing, I haven't looked closely since it's a bit difficult to get the big picture and I agree with the points Sergey makes.
The quit.blend saving changes could also be reviewed separately from the other undo changes.
For naming, I think this should be called either "copy on write" or "implicit sharing" since that's the standard terminology. Just shared / shareable seems not distinguished enough for
shared_ptr
.@ -0,0 +10,4 @@
namespace blender {
template<typename T> class COWUser {
I would not abbreviate to COW but use CopyOnWrite, or whichever name we end up choosing.
@JacquesLucke The plan sounds good.
The memory saving is fantastic!
@JacquesLucke @HooglyBoogly Any objections of sticking to
implicit sharing
?To me it seems to be the most clear from both showing intent, disambiguating it from the name of a technique, and solves the possible confusion with
shared_ptr
.P.S. The latter one I personally don't really have issues with. To me the
bCopyOnWrite
(how it is currently called and intended to be used anyway) is the same asshared_ptr
when it comes to the life management and assignment. It "just" adds some extra call to "un-share" the resource. But it is important to keep such fundamental data types and APIs as clear as possible and intuitively understood by everyone.I didn't use the term
implicit sharing
before and didn't come across it much, but can get used to it. Based on Wikipedia the term indeed seems to mean what we want, so I'm fine with that.Core: use generic copy-on-write system to avoid redundant copiesto WIP: Core: use generic copy-on-write system to avoid redundant copies@ -0,0 +63,4 @@
void remove_user_and_delete_if_last() const
{
const int old_user_count = users_.fetch_sub(1, std::memory_order_relaxed);
My understanding is that user count decrement requires
std::memory_order_acq_rel
, while increment can usestd::memory_order_relaxed
.For example libc++ does this for
shared_ptr
. More details:https://stackoverflow.com/questions/48124031/stdmemory-order-relaxed-atomicity-with-respect-to-the-same-atomic-variable
@ -0,0 +63,4 @@
void remove_user_and_delete_if_last() const
{
const int old_user_count = users_.fetch_sub(1, std::memory_order_relaxed);
Oops, this was meant to go to #105994.
A patch that only contains the
CustomData
integration is available here: #106228.Reviewers
Pull request closed