-
Notifications
You must be signed in to change notification settings - Fork 50
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
Take control over resizing to enforce required invariants #383
Take control over resizing to enforce required invariants #383
Conversation
b10cbe9
to
48f3f47
Compare
template <> | ||
inline typename r_vector<double>::underlying_type const* r_vector<double>::get_const_p( | ||
bool is_altrep, SEXP data) { | ||
return REAL_OR_NULL(data); | ||
} |
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.
Using utilities like REAL_OR_NULL()
gives ALTREP types that can cheaply return a const pointer to an underlying buffer a chance to do so (like ALTREP views)
inline r_vector<T>::r_vector(const r_vector& rhs) | ||
: cpp11::r_vector<T>(safe[Rf_shallow_duplicate](rhs)), capacity_(rhs.capacity_) {} | ||
inline r_vector<T>::r_vector(const r_vector& rhs) { | ||
// We don't want to just pass through to the read-only constructor because we'd | ||
// have to convert to `SEXP` first, which could truncate, and then we'd still have | ||
// to shallow duplicate after that to ensure we have a duplicate, which can result in | ||
// too many copies (#369). | ||
// | ||
// Instead we take control of setting all fields to try and only duplicate 1 time. | ||
// We try and reclaim unused capacity during the duplication by only reserving up to | ||
// the `rhs.length_`. This is nice because if the user returns this object, the | ||
// truncation has already been done and they don't have to pay for another allocation. | ||
// Importantly, `reserve_data()` always duplicates even if there wasn't extra capacity, | ||
// which ensures we have our own copy. | ||
data_ = reserve_data(rhs.data_, rhs.is_altrep_, rhs.length_); | ||
protect_ = detail::store::insert(data_); | ||
is_altrep_ = ALTREP(data_); | ||
data_p_ = get_p(is_altrep_, data_); | ||
length_ = rhs.length_; | ||
capacity_ = rhs.length_; | ||
} |
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.
This avoids the double copy from #369
Before
> profmem::profmem(test_())
Rprofmem memory profiling of:
test_()
Memory allocations:
what bytes calls
1 alloc 400048 test_() -> .Call()
2 alloc 800048 test_() -> .Call()
3 alloc 400056 test_() -> .Call()
4 alloc 400056 test_() -> .Call() # <- this one is extra
total 2000208
After
> profmem::profmem(test_())
Rprofmem memory profiling of:
test_()
Memory allocations:
what bytes calls
1 alloc 400048 test_() -> .Call()
2 alloc 800048 test_() -> .Call()
3 alloc 400056 test_() -> .Call()
total 1600152
// Compared to `Rf_xlengthgets()`: | ||
// - This always allocates, even if it is the same size, which is important when we use | ||
// it in a constructor and need to ensure that it duplicates on the way in. | ||
// - This copies over attributes with `Rf_copyMostAttrib()`, which is important when we | ||
// use it in constructors and when we truncate right before returning from the `SEXP` | ||
// operator. | ||
// - This is more friendly to ALTREP `x`. | ||
template <typename T> | ||
inline SEXP r_vector<T>::reserve_data(SEXP x, bool is_altrep, R_xlen_t size) { |
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.
Important invariants of reserve_data()
vs Rf_xlengthgets()
Useful for both:
- Usage in constructors, when we need to enforce a duplication to occur
- Usage during truncation, when we need to copy attributes too
Closes #369