Skip to content
2 changes: 2 additions & 0 deletions cpp/dolfinx/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion cpp/dolfinx/common/IndexMap.cpp
Original file line number Diff line number Diff line change
@@ -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)
//
Expand Down Expand Up @@ -1374,3 +1374,18 @@ std::array<std::vector<int>, 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;
}
//-----------------------------------------------------------------------------
7 changes: 6 additions & 1 deletion cpp/dolfinx/common/IndexMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

#include "IndexMap.h"
#include "MPI.h"
#include "memory.h"
#include <cstdint>
#include <dolfinx/graph/AdjacencyList.h>
#include <memory>
#include <span>
#include <tuple>
#include <utility>
Expand Down Expand Up @@ -332,4 +332,9 @@ class IndexMap
std::vector<int> _dest;
};

namespace impl
{
std::size_t memory(const IndexMap& im);
}

} // namespace dolfinx::common
107 changes: 107 additions & 0 deletions cpp/dolfinx/common/memory.h
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <type_traits>
#include <vector>

#include <basix/mdspan.hpp>

#include "memory_fwd.h"
#include "types.h"

namespace dolfinx::common
{

namespace impl
{

template <typename T>
std::size_t memory(const T& /* obj */)
{
static_assert(false, "Memory usage not supported for provided type.");
return 0;
}

template <typename S, std::size_t N>
requires std::is_arithmetic_v<S>
std::size_t memory(const std::array<S, N>& array)
{
return sizeof(array);
}

template <typename S>
requires std::is_arithmetic_v<S>
std::size_t memory(const std::vector<S>& vec)
{
using value_type = typename std::vector<S>::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 <typename S>
requires std::is_arithmetic_v<S>
std::size_t memory(const std::span<const S>& span)
{
using value_type = typename std::vector<S>::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 <typename T>
std::size_t memory(const std::vector<std::vector<T>>& vec)
{
std::size_t size = sizeof(vec);
std::ranges::for_each(vec, [&](const auto& e) { size += memory(e); });
return size;
}

template <typename S, class Extents, class LayoutPolicy = md::layout_right,
class AccessorPolicy = md::default_accessor<S>>
requires std::is_arithmetic_v<S>
std::size_t
memory(const md::mdspan<S, Extents, LayoutPolicy, AccessorPolicy>& mdspan)
{
using value_type = typename std::mdspan<S, Extents, LayoutPolicy,
AccessorPolicy>::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<std::int64_t, 1> byte;
constexpr std::integral_constant<std::int64_t, 1'024> kilobyte;
constexpr std::integral_constant<std::int64_t, 1'048'576> megabyte;
constexpr std::integral_constant<std::int64_t, 1'073'741'824> gigabyte;
constexpr std::integral_constant<std::int64_t, 1'099'511'627'776> terabyte;

template <typename T, std::int64_t U = 1>
std::conditional_t<U == 1, std::size_t, double>
memory(const T& obj, std::integral_constant<std::int64_t, U> bytes_per_unit)
{
std::size_t bytes = impl::memory(obj);
if constexpr (bytes_per_unit == byte)
return bytes;
else
return static_cast<double>(bytes) / bytes_per_unit.value;
}

} // namespace dolfinx::common
33 changes: 33 additions & 0 deletions cpp/dolfinx/common/memory_fwd.h
Original file line number Diff line number Diff line change
@@ -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 <concepts>

namespace dolfinx
{

namespace common
{
class IndexMap;
}

namespace mesh
{
template <std::floating_point T>
class Geometry;
}

namespace common::impl
{
std::size_t memory(const IndexMap& im);

template <std::floating_point T>
std::size_t memory(const dolfinx::mesh::Geometry<T>& geometry);
} // namespace common::impl

} // namespace dolfinx
28 changes: 27 additions & 1 deletion cpp/dolfinx/mesh/Geometry.h
Original file line number Diff line number Diff line change
@@ -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)
//
Expand Down Expand Up @@ -328,3 +328,29 @@ create_geometry(const Topology& topology,
}

} // namespace dolfinx::mesh

namespace dolfinx::common::impl
{

template <std::floating_point T>
std::size_t memory(const dolfinx::mesh::Geometry<T>& 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
1 change: 1 addition & 0 deletions cpp/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
126 changes: 126 additions & 0 deletions cpp/test/common/memory.cpp
Original file line number Diff line number Diff line change
@@ -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 <catch2/catch_approx.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>

#include <mpi.h>
#include <vector>

#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<float>, std::complex<double>
{
std::array<TestType, 10> v;

std::size_t bytes = 10 * sizeof(TestType);

CHECK(memory(v, byte) == bytes);

CHECK(memory(v, kilobyte)
== Catch::Approx(static_cast<double>(bytes) / kilobyte));
CHECK(memory(v, megabyte)
== Catch::Approx(static_cast<double>(bytes) / megabyte));
CHECK(memory(v, gigabyte)
== Catch::Approx(static_cast<double>(bytes) / gigabyte));
CHECK(memory(v, terabyte)
== Catch::Approx(static_cast<double>(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<float>, std::complex<double>
{
std::vector<TestType> v;
v.reserve(10);

std::size_t bytes = sizeof(std::vector<TestType>) + 10 * sizeof(TestType);

CHECK(memory(v, byte) == bytes);

CHECK(memory(v, kilobyte)
== Catch::Approx(static_cast<double>(bytes) / kilobyte));
CHECK(memory(v, megabyte)
== Catch::Approx(static_cast<double>(bytes) / megabyte));
CHECK(memory(v, gigabyte)
== Catch::Approx(static_cast<double>(bytes) / gigabyte));
CHECK(memory(v, terabyte)
== Catch::Approx(static_cast<double>(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<float>, std::complex<double>
{
std::array<TestType, 10> 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<double>(bytes) / kilobyte));
CHECK(memory(v, megabyte)
== Catch::Approx(static_cast<double>(bytes) / megabyte));
CHECK(memory(v, gigabyte)
== Catch::Approx(static_cast<double>(bytes) / gigabyte));
CHECK(memory(v, terabyte)
== Catch::Approx(static_cast<double>(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<float>, std::complex<double>
{
std::vector<std::vector<TestType>> v;
v.reserve(3);
v.template emplace_back<std::vector<TestType>>({{0, 1, 2}});
v.template emplace_back<std::vector<TestType>>({{0, 1, 2, 3}});
v.template emplace_back<std::vector<TestType>>({{0, 1, 2, 3, 4}});

std::size_t bytes = sizeof(std::vector<std::vector<TestType>>)
+ 3 * sizeof(std::vector<TestType>)
+ (3 + 4 + 5) * sizeof(TestType);

CHECK(memory(v, byte) == bytes);

CHECK(memory(v, kilobyte)
== Catch::Approx(static_cast<double>(bytes) / kilobyte));
CHECK(memory(v, megabyte)
== Catch::Approx(static_cast<double>(bytes) / megabyte));
CHECK(memory(v, gigabyte)
== Catch::Approx(static_cast<double>(bytes) / gigabyte));
CHECK(memory(v, terabyte)
== Catch::Approx(static_cast<double>(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<TestType>(
MPI_COMM_SELF, {{{0, 0}, {1, 1}}}, {1, 1},
dolfinx::mesh::CellType::quadrilateral);

const auto& geo = mesh.geometry();
CHECK(memory<dolfinx::mesh::Geometry<TestType>>(geo, byte) > 0);
}
Loading
Loading