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

[Core] Remove push_back from ModelPart #12903

Open
wants to merge 25 commits into
base: master
Choose a base branch
from

Conversation

sunethwarna
Copy link
Member

@sunethwarna sunethwarna commented Dec 5, 2024

📝 Description
This PR removes uses of PointerVectorSet::push_back method from the ModelPart. Followings are the modifications.

  • When removing entities, erase_count was not calculated correctly, hence the reserved size is wrong. This was fixed
  • Introduced Container class which can retireve the correct PointerVectorSet container from a given Mesh, and its std::string for nice error message output.
  • Refactored the methods as much as possible using templates. [Unfortunatly, Geometries container cannot be templated since it is in the ModelPart, and all the other PointerVectorSet containers are in the Mesh].

🆕 Changelog

  • Removes PointerVectorSet::push_back uses from the ModelPart
  • Fixes bugs in erase count calculation and size reservation when removing Nodes/Conditions/Elements/MasterSlaveConstraints.
  • Added Container class with templates to retrieve given PointerVectorSet container from a mesh.

@loumalouomega
Copy link
Member

Can you use the benchmark to see if there is an improvement?

@loumalouomega
Copy link
Member

I will further review once the CI passes

template<class TReturnValueType>
struct ReferenceGetter
{
template<class TInputValueType>
Copy link
Member

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...

Copy link
Member Author

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.

});

//now add to the root model part
Container<TContainerType>::GetContainer(p_root_model_part->GetMesh()).insert(begin, end);
Copy link
Member

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

Copy link
Member Author

@sunethwarna sunethwarna Dec 10, 2024

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.

Copy link
Member

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?

@sunethwarna sunethwarna requested a review from a team as a code owner December 11, 2024 04:19
@sunethwarna
Copy link
Member Author

sunethwarna commented Dec 16, 2024

Hi all (@loumalouomega @roigcarlo @rubenzorrilla @RiccardoRossi @pooyan-dadvand @matekelemen @philbucher )

This is ready to be reviewed. I did the performance benchmark as well. The results are attached herewith. I did not put all the benchmarks in the benchmarks for model part because some of them are not supported by the existing implementation. Long story short -> Benchmarks shows that the new implementation is faster.

Explanation of the benchmarks:

All the benchmarks were run with 1e+2, 1e+3, 1e+4, 1e+5, 1e+6 entities being added to the model part. In the x-legend, if there is a trailing /0, that means it tries to add the entities to an empty model part / sub model part. if there is a trailin /1, that means it tries to add the entities to non-empty model part. I am here considering only 3 methods for benchmarking

  1. CreateNewNode [BM_ModelPartCreateNewNode]
  2. AddNodes -> Ranged insertion with begin and end [BM_ModelPartAddNodesToRootFromRange2]
  3. AddNodes -> Adding nodes by giving a vector of ints [BM_ModelPartAddNodesToSubSubFromId1]

Serial run:
output_1

4 threads:
output_4

16 threads:
output_16

@matekelemen
Copy link
Contributor

I have trouble understanding these graphs. Can you help me a bit plz?

@sunethwarna
Copy link
Member Author

@matekelemen I updated the original comment with the explanations.

Copy link
Member

@RiccardoRossi RiccardoRossi left a comment

Choose a reason for hiding this comment

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

blocking as there are some inefficiencies

kratos/benchmarks/model_part_benchmark.cpp Show resolved Hide resolved
kratos/sources/model_part.cpp Outdated Show resolved Hide resolved
// 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);
Copy link
Member

Choose a reason for hiding this comment

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

this is not efficient as you may be creating it out of order

Copy link
Member Author

Choose a reason for hiding this comment

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

It cannot be out of order, because we assume HDF5 files are written by the HDF5Application, hence when we write, we always write in the sorted order.

Copy link
Member

Choose a reason for hiding this comment

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

then i do not understand the role of mIds ...

Copy link
Member

Choose a reason for hiding this comment

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

wait, mIds is ordered?

Copy link
Member

Choose a reason for hiding this comment

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

i'll accept the comment, but ... what if somone writes the hdf5 "by hand"? in that case it will be enormously expensive, correct?

// here we can safely use the insert with the rNodes.end() as the hint
// because, when reading, we can assume that it was written in the
// sorted order within HDF5.
rNodes.insert(rNodes.end(), p_node);
Copy link
Member

Choose a reason for hiding this comment

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

same

Copy link
Member Author

Choose a reason for hiding this comment

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

Same comment as before

@@ -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>) {
Copy link
Member

Choose a reason for hiding this comment

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

@roigcarlo can you take a look into GetPointer and GetReference? these are difficult functions and i am quite afraid of their implications

kratos/includes/model_part.h Show resolved Hide resolved
kratos/sources/model_part.cpp Show resolved Hide resolved
// 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);
Copy link
Member

Choose a reason for hiding this comment

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

then i do not understand the role of mIds ...

std::sort(first, last, CompareKey());
auto new_last = std::unique(first, last, EqualKeyTo());
SortedInsert(first, new_last);
} else {
Copy link
Member

Choose a reason for hiding this comment

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

sorry i cannot understand this ...

Copy link
Member

Choose a reason for hiding this comment

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

@Myself, this is for the case in which what i comment above is not possible ... still the comment above still holds

Copy link
Member Author

Choose a reason for hiding this comment

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

then i do not understand the role of mIds ...

mIds are storing the list of ids from a PVS when writing. So each rank will write its own local PVS ids which are sorted. The final list of mIds in the .h5 file won't be sorted, but it will be sorted for each partition in each rank. So when you read it also, each partition will read its portion of the mIds from the .h5 file whill will be an already sorted ids list. hence the rElements.insert(rElements.end(), p_elem) will have the hint which is valid, then PVS will do a push_back at the backend.

Copy link
Member Author

Choose a reason for hiding this comment

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

sorry i cannot understand this ...

This is to support iterators coming from const std::vector<NodeType::Pointer> for the insertion. There we need to sort and unique of the incoming iterators which is not allowed. So, you have to make a copy and do the sort and unique. This has additional overhead, but it cannot be avoided if we allow iterators coming from immutable containers.

Copy link
Member

Choose a reason for hiding this comment

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

yes i understand, however think at the following:

std::vector my_pointer_list ....
pvs.insert(my_pointer_list.begin(), my_pointer_list.end(); //it works
//but here my_pointer_list is ordered... i would not expect this. Is that something we accept? (open question...i am not sure of what would be a standardized behaviour)

indeed

const std::vector my_pointer_list .... //CONST!
//pvs.insert(my_pointer_list.begin(), my_pointer_list.end(); //will int principle not compile
pvs.insert(my_pointer_list.cbegin(), my_pointer_list.cend(); //i understand you will eventually remove the constness inside. Did i get it wrong?
//but here my_pointer_list is NOT ordered (ok, since it was const)

but now in the PVS will i have const pointers? if i do not, aren't we removing the constness wrongly?

to me this is a difficuly programming question, but i am really not clear about what will happen and OF WHAT SHOULD HAPPEN

kratos/sources/model_part.cpp Show resolved Hide resolved
<< ModelPart::Container<container_type>::GetEntityName()
<< " with Id " << *it_id << " does not exist in the root model part";

// here the hint is valid, hence this will be a simple push_back within the PVS.
Copy link
Member

Choose a reason for hiding this comment

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

oh, you mean that under the hood it will eb a push_back. Ok

Copy link
Member Author

Choose a reason for hiding this comment

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

exactly :)

// 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);
Copy link
Member

Choose a reason for hiding this comment

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

wait, mIds is ordered?

SortedInsert(first, new_last);
if constexpr(std::is_assignable_v<decltype(*std::declval<InputIterator>()), decltype(*std::declval<InputIterator>())>) {
// first sorts the input iterators and make the input unique if the iterators are assignable
std::sort(first, last, CompareKey());
Copy link
Member

Choose a reason for hiding this comment

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

this means that you are changing the order of the input "in place", correct?

I am not sure if this is what you would expect. I am not sure of what is the praxis in this context. Do you have it clear?

std::sort(first, last, CompareKey());
auto new_last = std::unique(first, last, EqualKeyTo());
SortedInsert(first, new_last);
} else {
Copy link
Member

Choose a reason for hiding this comment

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

@Myself, this is for the case in which what i comment above is not possible ... still the comment above still holds

@@ -39,8 +39,8 @@ namespace Kratos

// Auxiliar vectors to store pointers to the new entities
// These vectors will be use when renumbering the entities ids.
ModelPart::NodesContainerType new_nodes_vect;
ModelPart::ConditionsContainerType new_conds_vect;
std::vector<Node::Pointer> new_nodes_vect;
Copy link
Member

Choose a reason for hiding this comment

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

this looks ok to me but @rubenzorrilla i think this is your code, can you take a look

@RiccardoRossi RiccardoRossi dismissed their stale review December 17, 2024 09:34

removing my block because of the explanation. However i stil lthink some of the comments are relvant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 🏗 In progress
Development

Successfully merging this pull request may close these issues.

4 participants