diff --git a/cpp/dolfinx/common/CMakeLists.txt b/cpp/dolfinx/common/CMakeLists.txt index 3a5c77044e0..b94329122d4 100644 --- a/cpp/dolfinx/common/CMakeLists.txt +++ b/cpp/dolfinx/common/CMakeLists.txt @@ -7,6 +7,8 @@ set(HEADERS_common ${CMAKE_CURRENT_SOURCE_DIR}/sort.h ${CMAKE_CURRENT_SOURCE_DIR}/types.h ${CMAKE_CURRENT_SOURCE_DIR}/math.h + ${CMAKE_CURRENT_SOURCE_DIR}/memory.h + ${CMAKE_CURRENT_SOURCE_DIR}/memory_fwd.h ${CMAKE_CURRENT_SOURCE_DIR}/MPI.h ${CMAKE_CURRENT_SOURCE_DIR}/Scatterer.h ${CMAKE_CURRENT_SOURCE_DIR}/Table.h diff --git a/cpp/dolfinx/common/IndexMap.cpp b/cpp/dolfinx/common/IndexMap.cpp index ba2eb8a669a..5af45b44ca2 100644 --- a/cpp/dolfinx/common/IndexMap.cpp +++ b/cpp/dolfinx/common/IndexMap.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2015-2024 Chris Richardson, Garth N. Wells, Igor Baratta, -// Joseph P. Dean and Jørgen S. Dokken +// Joseph P. Dean, Jørgen S. Dokken and Paul T. Kühner // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -1374,3 +1374,18 @@ std::array, 2> IndexMap::rank_type(int split_type) const return {std::move(split_dest), std::move(split_src)}; } //----------------------------------------------------------------------------- + +std::size_t dolfinx::common::impl::memory(const IndexMap& im) +{ + std::size_t size = 0; + + size += sizeof(IndexMap); + size += memory(im.local_range()); + size += memory(im.ghosts()); + size += memory(im.owners()); + size += memory(im.src()); + size += memory(im.dest()); + + return size; +} +//----------------------------------------------------------------------------- diff --git a/cpp/dolfinx/common/IndexMap.h b/cpp/dolfinx/common/IndexMap.h index 0464a7752fa..a4cc43168e0 100644 --- a/cpp/dolfinx/common/IndexMap.h +++ b/cpp/dolfinx/common/IndexMap.h @@ -8,9 +8,9 @@ #include "IndexMap.h" #include "MPI.h" +#include "memory.h" #include #include -#include #include #include #include @@ -332,4 +332,9 @@ class IndexMap std::vector _dest; }; +namespace impl +{ +std::size_t memory(const IndexMap& im); +} + } // namespace dolfinx::common diff --git a/cpp/dolfinx/common/memory.h b/cpp/dolfinx/common/memory.h new file mode 100644 index 00000000000..5f9ecedeeb7 --- /dev/null +++ b/cpp/dolfinx/common/memory.h @@ -0,0 +1,107 @@ +// Copyright (C) 2025 Paul T. Kühner +// +// This file is part of DOLFINx (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "memory_fwd.h" +#include "types.h" + +namespace dolfinx::common +{ + +namespace impl +{ + +template +std::size_t memory(const T& /* obj */) +{ + static_assert(false, "Memory usage not supported for provided type."); + return 0; +} + +template + requires std::is_arithmetic_v +std::size_t memory(const std::array& array) +{ + return sizeof(array); +} + +template + requires std::is_arithmetic_v +std::size_t memory(const std::vector& vec) +{ + using value_type = typename std::vector::value_type; + + std::size_t size_type = sizeof(vec); + std::size_t size_data = vec.capacity() * sizeof(value_type); + return size_type + size_data; +} + +// TODO: document ownership assumption +template + requires std::is_arithmetic_v +std::size_t memory(const std::span& span) +{ + using value_type = typename std::vector::value_type; + + std::size_t size_type = sizeof(span); + std::size_t size_data = span.size() * sizeof(value_type); + return size_type + size_data; +} + +template +std::size_t memory(const std::vector>& vec) +{ + std::size_t size = sizeof(vec); + std::ranges::for_each(vec, [&](const auto& e) { size += memory(e); }); + return size; +} + +template > + requires std::is_arithmetic_v +std::size_t +memory(const md::mdspan& mdspan) +{ + using value_type = typename std::mdspan::value_type; + + // TODO: object size? - what happens for compile time sized mdspans? + std::size_t size_data = mdspan.size() * sizeof(value_type); + + return size_data; +} + +} // namespace impl + +constexpr std::integral_constant byte; +constexpr std::integral_constant kilobyte; +constexpr std::integral_constant megabyte; +constexpr std::integral_constant gigabyte; +constexpr std::integral_constant terabyte; + +template +std::conditional_t +memory(const T& obj, std::integral_constant bytes_per_unit) +{ + std::size_t bytes = impl::memory(obj); + if constexpr (bytes_per_unit == byte) + return bytes; + else + return static_cast(bytes) / bytes_per_unit.value; +} + +} // namespace dolfinx::common diff --git a/cpp/dolfinx/common/memory_fwd.h b/cpp/dolfinx/common/memory_fwd.h new file mode 100644 index 00000000000..66f2739d83d --- /dev/null +++ b/cpp/dolfinx/common/memory_fwd.h @@ -0,0 +1,33 @@ +// Copyright (C) 2025 Paul T. Kühner +// +// This file is part of DOLFINx (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include + +namespace dolfinx +{ + +namespace common +{ +class IndexMap; +} + +namespace mesh +{ +template +class Geometry; +} + +namespace common::impl +{ +std::size_t memory(const IndexMap& im); + +template +std::size_t memory(const dolfinx::mesh::Geometry& geometry); +} // namespace common::impl + +} // namespace dolfinx \ No newline at end of file diff --git a/cpp/dolfinx/mesh/Geometry.h b/cpp/dolfinx/mesh/Geometry.h index fca6a7db46c..0a983808a6f 100644 --- a/cpp/dolfinx/mesh/Geometry.h +++ b/cpp/dolfinx/mesh/Geometry.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2022 Anders Logg and Garth N. Wells +// Copyright (C) 2006-2022 Anders Logg, Garth N. Wells and Paul T. Kühner // // This file is part of DOLFINx (https://www.fenicsproject.org) // @@ -328,3 +328,29 @@ create_geometry(const Topology& topology, } } // namespace dolfinx::mesh + +namespace dolfinx::common::impl +{ + +template +std::size_t memory(const dolfinx::mesh::Geometry& geometry) +{ + + std::size_t size = 0; + size += sizeof(geometry); + + size += memory(*geometry.index_map()); + + for (std::size_t i = 0; i < geometry.cmaps().size(); i++) + { + size += memory(geometry.dofmap(i)); + // TODO: requires heavy work for basix::finite_element + // size += memory(geometry.cmaps()[i]); + } + + size += memory(geometry.x()); + size += memory(geometry.input_global_indices()); + + return size; +}; +} // namespace dolfinx::common::impl \ No newline at end of file diff --git a/cpp/test/CMakeLists.txt b/cpp/test/CMakeLists.txt index b3665a11791..f17cef50967 100644 --- a/cpp/test/CMakeLists.txt +++ b/cpp/test/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable( common/CIFailure.cpp common/sub_systems_manager.cpp common/index_map.cpp + common/memory.cpp common/sort.cpp fem/form.cpp fem/functionspace.cpp diff --git a/cpp/test/common/memory.cpp b/cpp/test/common/memory.cpp new file mode 100644 index 00000000000..09d3310a8d9 --- /dev/null +++ b/cpp/test/common/memory.cpp @@ -0,0 +1,126 @@ +// Copyright (C) 2025 Paul T. Kühner +// +// This file is part of DOLFINx (https://www.fenicsproject.org) +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#include +#include + +#include +#include + +#include "dolfinx/common/memory.h" +#include "dolfinx/mesh/Geometry.h" +#include "dolfinx/mesh/generation.h" + +using namespace dolfinx::common; + +TEMPLATE_TEST_CASE("memory-array", "[memory]", std::int16_t, std::int32_t, + std::int64_t, std::uint16_t, std::uint32_t, std::uint64_t, + float, double) +// std::complex, std::complex +{ + std::array v; + + std::size_t bytes = 10 * sizeof(TestType); + + CHECK(memory(v, byte) == bytes); + + CHECK(memory(v, kilobyte) + == Catch::Approx(static_cast(bytes) / kilobyte)); + CHECK(memory(v, megabyte) + == Catch::Approx(static_cast(bytes) / megabyte)); + CHECK(memory(v, gigabyte) + == Catch::Approx(static_cast(bytes) / gigabyte)); + CHECK(memory(v, terabyte) + == Catch::Approx(static_cast(bytes) / terabyte)); +} + +TEMPLATE_TEST_CASE("memory-vector", "[memory]", std::int16_t, std::int32_t, + std::int64_t, std::uint16_t, std::uint32_t, std::uint64_t, + float, double) +// std::complex, std::complex +{ + std::vector v; + v.reserve(10); + + std::size_t bytes = sizeof(std::vector) + 10 * sizeof(TestType); + + CHECK(memory(v, byte) == bytes); + + CHECK(memory(v, kilobyte) + == Catch::Approx(static_cast(bytes) / kilobyte)); + CHECK(memory(v, megabyte) + == Catch::Approx(static_cast(bytes) / megabyte)); + CHECK(memory(v, gigabyte) + == Catch::Approx(static_cast(bytes) / gigabyte)); + CHECK(memory(v, terabyte) + == Catch::Approx(static_cast(bytes) / terabyte)); +} + +TEMPLATE_TEST_CASE("memory-span", "[memory]", std::int16_t, std::int32_t, + std::int64_t, std::uint16_t, std::uint32_t, std::uint64_t, + float, double) +// std::complex, std::complex +{ + std::array v; + std::span span{v}; + + std::size_t bytes = 10 * sizeof(TestType); + + CHECK(memory(v, byte) == bytes); + + CHECK(memory(v, kilobyte) + == Catch::Approx(static_cast(bytes) / kilobyte)); + CHECK(memory(v, megabyte) + == Catch::Approx(static_cast(bytes) / megabyte)); + CHECK(memory(v, gigabyte) + == Catch::Approx(static_cast(bytes) / gigabyte)); + CHECK(memory(v, terabyte) + == Catch::Approx(static_cast(bytes) / terabyte)); +} + +TEMPLATE_TEST_CASE("memory-vector-vector", "[memory]", std::int16_t, + std::int32_t, std::int64_t, std::uint16_t, std::uint32_t, + std::uint64_t, float, double) +// std::complex, std::complex +{ + std::vector> v; + v.reserve(3); + v.template emplace_back>({{0, 1, 2}}); + v.template emplace_back>({{0, 1, 2, 3}}); + v.template emplace_back>({{0, 1, 2, 3, 4}}); + + std::size_t bytes = sizeof(std::vector>) + + 3 * sizeof(std::vector) + + (3 + 4 + 5) * sizeof(TestType); + + CHECK(memory(v, byte) == bytes); + + CHECK(memory(v, kilobyte) + == Catch::Approx(static_cast(bytes) / kilobyte)); + CHECK(memory(v, megabyte) + == Catch::Approx(static_cast(bytes) / megabyte)); + CHECK(memory(v, gigabyte) + == Catch::Approx(static_cast(bytes) / gigabyte)); + CHECK(memory(v, terabyte) + == Catch::Approx(static_cast(bytes) / terabyte)); +} + +TEST_CASE("memory-indexmap", "[memory]") +{ + auto im = IndexMap(MPI_COMM_WORLD, 10); + CHECK(memory(im, byte) > 0); +} + +TEMPLATE_TEST_CASE("memory-geometry", "[memory]", float, double) +{ + auto mesh = dolfinx::mesh::create_rectangle( + MPI_COMM_SELF, {{{0, 0}, {1, 1}}}, {1, 1}, + dolfinx::mesh::CellType::quadrilateral); + + const auto& geo = mesh.geometry(); + CHECK(memory>(geo, byte) > 0); +} diff --git a/python/dolfinx/wrappers/common.cpp b/python/dolfinx/wrappers/common.cpp index 54015f608a6..0241b708713 100644 --- a/python/dolfinx/wrappers/common.cpp +++ b/python/dolfinx/wrappers/common.cpp @@ -8,6 +8,7 @@ #include "dolfinx_wrappers/array.h" #include "dolfinx_wrappers/caster_mpi.h" #include +#include #include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -143,6 +145,21 @@ void common(nb::module_& m) m.attr("ufcx_signature") = dolfinx::ufcx_signature(); m.attr("version") = dolfinx::version(); + enum MemoryUnit : std::int64_t + { + byte = dolfinx::common::byte.value, + kilobyte = dolfinx::common::kilobyte.value, + megabyte = dolfinx::common::megabyte.value, + gigabyte = dolfinx::common::gigabyte.value, + terabyte = dolfinx::common::terabyte.value, + }; + nb::enum_(m, "MemoryUnit") + .value("byte", MemoryUnit::byte) + .value("kilobyte", MemoryUnit::kilobyte) + .value("megabyte", MemoryUnit::megabyte) + .value("gigabyte", MemoryUnit::gigabyte) + .value("terabyte", MemoryUnit::terabyte); + nb::enum_(m, "Reduction") .value("max", dolfinx::Table::Reduction::max) .value("min", dolfinx::Table::Reduction::min) @@ -246,7 +263,26 @@ void common(nb::module_& m) local); return dolfinx_wrappers::as_nbarray(std::move(local)); }, - nb::arg("global")); + nb::arg("global")) + .def("memory", + [](const dolfinx::common::IndexMap& self, MemoryUnit unit) + { + switch (unit) + { + case MemoryUnit::byte: + return double( + dolfinx::common::memory(self, dolfinx::common::byte)); + case MemoryUnit::kilobyte: + return dolfinx::common::memory(self, dolfinx::common::kilobyte); + case MemoryUnit::megabyte: + return dolfinx::common::memory(self, dolfinx::common::megabyte); + case MemoryUnit::gigabyte: + return dolfinx::common::memory(self, dolfinx::common::gigabyte); + case MemoryUnit::terabyte: + return dolfinx::common::memory(self, dolfinx::common::terabyte); + } + throw std::runtime_error("Not a valid memory unit."); + }); // dolfinx::common::Timer nb::class_>( diff --git a/python/test/unit/common/test_index_map.py b/python/test/unit/common/test_index_map.py index 5019a6a9fb9..406d9849d4f 100644 --- a/python/test/unit/common/test_index_map.py +++ b/python/test/unit/common/test_index_map.py @@ -42,6 +42,8 @@ def test_sub_index_map(): comm, map_local_size, [dest_ranks, src_ranks], map_ghosts, src_ranks ) assert map.size_global == map_local_size * comm.size + assert map.memory(dolfinx.cpp.common.MemoryUnit.byte) > 0 + assert map.memory(dolfinx.cpp.common.MemoryUnit.kilobyte) > 0 # Build list for each rank of the first (myrank + myrank % 2) local # indices