-
Notifications
You must be signed in to change notification settings - Fork 269
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
Use unordered maps for component look-ups in views #752
Conversation
Codecov Report
@@ Coverage Diff @@
## main #752 +/- ##
==========================================
- Coverage 65.34% 65.32% -0.03%
==========================================
Files 240 240
Lines 17602 17609 +7
==========================================
+ Hits 11502 11503 +1
- Misses 6100 6106 +6
Continue to review full report at Codecov.
|
/// \brief Hash functor for std::pair<Entity, ComponentTypeId> | ||
public: struct Hasher | ||
{ | ||
std::size_t operator()( | ||
std::pair<Entity, ComponentTypeId> _pair) const | ||
{ | ||
_pair.first ^= _pair.second + 0x9e3779b9 + (_pair.second << 6) | ||
+ (_pair.second >> 2); | ||
return _pair.first; | ||
} | ||
}; |
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 hash function was motivated by https://stackoverflow.com/a/27952689, but I'm wondering if there's a better hash function that we can use. The main concern I have with the current function is that we are implicitly converting uint64_t
(_pair.first
) to std::size_t
. Any thoughts?
When running tests locally, I noticed that the |
ABI checker is failing - I'm not sure if this is a false positive or not, since the files I am modifying are in the |
hmm I know we don't promise ABI/API compatibility for the headers in |
Do you have some rough numbers from profiling? I'm mainly gauging to see if it's worth risking breaking ABI to get these performance improvements. |
I tested 3000 non-static simple shapes, calling I should also mention that those numbers are running headless (physics system plugin only in my server config) with TPE.
I'm not sure about this either. I originally thought that these changes would have to target fortress, but @chapulina believes that they could land in citadel for the reasons you stated (not promising ABI/API for headers in the detail directory). We can wait to see what @j-rivero says about the linking. |
Umm, downloaded the latest version of include/ignition/gazebo on main via 喝 v3.10.2 ❯ nm -DC /usr/lib/x86_64-linux-gnu/libignition-gazebo5.so.5 | grep View::AddComponent
00000000001e42f0 T ignition::gazebo::v5::detail::View::AddComponent(unsigned long, unsigned long, int) Somehow the binary symbol for the detail class is there. Might be wrong but looks like an valid ABI breakage to me. |
hmm sounds like we won't be able to land this in a released version and should probably also ticket an issue about hiding symbols of classes in the perf improvement from 4.2s to 2.9s is actually good in my opinion. We could target this to F or wait till you find a better solution with caching data in Views. |
Signed-off-by: Ashton Larkin <ashton@openrobotics.org>
13ac7ff
to
22adcce
Compare
Done: #759
I went ahead and re-targeted this PR to Fortress, and also added some actual performance numbers/comparisons in the PR description. Based on the numbers I'm seeing in my tests/comparisons, I think that this change could be worthwhile for worlds with a lot of entities. If you wouldn't mind reading the updated PR description and letting me know what you think, that would be great! |
I'm closing this since #856 has been merged. |
Signed-off-by: Ashton Larkin ashton@openrobotics.org
Summary
Related to #711
Currently, when calling
EntityComponentManager::Each
, the following methods get called:There are a few
std::map
lookups in this process:Since lookup time in a
std::map
isO(log(n))
, it's better to usestd::unordered_map
which has a lookup time ofO(1)
(assuming no collisions). This PR makes these changes, with the hope of improving the performance ofecm.Each
.Testing
I tested these changes applied to
ign-gazebo
at commit a41cf80. I used the testing methodology that was followed in #678 (take a look at that PR for more details about how to run the tests). The results are as follows (the modelecm.Each
call I am referring to in the test results is a part of theUpdateSim
in the physics system that can be found here):3000 non-static simple shapes
Using
std::map
: 15.75% RTF, .803ms for the modelecm.Each
callUsing
std::unordered_map
: 16.5% RTF, .306ms for the modelecm.Each
call3000 static simple shapes
Using
std::map
: 50% RTF, .754ms for the modelecm.Each
callUsing
std::unordered_map
: 70% RTF, .289ms for the modelecm.Each
callTakeaways
After running some tests, it appears that the performance gains from this change are minimal, and in some cases, not noticeable at all. This change seems to help the use case where
ecm.Each
is being used on a large(r) set of components that match a lot of entities.We could also change EntityComponentManagerPrivate::views to an unordered map, but this would require some more work/thought because the key of this map is actually a
std::set
. I don't think we should be using astd::set
as the key of a map, but this seems to be necessary due to some limitations in our current implementation of views (this leads me to my next point that we need to fix the current design of views - see the next paragraph).The real reason why this change does not help the performance of
ecm.Each
much is because of how views are currently being used. See #711 (comment) for more information - I plan to address what is mentioned in this comment next.Also, on a related note: I believe that changing https://github.com/ignitionrobotics/ign-gazebo/blob/ignition-gazebo3_3.8.0/include/ignition/gazebo/detail/View.hh#L37-L38 to a
std::unordered_set
may help a bit with performance, but I don't believe this can be done in an existing release because there are methods in the ECM (like FindView) that use thisstd::set
.Edit:
I'd like to give one other perspective as to why the changes here don't result in much of an improvement. If we look at the callback loop in
ecm.Each
again, the time complexity can be calculated as:The variables are defined as follows:
E
: the number of entities (or number offor
loops)C
: the number of componentsL
: the lookup time for a component in a viewC * L
: the time complexity forview.Component<ComponentTypeTs>(entity, this)...
Before this PR,
L
wasO(log(n))
sincestd::map
s were used. After this PR,L
is nowO(1)
. It's worth analyzing the time complexity for two cases:ecm.Each
call. The complexity is now:Before this PR, the time complexity for this case would be
O(C * E * log(E))
.Before this PR, the time complexity for this case would be
O(E^2 * log(E))
.As we can see in this analysis, the real issue is that we still have to spend time finding each component in the view (as mentioned in #711 (comment)) - the time complexity for the
ecm.Each
callback loop should really beO(E)
(all components should be cached at the time of view creation, which would mean thatC * L
is no longer part of the equation for time complexity). If anyone believes that my analysis here may be incorrect, feel free to mention what I am missing, and I will update this accordingly.Checklist
codecheck
passed (See contributing)