Skip to content

Commit

Permalink
Fix dirtyRecursive() performance for Series with many steps (#1598)
Browse files Browse the repository at this point in the history
* Don't check closed iterations if they are dirty

* WIP: Alternative way of checking for errors

* Re-enable some tests

* Clear dirty files after flushing in ADIOS2

* [WIP] Track dirtyRecursive from the beginning

* Continue fixing things

* Further fixes

* Test cleanup

* CI fixes

* Replace drop() with a condition check

* Move printDirty to debug::printDirty

* Update include/openPMD/RecordComponent.hpp

Co-authored-by: Axel Huebl <axel.huebl@plasma.ninja>

* Revert to older verification logic

It's more performant now with the new implementation for dirtyRecursive

* Document

* Remove stale changes

---------

Co-authored-by: Axel Huebl <axel.huebl@plasma.ninja>
  • Loading branch information
franzpoeschel and ax3l committed Mar 25, 2024
1 parent 5940f7c commit e4ce81f
Show file tree
Hide file tree
Showing 19 changed files with 248 additions and 153 deletions.
10 changes: 0 additions & 10 deletions include/openPMD/Iteration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -380,16 +380,6 @@ class Iteration : public Attributable
*/
void setStepStatus(StepStatus);

/*
* @brief Check recursively whether this Iteration is dirty.
* It is dirty if any attribute or dataset is read from or written to
* the backend.
*
* @return true If dirty.
* @return false Otherwise.
*/
bool dirtyRecursive() const;

/**
* @brief Link with parent.
*
Expand Down
10 changes: 0 additions & 10 deletions include/openPMD/ParticleSpecies.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,6 @@ class ParticleSpecies : public Container<Record>

void read();
void flush(std::string const &, internal::FlushParams const &) override;

/**
* @brief Check recursively whether this ParticleSpecies is dirty.
* It is dirty if any attribute or dataset is read from or written to
* the backend.
*
* @return true If dirty.
* @return false Otherwise.
*/
bool dirtyRecursive() const;
};

namespace traits
Expand Down
12 changes: 2 additions & 10 deletions include/openPMD/RecordComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ namespace internal
* Chunk reading/writing requests on the contained dataset.
*/
std::queue<IOTask> m_chunks;

void push_chunk(IOTask &&task);
/**
* Stores the value for constant record components.
* Ignored otherwise.
Expand Down Expand Up @@ -497,16 +499,6 @@ class RecordComponent : public BaseRecordComponent
void storeChunk(
auxiliary::WriteBuffer buffer, Datatype datatype, Offset o, Extent e);

/**
* @brief Check recursively whether this RecordComponent is dirty.
* It is dirty if any attribute or dataset is read from or written to
* the backend.
*
* @return true If dirty.
* @return false Otherwise.
*/
bool dirtyRecursive() const;

// clang-format off
OPENPMD_protected
// clang-format on
Expand Down
2 changes: 1 addition & 1 deletion include/openPMD/RecordComponent.tpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ RecordComponent::loadChunk(std::shared_ptr<T> data, Offset o, Extent e)
dRead.extent = extent;
dRead.dtype = getDatatype();
dRead.data = std::static_pointer_cast<void>(data);
rc.m_chunks.push(IOTask(this, dRead));
rc.push_chunk(IOTask(this, dRead));
}
}

Expand Down
5 changes: 5 additions & 0 deletions include/openPMD/Series.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,11 @@ OPENPMD_private
AbstractIOHandler *IOHandler();
AbstractIOHandler const *IOHandler() const;
}; // Series

namespace debug
{
void printDirty(Series const &);
}
} // namespace openPMD

// Make sure that this one is always included if Series.hpp is included,
Expand Down
55 changes: 51 additions & 4 deletions include/openPMD/backend/Attributable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,15 @@ namespace internal

template <typename, typename>
class BaseRecordData;

class RecordComponentData;
} // namespace internal

namespace debug
{
void printDirty(Series const &);
}

/** @brief Layer to manage storage of attributes associated with file objects.
*
* Mandatory and user-defined Attributes and their data for every object in the
Expand All @@ -109,6 +116,8 @@ class Attributable
friend class Series;
friend class Writable;
friend class WriteIterations;
friend class internal::RecordComponentData;
friend void debug::printDirty(Series const &);

protected:
// tag for internal constructor
Expand Down Expand Up @@ -378,11 +387,49 @@ OPENPMD_protected

bool dirty() const
{
return writable().dirty;
return writable().dirtySelf;
}
/** O(1).
*/
bool dirtyRecursive() const
{
return writable().dirtyRecursive;
}
void setDirty(bool dirty_in)
{
auto &w = writable();
w.dirtySelf = dirty_in;
setDirtyRecursive(dirty_in);
}
bool &dirty()
/* Amortized O(1) if dirty_in is true, else O(1).
*
* Must be used carefully with `dirty_in == false` since it is assumed that
* all children are not dirty.
*
* Invariant of dirtyRecursive:
* this->dirtyRecursive implies parent->dirtyRecursive.
*
* Hence:
*
* * If dirty_in is true: This needs only go up far enough until a parent is
* found that itself is dirtyRecursive.
* * If dirty_in is false: Only sets `this` to `dirtyRecursive == false`.
* The caller must ensure that the invariant holds (e.g. clearing
* everything during flushing or reading logic).
*/
void setDirtyRecursive(bool dirty_in)
{
return writable().dirty;
auto &w = writable();
w.dirtyRecursive = dirty_in;
if (dirty_in)
{
auto current = w.parent;
while (current && !current->dirtyRecursive)
{
current->dirtyRecursive = true;
current = current->parent;
}
}
}
bool written() const
{
Expand Down Expand Up @@ -417,7 +464,7 @@ inline bool Attributable::setAttribute(std::string const &key, T value)
error::throwNoSuchAttribute(out_of_range_msg(key));
}

dirty() = true;
setDirty(true);
auto it = attri.m_attributes.lower_bound(key);
if (it != attri.m_attributes.end() &&
!attri.m_attributes.key_comp()(key, it->first))
Expand Down
30 changes: 4 additions & 26 deletions include/openPMD/backend/BaseRecord.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -494,15 +494,6 @@ class BaseRecord
virtual void
flush_impl(std::string const &, internal::FlushParams const &) = 0;

/**
* @brief Check recursively whether this BaseRecord is dirty.
* It is dirty if any attribute or dataset is read from or written to
* the backend.
*
* @return true If dirty.
* @return false Otherwise.
*/
bool dirtyRecursive() const;
void eraseScalar();
}; // BaseRecord

Expand Down Expand Up @@ -999,25 +990,12 @@ inline void BaseRecord<T_elem>::flush(
}

this->flush_impl(name, flushParams);
// flush_impl must take care to correctly set the dirty() flag so this
// method doesn't do it
}

template <typename T_elem>
inline bool BaseRecord<T_elem>::dirtyRecursive() const
{
if (this->dirty())
if (flushParams.flushLevel != FlushLevel::SkeletonOnly)
{
return true;
this->setDirty(false);
}
for (auto const &pair : *this)
{
if (pair.second.dirtyRecursive())
{
return true;
}
}
return false;
// flush_impl must take care to correctly set the dirty() flag so this
// method doesn't do it
}

template <typename T_elem>
Expand Down
6 changes: 3 additions & 3 deletions include/openPMD/backend/PatchRecordComponent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ inline void PatchRecordComponent::load(std::shared_ptr<T> data)
dRead.dtype = getDatatype();
dRead.data = std::static_pointer_cast<void>(data);
auto &rc = get();
rc.m_chunks.push(IOTask(this, dRead));
rc.push_chunk(IOTask(this, dRead));
}

template <typename T>
Expand Down Expand Up @@ -182,7 +182,7 @@ inline void PatchRecordComponent::store(uint64_t idx, T data)
dWrite.dtype = dtype;
dWrite.data = std::make_shared<T>(data);
auto &rc = get();
rc.m_chunks.push(IOTask(this, std::move(dWrite)));
rc.push_chunk(IOTask(this, std::move(dWrite)));
}

template <typename T>
Expand Down Expand Up @@ -211,6 +211,6 @@ inline void PatchRecordComponent::store(T data)
dWrite.dtype = dtype;
dWrite.data = std::make_shared<T>(data);
auto &rc = get();
rc.m_chunks.push(IOTask(this, std::move(dWrite)));
rc.push_chunk(IOTask(this, std::move(dWrite)));
}
} // namespace openPMD
27 changes: 26 additions & 1 deletion include/openPMD/backend/Writable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ template <typename FilePositionType>
class AbstractIOHandlerImplCommon;
template <typename>
class Span;
class Series;

namespace internal
{
Expand All @@ -55,6 +56,11 @@ namespace detail
class ADIOS2File;
}

namespace debug
{
void printDirty(Series const &);
}

/** @brief Layer to mirror structure of logical data and persistent data in
* file.
*
Expand Down Expand Up @@ -94,6 +100,7 @@ class Writable final
friend std::string concrete_bp1_file_position(Writable *);
template <typename>
friend class Span;
friend void debug::printDirty(Series const &);

private:
Writable(internal::AttributableData *);
Expand Down Expand Up @@ -135,7 +142,25 @@ OPENPMD_private
IOHandler = nullptr;
internal::AttributableData *attributable = nullptr;
Writable *parent = nullptr;
bool dirty = true;

/** Tracks if there are unwritten changes for this specific Writable.
*
* Manipulate via Attributable::dirty() and Attributable::setDirty().
*/
bool dirtySelf = true;
/**
* Tracks if there are unwritten changes anywhere in the
* tree whose ancestor this Writable is.
*
* Invariant: this->dirtyRecursive implies parent->dirtyRecursive.
*
* dirtySelf and dirtyRecursive are separated since that allows specifying
* that `this` is not dirty, but some child is.
*
* Manipulate via Attributable::dirtyRecursive() and
* Attributable::setDirtyRecursive().
*/
bool dirtyRecursive = true;
/**
* If parent is not null, then this is a key such that:
* &(*parent)[key] == this
Expand Down
8 changes: 7 additions & 1 deletion src/IO/ADIOS/ADIOS2File.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

#include "openPMD/IO/ADIOS/ADIOS2File.hpp"
#include "openPMD/Error.hpp"
#include "openPMD/IO/ADIOS/ADIOS2IOHandler.hpp"
#include "openPMD/auxiliary/Environment.hpp"

Expand Down Expand Up @@ -1188,7 +1189,12 @@ AdvanceStatus ADIOS2File::advance(AdvanceMode mode)

void ADIOS2File::drop()
{
m_buffer.clear();
if (!m_buffer.empty())
{
throw error::Internal(
"ADIOS2 backend: File data for '" + m_file +
"' dropped, but there were enqueued operations.");
}
}

static std::vector<std::string> availableAttributesOrVariablesPrefixed(
Expand Down
1 change: 1 addition & 0 deletions src/IO/ADIOS/ADIOS2IOHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ ADIOS2IOHandlerImpl::flush(internal::ParsedFlushParams &flushParams)
p.second->drop();
}
}
m_dirty.clear();
return res;
}

Expand Down

0 comments on commit e4ce81f

Please sign in to comment.