Skip to content

Conversation

jessdagostini
Copy link
Contributor

Summary

This PR adds functions to return memory usage from Particles and FabArray/MultiFabs within a grid/box. The main goal is to use this information for load balancing. We aim to consider the memory allocated to a worker as a factor in the load-balancing algorithms for AMReX, so there is a need to capture specific grid/box memory information.

Additional background

Checklist

The proposed changes:

  • fix a bug or incorrect behavior in AMReX
  • add new capabilities to AMReX
  • changes answers in the test suite to more than roundoff level
  • are likely to significantly affect the results of downstream AMReX users
  • include documentation in the code and/or rst files, if appropriate

// We have tried to work with more templated version, but it didn't work. What is the most appropriate way to do it?
LayoutData<std::size_t> CapacityOfFabs (BoxArray const& ba,
DistributionMapping const& dm,
std::vector<MultiFab*> const& mfs);
Copy link
Member

@WeiqunZhang WeiqunZhang Aug 28, 2025

Choose a reason for hiding this comment

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

Could we remove BoxArray and DistributionMapping from the function arguments? The assumption here is all multifabs must have the same boxarray (ignoring the index type) and distributionmapping.

Could make argument be Vector<MultiFab const*> const& mfs. Vector provides bound check if assertion is on. We are not changing the data in mulitfabs. Thus we should pass them in as const*.

If we want it be consistent with how it's done in particle container, we could make this a member function of FabArray. That might be better. That will also solve the template "issue".

Copy link
Contributor Author

@jessdagostini jessdagostini Aug 29, 2025

Choose a reason for hiding this comment

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

I believe it's possible to remove the BoxArray and the DistributionMapping if we have a way to access this information from MultiFabUtil. In the implementation at the .cpp file, we need those to create our LayoutData object that will hold the memory information for each box (7a29e92#diff-e104e0f29e7a0e37ad2ee839c42e2c317091f995556dcc35c18b47d66506761fR67)

Sounds good about the Vector! We weren't sure if our approach was the best :)

We have tried to implement this function over FabArrayUtil, and we had a hard time calling it from an "user-end" code. If you have a suggestion on how to make it work in the FabArray I would appreciate!
For context, I have tested both implementations using examples from the AMReX Tutorials.

More specifically, I have used this example for the Particles https://github.com/AMReX-Codes/amrex-tutorials/blob/main/ExampleCodes/Particles/NeighborList/main.cpp and this for the MultiFabs https://github.com/AMReX-Codes/amrex-tutorials/blob/main/ExampleCodes/Basic/HeatEquation_EX0_C/Source/main.cpp
On the MultiFabs, I used the following code to get the memory information

std::vector<amrex::MultiFab*> multifabs_to_check;
multifabs_to_check.push_back(&phi_old);
multifabs_to_check.push_back(&phi_new);

amrex::LayoutData<std::size_t> mem_usage = amrex::CapacityOfFabs(ba, dm, multifabs_to_check);

long total_bytes = 0;
for (amrex::MFIter mfi(mem_usage); mfi.isValid(); ++mfi) {
       amrex::Print() << "Box " << mfi.index() << " uses "
                   << mem_usage[mfi] << " bytes\n";
 }

Please, let me know if I am missing something or if there's another approach for this!

Copy link
Member

@WeiqunZhang WeiqunZhang Aug 29, 2025

Choose a reason for hiding this comment

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

MultiFab is derived from FabArray, which has member functions that return BoxArray and DistributionMapping used to build the MultiFab. Something like

auto const& ba = mf_ptr->boxArray();
auto const& dm = mf_ptr->DistributionMap();

Copy link
Contributor Author

@jessdagostini jessdagostini Aug 29, 2025

Choose a reason for hiding this comment

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

Thank you! I have pushed an updated version with adjustments. I have tested with the same code example as shared above and got the same results, so I believe it is producing correct data.
About the point on

Vector provides bound check if assertion is on.

I am not doing any assertions at this point. Should I?

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @WeiqunZhang's suggestion to make CapacityofFabs a member function of FabArray. Other than that, looks good to me.

Copy link
Member

Choose a reason for hiding this comment

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

Nice, do you also want to change the particle version to use the same interface? I think this will be helpful when there is more than on ParticleContainer in an application (as there is in WarpX).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think it makes sense to change ParticleContainer to this format! The only concern is that doing so will result in this function having a slightly different signature than other ParticleContainer functions that perform similar tasks, but are not directly memory-related (like https://github.com/AMReX-Codes/amrex/blob/development/Src/Particle/AMReX_ParticleContainerI.H#L496). Do you think this is ok?

Copy link
Member

Choose a reason for hiding this comment

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

I'm okay with that. That function has a slightly different use case. I believe it was added for IO, where we write separate files for each species anyway, so we don't need to aggregate the totals over multiple species, as you do here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok! Working on it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! 7fb20d4

template <class F, std::enable_if_t<IsBaseFab<F>::value,int> FOO>
void
FabArray<FAB>::capacityOfFabs(LayoutData<std::size_t>& mem) {
// Should we have assertions here? What should be asserted?
Copy link
Member

Choose a reason for hiding this comment

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

It would be good to assert that mem and *this use the same BoxArray and DistributionMap. You can use the CellEqual method for BoxArray and the == operator for the DistributionMap.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it! Just pushed the assertion on 7a29e92

Co-authored-by: Andrew Myers <atmyers@lbl.gov>
@jessdagostini
Copy link
Contributor Author

I have committed fixes for errors pointed out by two pipelines. There were two other pipelines "oneAPI" that failed because of a GPG key error. Not sure if I can fix this on my side

Copy link
Member

@WeiqunZhang WeiqunZhang left a comment

Choose a reason for hiding this comment

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

The function is straightforward enough that I don't think we need to add tests for it.

void setDomainBndry (value_type val, int strt_comp, int ncomp, const Geometry& geom);

// Get capacity of the FabArray
template <class F=FAB, std::enable_if_t<IsBaseFab<F>::value,int> = 0>
Copy link
Member

@WeiqunZhang WeiqunZhang Sep 8, 2025

Choose a reason for hiding this comment

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

For flexibility, we could also make the integer type a template typename here and for in particle containers. For example,

template <typename I, class F=FAB, std::enable_if_t<IsBaseFab<F>::value && std::is_integral_v<I> && sizeof(I) >= sizeof(Long), int> = 0>
void capacityOfFabs (LayoutData<I>& mem) const;

And in the implementation, we also want to do explicit static_cast before adding the values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok! Working on the update for this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, do you think it's worth doing it? Because I know that it is a best practice in C++ to use std::size_t for memory-related math (size_t was created for holding sizeof returns https://en.cppreference.com/w/cpp/types/size_t.html)

Copy link
Member

@WeiqunZhang WeiqunZhang Sep 11, 2025

Choose a reason for hiding this comment

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

Actually the best practice is proably to avoid unsigned integers in almost all cases.

Even Stroustrup does not like unsigned integers. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1428r0.pdf Stroustrup said,

Unfortunately, sizeof yields an unsigned (and it would be hard to change that), but we don’t have to
follow that for all types with something to do with sizes

Java does not even have build-in unsigned integer types. https://www.artima.com/articles/james-gosling-on-java-may-2001 Goshling said,

In programming language design, one of the standard problems is that the language grows so complex that nobody can understand it. One of the little experiments I tried was asking people about the rules for unsigned arithmetic in C. It turns out nobody understands how unsigned arithmetic in C works. There are a few obvious things that people understand, but many people don't understand it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, got it! Interesting that the unsigned is such unknown territory. Will start working on including the template you suggested in both functions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just update both function signatures with the new template 55d4a34

jessdagostini and others added 6 commits September 11, 2025 10:36
Co-authored-by: Weiqun Zhang <WeiqunZhang@lbl.gov>
Co-authored-by: Weiqun Zhang <WeiqunZhang@lbl.gov>
Co-authored-by: Weiqun Zhang <WeiqunZhang@lbl.gov>
Co-authored-by: Weiqun Zhang <WeiqunZhang@lbl.gov>
Co-authored-by: Weiqun Zhang <WeiqunZhang@lbl.gov>
Co-authored-by: Weiqun Zhang <WeiqunZhang@lbl.gov>
@atmyers
Copy link
Member

atmyers commented Sep 11, 2025

The function is straightforward enough that I don't think we need to add tests for it.

I asked Jessica to add those. Maybe these new functions could just be called somewhere in an existing test, instead creating new ones?

@jessdagostini
Copy link
Contributor Author

jessdagostini commented Sep 12, 2025

The function is straightforward enough that I don't think we need to add tests for it.

I asked Jessica to add those. Maybe these new functions could just be called somewhere in an existing test, instead creating new ones?

I could include the Particles call in the NeighborList test. Not sure if it's the best option. I am not sure where to put the FabArray one, though.

AMREX_ASSERT(BoxArray::SameRefs(mem.boxArray(), ParticleBoxArray(lev)) &&
mem.DistributionMap() == ParticleDistributionMap(lev));

[[maybe_unused]] Gpu::NoSyncRegion no_sync{};

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable no_sync is not used.
@atmyers
Copy link
Member

atmyers commented Sep 16, 2025

The function is straightforward enough that I don't think we need to add tests for it.

I asked Jessica to add those. Maybe these new functions could just be called somewhere in an existing test, instead creating new ones?

I could include the Particles call in the NeighborList test. Not sure if it's the best option. I am not sure where to put the FabArray one, though.

I think that if you 1) Remove the new particle test added in this PR, in lieu of the modification to the existing NeighborList one, and 2) move/rename the MultiFab one to amrex/Tests/HeatEquation, then this should be good to go.

@jessdagostini
Copy link
Contributor Author

The function is straightforward enough that I don't think we need to add tests for it.

I asked Jessica to add those. Maybe these new functions could just be called somewhere in an existing test, instead creating new ones?

I could include the Particles call in the NeighborList test. Not sure if it's the best option. I am not sure where to put the FabArray one, though.

I think that if you 1) Remove the new particle test added in this PR, in lieu of the modification to the existing NeighborList one, and 2) move/rename the MultiFab one to amrex/Tests/HeatEquation, then this should be good to go.

Sounds good! Did that on 64e1e84

@WeiqunZhang
Copy link
Member

LGTM. Hopefully, one last thing. For the test to run, we need to add it to Line 128 of amrex/Tests/CMakeLists.txt.

@jessdagostini
Copy link
Contributor Author

LGTM. Hopefully, one last thing. For the test to run, we need to add it to Line 128 of amrex/Tests/CMakeLists.txt.

Cool! Pushed here 2b01097

@WeiqunZhang WeiqunZhang merged commit f4295e3 into AMReX-Codes:development Sep 17, 2025
73 of 75 checks passed
EZoni pushed a commit to BLAST-WarpX/warpx that referenced this pull request Sep 17, 2025
Pull in AMReX-Codes/amrex#4629 for #6166.

Signed-off-by: Axel Huebl <axel.huebl@plasma.ninja>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants