Skip to content
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

Replace hash_gid with FNV-1a #422

Open
wants to merge 2 commits into
base: rolling
Choose a base branch
from
Open
Show file tree
Hide file tree
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
13 changes: 1 addition & 12 deletions rmw_zenoh_cpp/src/detail/liveliness_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ Entity::Entity(
sizeof(keyexpr_gid.high64));

// We also hash the liveliness keyexpression into a size_t that we use to index into our maps.
this->keyexpr_hash_ = hash_gid(this->gid_);
this->keyexpr_hash_ = std::hash<Gid>{}(this->gid_);
}

///=============================================================================
Expand Down Expand Up @@ -670,15 +670,4 @@ std::string demangle_name(const std::string & input)
return output;
}
} // namespace liveliness

///=============================================================================
size_t hash_gid(const std::array<uint8_t, RMW_GID_STORAGE_SIZE> gid)
{
std::stringstream hash_str;
hash_str << std::hex;
for (const auto & g : gid) {
hash_str << static_cast<int>(g);
}
return std::hash<std::string>{}(hash_str.str());
}
} // namespace rmw_zenoh_cpp
26 changes: 24 additions & 2 deletions rmw_zenoh_cpp/src/detail/liveliness_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,7 @@ std::string qos_to_keyexpr(const rmw_qos_profile_t & qos);
std::optional<rmw_qos_profile_t> keyexpr_to_qos(const std::string & keyexpr);
} // namespace liveliness

///=============================================================================
size_t hash_gid(const std::array<uint8_t, RMW_GID_STORAGE_SIZE> gid);
using Gid = std::array<uint8_t, RMW_GID_STORAGE_SIZE>;
} // namespace rmw_zenoh_cpp

///=============================================================================
Expand Down Expand Up @@ -270,6 +269,29 @@ struct equal_to<rmw_zenoh_cpp::liveliness::ConstEntityPtr>
return lhs->keyexpr_hash() == rhs->keyexpr_hash();
}
};

template<>
struct hash<rmw_zenoh_cpp::Gid>
{
std::size_t operator()(const rmw_zenoh_cpp::Gid & gid) const noexcept
{
// This function implemented FNV-1a 64-bit as the GID is small enough
// (e.g. as of commit db4d05e of ros2/rmw RMW_GID_STORAGE_SIZE is set to 16)
// and FNV is known to be efficient in such cases.
//
// See https://github.com/ros2/rmw/blob/db4d05e/rmw/include/rmw/types.h#L44
// and https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html
static constexpr uint64_t FNV_OFFSET_BASIS_64 = 0x00000100000001b3;
static constexpr uint64_t FNV_PRIME_64 = 0xcbf29ce484222325;
Comment on lines +278 to +285
Copy link
Collaborator

Choose a reason for hiding this comment

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

So we are already using xxhash3 in liveliness_utils to take a string and hash it into a 128-bit quantity. Do we need to hash again? In other words, could we just use the 128-bit GID directly and use that as the key?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe std::hash requires a size_t.


uint64_t hash = FNV_OFFSET_BASIS_64;
for (const uint8_t & byte : gid) {
hash ^= byte;
hash *= FNV_PRIME_64;
}
return hash;
}
};
} // namespace std

#endif // DETAIL__LIVELINESS_UTILS_HPP_
16 changes: 7 additions & 9 deletions rmw_zenoh_cpp/src/detail/rmw_service_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -346,12 +346,11 @@ rmw_ret_t ServiceData::take_request(
request_header->received_timestamp = query->get_received_timestamp();

// Add this query to the map, so that rmw_send_response can quickly look it up later.
const size_t hash = rmw_zenoh_cpp::hash_gid(writer_guid);
std::unordered_map<size_t, SequenceToQuery>::iterator it = sequence_to_query_map_.find(hash);
std::unordered_map<Gid, SequenceToQuery>::iterator it = sequence_to_query_map_.find(writer_guid);
if (it == sequence_to_query_map_.end()) {
SequenceToQuery stq;
sequence_to_query_map_.insert(std::make_pair(hash, std::move(stq)));
it = sequence_to_query_map_.find(hash);
sequence_to_query_map_.insert(std::make_pair(writer_guid, std::move(stq)));
it = sequence_to_query_map_.find(writer_guid);
} else {
// Client already in the map
if (it->second.find(request_header->request_id.sequence_number) != it->second.end()) {
Expand Down Expand Up @@ -380,12 +379,11 @@ rmw_ret_t ServiceData::send_response(
return RMW_RET_OK;
}

std::array<uint8_t, RMW_GID_STORAGE_SIZE> writer_guid;
Gid writer_guid;
memcpy(writer_guid.data(), request_id->writer_guid, RMW_GID_STORAGE_SIZE);

// Create the queryable payload
const size_t hash = hash_gid(writer_guid);
std::unordered_map<size_t, SequenceToQuery>::iterator it = sequence_to_query_map_.find(hash);
std::unordered_map<Gid, SequenceToQuery>::iterator it = sequence_to_query_map_.find(writer_guid);
if (it == sequence_to_query_map_.end()) {
// If there is no data associated with this request, the higher layers of
// ROS 2 seem to expect that we just silently return with no work.
Expand All @@ -399,8 +397,8 @@ rmw_ret_t ServiceData::send_response(
}
std::unique_ptr<ZenohQuery> query = std::move(query_it->second);
it->second.erase(query_it);
if (sequence_to_query_map_[hash].size() == 0) {
sequence_to_query_map_.erase(hash);
if (sequence_to_query_map_[writer_guid].size() == 0) {
sequence_to_query_map_.erase(writer_guid);
}

rcutils_allocator_t * allocator = &(rmw_node_->context->options.allocator);
Expand Down
2 changes: 1 addition & 1 deletion rmw_zenoh_cpp/src/detail/rmw_service_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class ServiceData final : public std::enable_shared_from_this<ServiceData>
std::deque<std::unique_ptr<ZenohQuery>> query_queue_;
// Map to store the sequence_number (as given by the client) -> ZenohQuery
using SequenceToQuery = std::unordered_map<int64_t, std::unique_ptr<ZenohQuery>>;
std::unordered_map<size_t, SequenceToQuery> sequence_to_query_map_;
std::unordered_map<Gid, SequenceToQuery> sequence_to_query_map_;
// Wait set data.
rmw_wait_set_data_t * wait_set_data_;
// Data callback manager.
Expand Down
6 changes: 3 additions & 3 deletions rmw_zenoh_cpp/src/detail/rmw_subscription_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -610,8 +610,8 @@ void SubscriptionData::add_new_message(
}

// Check for messages lost if the new sequence number is not monotonically increasing.
const size_t gid_hash = hash_gid(msg->attachment.copy_gid());
auto last_known_pub_it = last_known_published_msg_.find(gid_hash);
const Gid gid = msg->attachment.copy_gid();
auto last_known_pub_it = last_known_published_msg_.find(gid);
if (last_known_pub_it != last_known_published_msg_.end()) {
const int64_t seq_increment = std::abs(
msg->attachment.sequence_number() -
Expand All @@ -624,7 +624,7 @@ void SubscriptionData::add_new_message(
}
}
// Always update the last known sequence number for the publisher.
last_known_published_msg_[gid_hash] = msg->attachment.sequence_number();
last_known_published_msg_[gid] = msg->attachment.sequence_number();

message_queue_.emplace_back(std::move(msg));

Expand Down
2 changes: 1 addition & 1 deletion rmw_zenoh_cpp/src/detail/rmw_subscription_data.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class SubscriptionData final : public std::enable_shared_from_this<SubscriptionD
std::unique_ptr<MessageTypeSupport> type_support_;
std::deque<std::unique_ptr<Message>> message_queue_;
// Map GID of a subscription to the sequence number of the message it published.
std::unordered_map<size_t, int64_t> last_known_published_msg_;
std::unordered_map<Gid, int64_t> last_known_published_msg_;
// Wait set data.
rmw_wait_set_data_t * wait_set_data_;
// Callback managers.
Expand Down