From 638655a254db067a4e6a22f1fa235c164fab53dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Tue, 14 Oct 2025 16:04:14 +0200 Subject: [PATCH 1/4] Shortcut zero-size storeChunk calls without breaking collectivity --- include/openPMD/RecordComponent.tpp | 13 ++++--------- src/IO/ADIOS/ADIOS2IOHandler.cpp | 11 +++++++++++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp index 2796ed1ae3..9a65be8412 100644 --- a/include/openPMD/RecordComponent.tpp +++ b/include/openPMD/RecordComponent.tpp @@ -115,14 +115,6 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer) IOHandler()->enqueue(IOTask(this, dCreate)); } - if (size == 0) - { - // Don't forward this operation to the backend as it might create ugly - // zero-blocks in ADIOS2 - setDirtyRecursive(true); - return DynamicMemoryView(); - } - Parameter getBufferView; getBufferView.offset = o; getBufferView.extent = e; @@ -136,7 +128,10 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer) // type shared_ptr or shared_ptr auto data = std::forward(createBuffer)(size); out.ptr = static_cast(data.get()); - storeChunk(std::move(data), std::move(o), std::move(e)); + if (size > 0) + { + storeChunk(std::move(data), std::move(o), std::move(e)); + } } setDirtyRecursive(true); return DynamicMemoryView{std::move(getBufferView), size, *this}; diff --git a/src/IO/ADIOS/ADIOS2IOHandler.cpp b/src/IO/ADIOS/ADIOS2IOHandler.cpp index 884a4b6341..5d3ce17f36 100644 --- a/src/IO/ADIOS/ADIOS2IOHandler.cpp +++ b/src/IO/ADIOS/ADIOS2IOHandler.cpp @@ -1299,6 +1299,17 @@ void ADIOS2IOHandlerImpl::getBufferView( auto file = refreshFileFromParent(writable, /* preferParentFile = */ false); detail::ADIOS2File &ba = getFileData(file, IfFileNotOpen::ThrowError); + if (std::any_of( + parameters.extent.begin(), parameters.extent.end(), [](auto val) { + return val == 0; + })) + { + // Refuse empty operations, ADIOS2 creates ugly zero blocks for them, + // tell the frontend to do sth about it instead + parameters.out->backendManagedBuffer = false; + return; + } + std::string name = nameOfVariable(writable); switch (m_useSpanBasedPutByDefault) { From ca08ee3e1f544409f9734e7230d4e3160ed785e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Tue, 14 Oct 2025 16:09:18 +0200 Subject: [PATCH 2/4] Fix the documentation --- docs/source/details/mpi.rst | 3 +++ include/openPMD/RecordComponent.hpp | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/source/details/mpi.rst b/docs/source/details/mpi.rst index 38bdc2643d..1f0cd36ca4 100644 --- a/docs/source/details/mpi.rst +++ b/docs/source/details/mpi.rst @@ -30,6 +30,7 @@ Functionality Behavior Description ``::resetDataset`` [1]_ [2]_ *backend-specific* declare, write ``::makeConstant`` [3]_ *backend-specific* declare, write ``::storeChunk`` [1]_ independent write +``::storeChunk`` [5]_ collective Span-based overloads ``::loadChunk`` independent read ``::availableChunks`` [4]_ collective read, immediate result ============================ ================== ================================ @@ -49,6 +50,8 @@ Functionality Behavior Description .. [4] We usually open iterations delayed on first access. This first access is usually the ``flush()`` call after a ``storeChunk``/``loadChunk`` operation. If the first access is non-collective, an explicit, collective ``Iteration::open()`` can be used to have the files already open. Alternatively, iterations might be accessed for the first time by immediate operations such as ``::availableChunks()``. +.. [5] The Span-based ``storeChunk`` API calls return a backend-allocated pointer, requiring flush operations. These API calls hence inherit the collective properties of flushing. Use store calls with zero-size extent if a rank does not contribute anything. + .. warning:: The openPMD-api will by default flush only those Iterations which are dirty, i.e. have been written to. diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index 0c9ead0263..2a23c719ce 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -417,6 +417,11 @@ class RecordComponent : public BaseRecordComponent * @brief Overload of storeChunk() that lets the openPMD API allocate * a buffer. * + * Collective call: Unlike most other storeChunk() calls, this call returns + * pointers allocated by the IO backends and hence inherits the collective + * properties of flushing. Apply zero-extent storeChunk() calls from ranks + * that do not contribute anything. + * * This may save memory if the openPMD backend in use is able to provide * users a view into its own buffers, avoiding the need to allocate * a new buffer. @@ -453,6 +458,11 @@ class RecordComponent : public BaseRecordComponent /** * Overload of span-based storeChunk() that uses operator new() to create * a buffer. + * + * Collective call: Unlike most other storeChunk() calls, this call returns + * pointers allocated by the IO backends and hence inherits the collective + * properties of flushing. Apply zero-extent storeChunk() calls from ranks + * that do not contribute anything. */ template DynamicMemoryView storeChunk(Offset, Extent); From da3a7e97c3fd5b2b584524fe3a13c8d704457066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Wed, 15 Oct 2025 12:47:01 +0200 Subject: [PATCH 3/4] Fix: Iteration::open() should access files --- include/openPMD/RecordComponent.hpp | 11 ----------- include/openPMD/RecordComponent.tpp | 3 ++- include/openPMD/backend/Writable.hpp | 1 + src/Iteration.cpp | 13 +++++++++++-- src/RecordComponent.cpp | 1 - 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index 2a23c719ce..fbb898e9fb 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -81,16 +81,6 @@ namespace internal * Ignored otherwise. */ Attribute m_constantValue{-1}; - /** - * The same std::string that the parent class would pass as parameter to - * RecordComponent::flush(). - * This is stored only upon RecordComponent::flush() if - * AbstractIOHandler::flushLevel is set to FlushLevel::SkeletonOnly - * (for use by the Span-based overload of - * RecordComponent::storeChunk()). - * @todo Merge functionality with ownKeyInParent? - */ - std::string m_name; /** * True if this component is an empty dataset, i.e. its extent is zero * in at least one dimension. @@ -109,7 +99,6 @@ namespace internal BaseRecordComponentData::reset(); m_chunks = std::queue(); m_constantValue = -1; - m_name = std::string(); m_isEmpty = false; m_hasBeenExtended = false; } diff --git a/include/openPMD/RecordComponent.tpp b/include/openPMD/RecordComponent.tpp index 9a65be8412..c358e56d6d 100644 --- a/include/openPMD/RecordComponent.tpp +++ b/include/openPMD/RecordComponent.tpp @@ -29,6 +29,7 @@ #include "openPMD/auxiliary/ShareRawInternal.hpp" #include "openPMD/auxiliary/TypeTraits.hpp" #include "openPMD/auxiliary/UniquePtr.hpp" +#include "openPMD/backend/Attributable.hpp" #include #include @@ -111,7 +112,7 @@ RecordComponent::storeChunk(Offset o, Extent e, F &&createBuffer) "using storeChunk() (see RecordComponent::resetDataset())."); } Parameter dCreate(rc.m_dataset.value()); - dCreate.name = rc.m_name; + dCreate.name = Attributable::get().m_writable.ownKeyWithinParent; IOHandler()->enqueue(IOTask(this, dCreate)); } diff --git a/include/openPMD/backend/Writable.hpp b/include/openPMD/backend/Writable.hpp index bfb9c67e03..2d29bac983 100644 --- a/include/openPMD/backend/Writable.hpp +++ b/include/openPMD/backend/Writable.hpp @@ -89,6 +89,7 @@ class Writable final friend class ParticleSpecies; friend class Series; friend class Record; + friend class RecordComponent; friend class AbstractIOHandlerImpl; friend class ADIOS2IOHandlerImpl; friend class detail::ADIOS2File; diff --git a/src/Iteration.cpp b/src/Iteration.cpp index 35fbf15247..d50d7d6a2f 100644 --- a/src/Iteration.cpp +++ b/src/Iteration.cpp @@ -161,12 +161,21 @@ Iteration &Iteration::open() it.m_closed = CloseStatus::Open; runDeferredParseAccess(); } - if (getStepStatus() == StepStatus::OutOfStep) + switch (getStepStatus()) { + case StepStatus::OutOfStep: beginStep(/* reread = */ false); setStepStatus(StepStatus::DuringStep); + break; + case StepStatus::DuringStep: + case StepStatus::NoStep: { + auto end = begin; + ++end; + s.flush_impl(begin, end, {FlushLevel::CreateOrOpenFiles}); } - IOHandler()->flush(internal::defaultFlushParams); + break; + } + // IOHandler()->flush(internal::defaultFlushParams); return *this; } diff --git a/src/RecordComponent.cpp b/src/RecordComponent.cpp index f2a67a2f60..6d11fbfa77 100644 --- a/src/RecordComponent.cpp +++ b/src/RecordComponent.cpp @@ -262,7 +262,6 @@ void RecordComponent::flush( auto &rc = get(); if (flushParams.flushLevel == FlushLevel::SkeletonOnly) { - rc.m_name = name; return; } if (access::readOnly(IOHandler()->m_frontendAccess)) From 58c08ea272c43e838650a5f6eab0c74a9a49f397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Wed, 15 Oct 2025 12:49:34 +0200 Subject: [PATCH 4/4] Revert "Fix the documentation" This reverts commit dfc8ceab238a016fe2bcd6642bc54f83be32bb00. --- docs/source/details/mpi.rst | 3 --- include/openPMD/RecordComponent.hpp | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/docs/source/details/mpi.rst b/docs/source/details/mpi.rst index 1f0cd36ca4..38bdc2643d 100644 --- a/docs/source/details/mpi.rst +++ b/docs/source/details/mpi.rst @@ -30,7 +30,6 @@ Functionality Behavior Description ``::resetDataset`` [1]_ [2]_ *backend-specific* declare, write ``::makeConstant`` [3]_ *backend-specific* declare, write ``::storeChunk`` [1]_ independent write -``::storeChunk`` [5]_ collective Span-based overloads ``::loadChunk`` independent read ``::availableChunks`` [4]_ collective read, immediate result ============================ ================== ================================ @@ -50,8 +49,6 @@ Functionality Behavior Description .. [4] We usually open iterations delayed on first access. This first access is usually the ``flush()`` call after a ``storeChunk``/``loadChunk`` operation. If the first access is non-collective, an explicit, collective ``Iteration::open()`` can be used to have the files already open. Alternatively, iterations might be accessed for the first time by immediate operations such as ``::availableChunks()``. -.. [5] The Span-based ``storeChunk`` API calls return a backend-allocated pointer, requiring flush operations. These API calls hence inherit the collective properties of flushing. Use store calls with zero-size extent if a rank does not contribute anything. - .. warning:: The openPMD-api will by default flush only those Iterations which are dirty, i.e. have been written to. diff --git a/include/openPMD/RecordComponent.hpp b/include/openPMD/RecordComponent.hpp index fbb898e9fb..f319e10cff 100644 --- a/include/openPMD/RecordComponent.hpp +++ b/include/openPMD/RecordComponent.hpp @@ -406,11 +406,6 @@ class RecordComponent : public BaseRecordComponent * @brief Overload of storeChunk() that lets the openPMD API allocate * a buffer. * - * Collective call: Unlike most other storeChunk() calls, this call returns - * pointers allocated by the IO backends and hence inherits the collective - * properties of flushing. Apply zero-extent storeChunk() calls from ranks - * that do not contribute anything. - * * This may save memory if the openPMD backend in use is able to provide * users a view into its own buffers, avoiding the need to allocate * a new buffer. @@ -447,11 +442,6 @@ class RecordComponent : public BaseRecordComponent /** * Overload of span-based storeChunk() that uses operator new() to create * a buffer. - * - * Collective call: Unlike most other storeChunk() calls, this call returns - * pointers allocated by the IO backends and hence inherits the collective - * properties of flushing. Apply zero-extent storeChunk() calls from ranks - * that do not contribute anything. */ template DynamicMemoryView storeChunk(Offset, Extent);