-
Notifications
You must be signed in to change notification settings - Fork 248
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
[Core] Remove push_back from ModelPart #12903
base: master
Are you sure you want to change the base?
Conversation
Can you use the benchmark to see if there is an improvement? |
I will further review once the CI passes |
template<class TReturnValueType> | ||
struct ReferenceGetter | ||
{ | ||
template<class TInputValueType> |
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.
wow, this needs some explanation to me.
and the GetPointer even more...
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.
comments are added for the use case.
kratos/includes/model_part.h
Outdated
}); | ||
|
||
//now add to the root model part | ||
Container<TContainerType>::GetContainer(p_root_model_part->GetMesh()).insert(begin, end); |
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.
by adding always to the root all of them, a reserve is done in the pointer vector set on the root which increases its 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.
On the other hand, it is not always good to call shrink_to_fit
as well. In the new design, if a user tries to add a range to a submodel part which is a sub-set of the one of the parent model parts, then it will not reserve the parent model parts which a super set of the range being added. I think this solves this problem partially.
In any case, I can expose the method shrink_to_fit
, but I think this should be done if required only.
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.
sorry, but then with the current implementation the size will always grow?
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.
size will not grow, but the capcity will only grow if the current_size + new_item < current_capcity. So, it may grow or it may not. But it will never keep on growing unless the size keeps on growings. This is further optimized by chekcing for subranges, if the addition is of a subrange, then the size, nor the capacity will increase. It will simply ignore the addition.
There is a test also added to check this.
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.
i would say this is fine as far as i can see it .
I leave @KratosMultiphysics/technical-committee for approval
well, two curiosities (the second more important than the first): 1 - from what i can see threading does not provide any benefit in the benchmarks (nor any deterioration). It is what you expected, correct? |
@RiccardoRossi followings are my answers:
|
That should be OK if tests are global enough. |
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.
Nice, thx @sunethwarna.
I have some mostly doc-related and aesthetical comments, but also request about a test. You mustn't check the container once you moved it.
/** | ||
* @brief Inserts elements from a r_value container. | ||
* @details This method is used to insert all values from an r valued container. | ||
* This mutates the r valued input container to be sorted and unique if it is not the type of PointerVectorSet. | ||
* If it is the type of the PointerVectorSet, then it doesn't mutate the input container. | ||
* | ||
* @warning The move assumes the ownership of the container, hence the the rContainer may be mutated. if the TContainer is of type std::shared_ptr or intrusive_ptr | ||
* then, there will always be a null pointer at the end past position of the unique sorted list since the std::unique uses the move assignment operator | ||
* within its algorithm. Therefore, the new_last created here will have the correct pointer to the object, and the rContainer will have a nullptr in the | ||
* corresponding place. Therefore, if this method is called once, and if it used with a container type which is not PointerVectorSet, please do not use | ||
* the input rContainer anymore. It will segfault unless the PointerVectorSet is created with raw pointers. [This is because since c++11 std::unique uses the move assignment operator] | ||
* @tparam TContainer | ||
* @param rContainer | ||
*/ |
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.
The user doesn't have to know all these details. In fact they must not know in my opinion. All they should be aware of, is that they moved their container into the function, so they cannot use it anymore.
/** | |
* @brief Inserts elements from a r_value container. | |
* @details This method is used to insert all values from an r valued container. | |
* This mutates the r valued input container to be sorted and unique if it is not the type of PointerVectorSet. | |
* If it is the type of the PointerVectorSet, then it doesn't mutate the input container. | |
* | |
* @warning The move assumes the ownership of the container, hence the the rContainer may be mutated. if the TContainer is of type std::shared_ptr or intrusive_ptr | |
* then, there will always be a null pointer at the end past position of the unique sorted list since the std::unique uses the move assignment operator | |
* within its algorithm. Therefore, the new_last created here will have the correct pointer to the object, and the rContainer will have a nullptr in the | |
* corresponding place. Therefore, if this method is called once, and if it used with a container type which is not PointerVectorSet, please do not use | |
* the input rContainer anymore. It will segfault unless the PointerVectorSet is created with raw pointers. [This is because since c++11 std::unique uses the move assignment operator] | |
* @tparam TContainer | |
* @param rContainer | |
*/ | |
/** | |
* @brief Inserts items from an @a rvalue container. | |
* @details This method takes ownership of the input container, and inserts all its items into @ref PointerVectorSet. | |
* | |
* @warning The move assumes the ownership of the container, hence the rContainer may be mutated. | |
* @tparam TContainer | |
* @param rContainer | |
*/ |
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.
I would say, lets keep this detailed description so that we will find it easier to understand. 99% of the users will never look at the PVS, they will stop at the ModelPart
.
template<class TContainerType> | ||
void RemoveEntities( | ||
ModelPart::MeshType& rMesh, | ||
const Flags& rIdentifierFlag) |
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.
I'd just pass a predicate lambda to select which items to remove, instead hard-coding flags to be checked. It'd be much more flexible and similar to the standard lib's interface. Your call though.
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 one is already there...we could add a new one
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.
Not in this PR. Let's do it in a different PR if someone requests it, or if we see some performance improvements by it.
if (GeometryType::HasSameGeometryType(r_geometry, *it_found)) { // Check the geometry type and connectivities | ||
for (IndexType i_pt = 0; i_pt < r_geometry.PointsNumber(); ++i_pt) { | ||
KRATOS_ERROR_IF((r_geometry)[i_pt].Id() != (*it_found)[i_pt].Id()) << "Attempting to add a new geometry with Id: " << r_geometry.Id() << ". A same type geometry with same Id but different connectivities already exists." << std::endl; | ||
} | ||
} else if(&(*it_found) != &r_geometry) { // Check if the pointee coincides | ||
KRATOS_ERROR << "Attempting to add a new geometry with Id: " << it_found->Id() << ". A different geometry with the same Id already exists." << std::endl; | ||
} |
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.
@sunethwarna I know you didn't write this and it's the current behavior, but I have to mention that I really don't like that we have to support duplicate insertions of the same geometry.
Apart from code complexity issues, this really does have a pretty significant performance impact on insertion. I don't get why we're nitpicking over other minor issues when we validate every single geometry inserted into the model part.
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.
i would say this is a different discussion ... i really do not think we should chaneg any behaviour in this PR
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.
I agree with @RiccardoRossi . This PR tries to keep the behaviour changes to the minimum.
@@ -150,6 +150,18 @@ KRATOS_TEST_CASE_IN_SUITE(PointerVectorSetInsert4, KratosCoreFastSuite) | |||
tmp.push_back(p_element_3_copy); | |||
test_container.insert(tmp.begin(), tmp.end()); | |||
|
|||
// check whether the original tmp is not touched. | |||
auto temp_itr = tmp.begin(); | |||
KRATOS_EXPECT_EQ(&**(temp_itr++), &*p_element_2); |
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.
&**(temp_itr++)
oh god :D
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.
hehe.... It it a test, so people can bear it :)
if (GeometryType::HasSameGeometryType(r_geometry, *it_found)) { // Check the geometry type and connectivities | ||
for (IndexType i_pt = 0; i_pt < r_geometry.PointsNumber(); ++i_pt) { | ||
KRATOS_ERROR_IF((r_geometry)[i_pt].Id() != (*it_found)[i_pt].Id()) << "Attempting to add a new geometry with Id: " << r_geometry.Id() << ". A same type geometry with same Id but different connectivities already exists." << std::endl; | ||
} | ||
} else if(&(*it_found) != &r_geometry) { // Check if the pointee coincides | ||
KRATOS_ERROR << "Attempting to add a new geometry with Id: " << it_found->Id() << ". A different geometry with the same Id already exists." << std::endl; | ||
} |
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.
i would say this is a different discussion ... i really do not think we should chaneg any behaviour in this PR
template<class TContainerType> | ||
void RemoveEntities( | ||
ModelPart::MeshType& rMesh, | ||
const Flags& rIdentifierFlag) |
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 one is already there...we could add a new one
// when insertion is done. since we dont use the entities anymore we can use the move operator here. | ||
// since aux is an empty container. it will call push_back in the insert. | ||
container_type aux; | ||
aux.insert(std::move(entities)); |
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.
why do we need the "container_type" at all here? cannot we move directly the entities in line 78?
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.
No, we cannot. Beacuse, the std::move
in line 73 wll sort the entities and will make them unique. Thereofre, line 78 will only be doing the insertion without sorting and making unique. Otherwise, it needs to sort and make it to unique when adding to each of the parent model parts which is redundant.
if (i_entity->IsNot(rIdentifierFlag)) { | ||
// we can safely insert them at the end with the correct hint here since, the original r_mesh | ||
// is sorted and unique. | ||
r_container.insert(r_container.end(), std::move(*(i_entity.base()))); |
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.
i would say this std::move is not needed ... it will be moved automatically
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.
I think it is cleaner to explicitly say it so whoever reads the code knows what is happening without having to dive through the code.
} | ||
|
||
KRATOS_EXPECT_EQ(r_model_part.Nodes().size(), 16); | ||
KRATOS_EXPECT_EQ(r_model_part.Nodes().capacity(), 16); |
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.
No wonder this fails in the CI. Memory management is up to the standard lib's implementation. The only guaranteed way of enforcing a specific capacity is with shrink_to_fit
and you haven't called that anywhere.
I'd just remove capacity tests altogether.
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.
I have changed to check for the change in capacity and used shrink_to_fit to align memory allocations in different compilers.
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.
Well, I don't think that checking shrink_to_fit
is necessary but it also doesn't hurt. The other change (checking against an earlier capacity) is better in my opinion.
Hi all, i think this PR has improved a lot and is ready for the final review. :) |
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.
👍 from my side
Not blocking from my side |
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.
Also think is ready. Just a couple of minor comments.
Also, I really think that not all cases where NodesContainerType
etc... are being misused are covered here (mainly apps that are not on the CI)
So please double check that 🙏
auto new_last = std::unique(temp.begin(), temp.end(), EqualKeyTo()); | ||
SortedInsert(temp.begin(), new_last); | ||
|
||
// KRATOS_ERROR << "HELLO THERE"; |
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.
Remove this.
@@ -1199,7 +1227,10 @@ class PointerVectorSet final | |||
// which is harder to guess, and cryptic. Hence, using the decltype. | |||
using iterator_value_type = std::decay_t<decltype(*Iterator)>; | |||
|
|||
if constexpr(std::is_same_v<iterator_value_type, std::remove_cv_t<TPointerType>>) { | |||
if constexpr(std::is_same_v<TIteratorType, iterator> || std::is_same_v<TIteratorType, reverse_iterator>) { |
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.
I think we had this same discussion in another PR o maybe it was this one. Not the most exciting code imo but is a perfectly valid case just to remove part of the boilerplate from boost.
Otherwise we would need to have these functions n-plicated for every trait combination of the iterator (or a stupidly long list of if constexpr).
// here we can safely use the insert with the rElements.end() as the hint | ||
// because, when reading, we can assume that it was written in the | ||
// sorted order within HDF5. | ||
rElements.insert(rElements.end(), p_elem); |
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.
I tend to lean with @sunethwarna point of view here. We just assume that id's are ordered here, or at least provide a mechanism to sort them before reaching this points. Not only the code is easier to read and do not need sorts in between, when we move to C++20 we can replace this with something like:
...
auto entities = std::views::iota(0,num_new_elems) | std::views::transform([&](int i) -> ElementType::Pointer {
for (unsigned j = 0; j < geometry_size; j++) {
const int node_id = mConnectivities(i, j);
nodes(j) = rNodes(node_id);
}
return p_elem = r_elem.Create(mIds[i], nodes, rProperties(mPropertiesIds[i]));
});
rElements.insert(entities.begin(), entities.end(), entities.end())
Which ensures that the view is inserted directly into rElements
and remove the need for a temporal container with random access or the need to insert 1 by 1, which means that will be faster.
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.
Approving on behalf of the @KratosMultiphysics/technical-committee ( but please remove the comments :) )
📝 Description
This PR removes uses of
PointerVectorSet::push_back
method from theModelPart
. Followings are the modifications.erase_count
was not calculated correctly, hence the reserved size is wrong. This was fixedContainer
class which can retireve the correctPointerVectorSet
container from a givenMesh
, and itsstd::string
for nice error message output.Geometries
container cannot be templated since it is in theModelPart
, and all the otherPointerVectorSet
containers are in theMesh
].🆕 Changelog
PointerVectorSet::push_back
uses from theModelPart
Nodes
/Conditions
/Elements
/MasterSlaveConstraints
.Container
class with templates to retrieve givenPointerVectorSet
container from a mesh.