Lazy instance value pointer allocation

We currently allocate instance values when creating the instance itself
(except when constructing the instance for a `cast()`), but there is no
particular reason to do so: the instance itself and the internals (for
a non-simple layout) are allocated via Python, with no reason to
expect better locality from the invoked `operator new`.  Moreover, it
makes implementation of factory function constructors trickier and
slightly less efficient: they don't use the pre-eallocate the memory,
which means there is a pointless allocation and free.

This commit makes the allocation lazy: instead of preallocating when
creating the instance, the allocation happens when the instance is
first loaded (if null at that time).

In addition to making it more efficient to deal with cases that don't
need preallocation, this also allows for a very slight performance
increase by not needing to look up the instances types during
allocation.  (There is a lookup during the eventual load, of course, but
that is happening already).
This commit is contained in:
Jason Rhinelander 2017-07-31 23:32:34 -04:00
parent 8665ee8100
commit fd81a03ec9
3 changed files with 15 additions and 17 deletions

View File

@ -560,7 +560,7 @@ inline PyThreadState *get_thread_state_unchecked() {
// Forward declarations // Forward declarations
inline void keep_alive_impl(handle nurse, handle patient); inline void keep_alive_impl(handle nurse, handle patient);
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value = true); inline PyObject *make_new_instance(PyTypeObject *type);
class type_caster_generic { class type_caster_generic {
public: public:
@ -591,7 +591,7 @@ public:
} }
} }
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type, false /* don't allocate value */)); auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
auto wrapper = reinterpret_cast<instance *>(inst.ptr()); auto wrapper = reinterpret_cast<instance *>(inst.ptr());
wrapper->owned = false; wrapper->owned = false;
void *&valueptr = values_and_holders(wrapper).begin()->value_ptr(); void *&valueptr = values_and_holders(wrapper).begin()->value_ptr();
@ -647,8 +647,14 @@ public:
protected: protected:
// Base methods for generic caster; there are overridden in copyable_holder_caster // Base methods for generic caster; there are overridden in copyable_holder_caster
void load_value(const value_and_holder &v_h) { void load_value(value_and_holder &&v_h) {
value = v_h.value_ptr(); auto *&vptr = v_h.value_ptr();
// Lazy allocation for unallocated values:
if (vptr == nullptr) {
auto *type = v_h.type ? v_h.type : typeinfo;
vptr = type->operator_new(type->type_size);
}
value = vptr;
} }
bool try_implicit_casts(handle src, bool convert) { bool try_implicit_casts(handle src, bool convert) {
for (auto &cast : typeinfo->implicit_casts) { for (auto &cast : typeinfo->implicit_casts) {

View File

@ -232,11 +232,10 @@ inline bool deregister_instance(instance *self, void *valptr, const type_info *t
return ret; return ret;
} }
/// Instance creation function for all pybind11 types. It only allocates space for the C++ object /// Instance creation function for all pybind11 types. It allocates the internal instance layout for
/// (or multiple objects, for Python-side inheritance from multiple pybind11 types), but doesn't /// holding C++ objects and holders. Allocation is done lazily (the first time the instance is cast
/// call the constructor -- an `__init__` function must do that (followed by an `init_instance` /// to a reference or pointer), and initialization is done by an `__init__` function.
/// to set up the holder and register the instance). inline PyObject *make_new_instance(PyTypeObject *type) {
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= true (in cast.h)*/) {
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION)
// PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first inherited // PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first inherited
// object is a a plain Python type (i.e. not derived from an extension type). Fix it. // object is a a plain Python type (i.e. not derived from an extension type). Fix it.
@ -251,13 +250,6 @@ inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= t
inst->allocate_layout(); inst->allocate_layout();
inst->owned = true; inst->owned = true;
// Allocate (if requested) the value pointers; otherwise leave them as nullptr
if (allocate_value) {
for (auto &v_h : values_and_holders(inst)) {
void *&vptr = v_h.value_ptr();
vptr = v_h.type->operator_new(v_h.type->type_size);
}
}
return self; return self;
} }

View File

@ -434,7 +434,7 @@ struct instance {
/// If true, get_internals().patients has an entry for this object /// If true, get_internals().patients has an entry for this object
bool has_patients : 1; bool has_patients : 1;
/// Initializes all of the above type/values/holders data /// Initializes all of the above type/values/holders data (but not the instance values themselves)
void allocate_layout(); void allocate_layout();
/// Destroys/deallocates all of the above /// Destroys/deallocates all of the above