Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 65 additions & 5 deletions rclcpp/include/rclcpp/memory_strategy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,39 @@ class MemoryStrategy

/// Borrow memory for storing data for subscriptions, services, clients, or guard conditions.
/**
* The default implementation ignores the handle type and dynamically allocates the memory.
* The default implementation stores std::vectors for each handle type and resizes the vectors
* as necessary based on the requested number of handles.
* \param[in] The type of entity that this function is requesting for.
* \param[in] The number of handles to borrow.
* \return Pointer to the allocated handles.
*/
virtual void ** borrow_handles(HandleType type, size_t number_of_handles)
{
(void)type;
return static_cast<void **>(alloc(sizeof(void *) * number_of_handles));
switch (type) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc block above looks out of date with this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the doc block now.

case HandleType::subscription_handle:
if (subscription_handles.size() < number_of_handles) {
subscription_handles.resize(number_of_handles, 0);
}
return static_cast<void **>(subscription_handles.data());
case HandleType::service_handle:
if (service_handles.size() < number_of_handles) {
service_handles.resize(number_of_handles, 0);
}
return static_cast<void **>(service_handles.data());
case HandleType::client_handle:
if (client_handles.size() < number_of_handles) {
client_handles.resize(number_of_handles, 0);
}
return static_cast<void **>(client_handles.data());
case HandleType::guard_condition_handle:
if (number_of_handles > 2) {
throw std::runtime_error("Too many guard condition handles requested!");
}
return guard_cond_handles.data();
default:
throw std::runtime_error("Unknown HandleType " + std::to_string(static_cast<int>(type)) +
", could not borrow handle memory.");
}
}

/// Return the memory borrowed in borrow_handles.
Expand All @@ -65,8 +89,39 @@ class MemoryStrategy
*/
virtual void return_handles(HandleType type, void ** handles)
{
(void)type;
this->free(handles);
switch (type) {
case HandleType::subscription_handle:
if (handles != subscription_handles.data()) {
throw std::runtime_error(
"tried to return memory that isn't handled by this MemoryStrategy");
}
memset(handles, 0, subscription_handles.size());
break;
case HandleType::service_handle:
if (handles != service_handles.data()) {
throw std::runtime_error(
"tried to return memory that isn't handled by this MemoryStrategy");
}
memset(handles, 0, service_handles.size());
break;
case HandleType::client_handle:
if (handles != client_handles.data()) {
throw std::runtime_error(
"tried to return memory that isn't handled by this MemoryStrategy");
}
memset(handles, 0, client_handles.size());
break;
case HandleType::guard_condition_handle:
if (handles != guard_cond_handles.data()) {
throw std::runtime_error(
"tried to return memory that isn't handled by this MemoryStrategy");
}
guard_cond_handles.fill(0);
break;
default:
throw std::runtime_error("Unknown HandleType " + std::to_string(static_cast<int>(type)) +
", could not borrow handle memory.");
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth checking that the value in handles matches the address of the pointer returned by .data() of the corresponding vector?

}

/// Provide a newly initialized AnyExecutable object.
Expand Down Expand Up @@ -101,6 +156,11 @@ class MemoryStrategy
std::vector<rclcpp::subscription::SubscriptionBase::SharedPtr> subs;
std::vector<rclcpp::service::ServiceBase::SharedPtr> services;
std::vector<rclcpp::client::ClientBase::SharedPtr> clients;

std::vector<void *> subscription_handles;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a max number of handles supported which could be reserved in the constructor? Using resize may or may not allocate unless it's been previously allocated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this memory strategy it doesn't matter if it allocates on .resize().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine for the vectors to be initialized to empty. Specifying a number of handles reserve beforehand seems unnecessarily complicated for the default case. The alternative MemoryStrategy implementations take max numbers of handles as constructor arguments, however.

When borrow_handles is called, if the size of the vector for that handle type is smaller than the requested size n, then the vector will call resize(n).

http://en.cppreference.com/w/cpp/container/vector/resize

My interpretation is that calling resize for a size greater than the vector's current size will insert elements into the vector to make it grow to the required size.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes resize is safe it will allocate if not enough is reserved. The title was about reducing allocations I didn't know if we wanted to do the preallocation. If this is just the default it will work fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dirk-thomas clarified that if the vector's capacity is smaller than the requested size, std::vector.resize() will allocate a block of new entries, often to the next power of two. So resize won't necessarily allocate every time it's called, but it may incur a large penalty when the capacity needs to grow.

But for our purposes this is fine since these vectors will usually store small numbers (<16).

std::vector<void *> service_handles;
std::vector<void *> client_handles;
std::array<void *, 2> guard_cond_handles;
};


Expand Down