Core: new blenlib library for implicit-sharing #105994

Merged
Jacques Lucke merged 17 commits from JacquesLucke/blender:implicit-sharing into main 2023-03-28 13:58:02 +02:00
5 changed files with 37 additions and 31 deletions
Showing only changes of commit d5b14539e3 - Show all commits

View File

@ -54,7 +54,7 @@ class AnonymousAttributeID : public ImplicitSharingMixin {
}; };
/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */ /** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */
using AutoAnonymousAttributeID = ImplicitSharePtr<const AnonymousAttributeID>; using AutoAnonymousAttributeID = ImplicitSharingPtr<const AnonymousAttributeID>;
/** /**
* A set of anonymous attribute names that is passed around in geometry nodes. * A set of anonymous attribute names that is passed around in geometry nodes.

View File

@ -101,7 +101,7 @@ inline constexpr bool is_geometry_component_v = std::is_base_of_v<GeometryCompon
*/ */
struct GeometrySet { struct GeometrySet {
private: private:
using GeometryComponentPtr = blender::ImplicitSharePtr<class GeometryComponent>; using GeometryComponentPtr = blender::ImplicitSharingPtr<class GeometryComponent>;
/* Indexed by #GeometryComponentType. */ /* Indexed by #GeometryComponentType. */
std::array<GeometryComponentPtr, GEO_COMPONENT_TYPE_ENUM_SIZE> components_; std::array<GeometryComponentPtr, GEO_COMPONENT_TYPE_ENUM_SIZE> components_;

View File

@ -15,26 +15,27 @@
namespace blender { namespace blender {
/** /**
* #bCopyOnWrite allows implementing copy-on-write behavior, i.e. it allows sharing read-only data * #ImplicitSharingInfo is the core data structure for implicit sharing in Blender. Implicit
* between multiple independend systems (e.g. meshes). The data is only copied when it is shared * sharing is a technique that avoids copying data when it is not necessary. This results in better
* and is about to be modified. This is in contrast to making copies before it is actually known * memory usage and performance. Only read-only data can be shared, because otherwise multiple
* that it is necessary. * owners might want to change the data in conflicting ways.
* *
* Internally, this is mostly just a glorified reference count. If the reference count is 1, the * To determine whether data is shared, #ImplicitSharingInfo keeps a user count. If the count is 1,
* data only has a single owner and is mutable. If it is larger than 1, it is shared and must be * the data only has a single owner and is therefore mutable. If some code wants to modify data
* logically const. * that is currently shared, it has to make a copy first.
* This behavior is also called "copy on write".
* *
* On top of containing a reference count, #bCopyOnWrite also knows how to destruct the referenced * On top of containing a reference count, #ImplicitSharingInfo also knows how to destruct the
* data. This is important because the code freeing the data in the end might not know how it was * referenced data. This is important because the code freeing the data in the end might not know
* allocated (for example, it doesn't know whether an array was allocated using the system or * how it was allocated (for example, it doesn't know whether an array was allocated using the
* guarded allocator). * system or guarded allocator).
* *
* #bCopyOnWrite is used in two ways: * #ImplicitSharingInfo can be used in two ways:
* - It can be allocated separately from the referenced data as is typically the case with raw * - It can be allocated separately from the referenced data. This is used when the shared data is
* arrays (e.g. for mesh attributes). * e.g. a plain data array.
* - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin. * - It can be embedded into another struct. For that it's best to use #ImplicitSharingMixin.
*/ */
struct ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable { class ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable {
private: private:
mutable std::atomic<int> users_; mutable std::atomic<int> users_;
@ -74,14 +75,14 @@ struct ImplicitSharingInfo : blender::NonCopyable, blender::NonMovable {
} }
brecht marked this conversation as resolved Outdated

My understanding is that user count decrement requires std::memory_order_acq_rel, while increment can use std::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

My understanding is that user count decrement requires `std::memory_order_acq_rel`, while increment can use `std::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
private: private:
/** Has to free the #bCopyOnWrite and the referenced data. */ /** Has to free the #ImplicitSharingInfo and the referenced data. */
virtual void delete_self_with_data() = 0; virtual void delete_self_with_data() = 0;
}; };
/** /**
* Makes it easy to embed copy-on-write behavior into a struct. * Makes it easy to embed implicit-sharing behavior into a struct.
JacquesLucke marked this conversation as resolved Outdated

This comment is a bit hard to follow, maybe replacing the subclass with something more specific would help (like saying the ptr class can be used with a class that derives from the mixin class).

This comment is a bit hard to follow, maybe replacing `the subclass` with something more specific would help (like saying the ptr class can be used with a class that derives from the mixin class).
*/ */
struct ImplicitSharingMixin : public ImplicitSharingInfo { class ImplicitSharingMixin : public ImplicitSharingInfo {
public: public:
ImplicitSharingMixin() : ImplicitSharingInfo(1) ImplicitSharingMixin() : ImplicitSharingInfo(1)
{ {

View File

@ -10,33 +10,38 @@
namespace blender { namespace blender {
template<typename T> class ImplicitSharePtr { /**
* #ImplicitSharingPtr is a smart pointer that manages implicit sharing. It's designed to work with
* types that derive from #ImplicitSharingMixin. It is fairly similar to #std::shared_ptr but
* expects the reference count to be embedded in the data.
JacquesLucke marked this conversation as resolved Outdated

expects -> requires, right? std::make_shared will allocate the use count with the object so it's helpful to clarify this a bit.

`expects` -> `requires`, right? `std::make_shared` will allocate the use count with the object so it's helpful to clarify this a bit.
*/
template<typename T> class ImplicitSharingPtr {
private: private:
T *data_ = nullptr; T *data_ = nullptr;
public: public:
ImplicitSharePtr() = default; ImplicitSharingPtr() = default;
ImplicitSharePtr(T *data) : data_(data) ImplicitSharingPtr(T *data) : data_(data)
{ {
} }
ImplicitSharePtr(const ImplicitSharePtr &other) : data_(other.data_) ImplicitSharingPtr(const ImplicitSharingPtr &other) : data_(other.data_)
{ {
this->add_user(data_); this->add_user(data_);
} }
ImplicitSharePtr(ImplicitSharePtr &&other) : data_(other.data_) ImplicitSharingPtr(ImplicitSharingPtr &&other) : data_(other.data_)
{ {
other.data_ = nullptr; other.data_ = nullptr;
} }
~ImplicitSharePtr() ~ImplicitSharingPtr()
{ {
this->remove_user_and_delete_if_last(data_); this->remove_user_and_delete_if_last(data_);
} }
ImplicitSharePtr &operator=(const ImplicitSharePtr &other) ImplicitSharingPtr &operator=(const ImplicitSharingPtr &other)
{ {
if (this == &other) { if (this == &other) {
return *this; return *this;
@ -48,7 +53,7 @@ template<typename T> class ImplicitSharePtr {
return *this; return *this;
} }
ImplicitSharePtr &operator=(ImplicitSharePtr &&other) ImplicitSharingPtr &operator=(ImplicitSharingPtr &&other)
{ {
if (this == &other) { if (this == &other) {
return *this; return *this;
@ -122,7 +127,7 @@ template<typename T> class ImplicitSharePtr {
return get_default_hash(data_); return get_default_hash(data_);
} }
friend bool operator==(const ImplicitSharePtr &a, const ImplicitSharePtr &b) friend bool operator==(const ImplicitSharingPtr &a, const ImplicitSharingPtr &b)
{ {
return a.data_ == b.data_; return a.data_ == b.data_;
} }

View File

@ -226,8 +226,8 @@ struct GatherTasks {
/* Volumes only have very simple support currently. Only the first found volume is put into the /* Volumes only have very simple support currently. Only the first found volume is put into the
* output. */ * output. */
ImplicitSharePtr<const VolumeComponent> first_volume; ImplicitSharingPtr<const VolumeComponent> first_volume;
ImplicitSharePtr<const GeometryComponentEditData> first_edit_data; ImplicitSharingPtr<const GeometryComponentEditData> first_edit_data;
}; };
/** Current offsets while during the gather operation. */ /** Current offsets while during the gather operation. */