diff --git a/.github/workflows/unix.yml b/.github/workflows/unix.yml index 299c0e7dff..91f4801480 100644 --- a/.github/workflows/unix.yml +++ b/.github/workflows/unix.yml @@ -78,6 +78,30 @@ jobs: export OPENPMD_BP_BACKEND=ADIOS1 ctest --output-on-failure + clang5_nopy_ompi_h5_ad2_newLayout_bp3: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: Spack Cache + uses: actions/cache@v2 + with: {path: /opt/spack, key: clang5_nopy_ompi_h5_ad1_ad2_bp3 } + - name: Install + run: | + sudo apt-get install clang-5.0 gfortran libopenmpi-dev python3 + sudo .github/workflows/dependencies/install_spack + - name: Build + env: {CC: clang-5.0, CXX: clang++-5.0, CXXFLAGS: -Werror -Wno-deprecated-declarations} + run: | + eval $(spack env activate --sh .github/ci/spack-envs/clang5_nopy_ompi_h5_ad1_ad2_bp3/) + spack install + + mkdir build && cd build + ../share/openPMD/download_samples.sh && chmod u-w samples/git-sample/*.h5 + cmake -S .. -B . -DopenPMD_USE_PYTHON=OFF -DopenPMD_USE_MPI=ON -DopenPMD_USE_HDF5=ON -DopenPMD_USE_ADIOS1=OFF -DopenPMD_USE_ADIOS2=ON -DopenPMD_USE_INVASIVE_TESTS=ON + cmake --build . --parallel 2 + export OPENPMD_NEW_ATTRIBUTE_LAYOUT=1 + ctest --output-on-failure + # TODO # clang6_py36_nompi_h5_ad1_ad2_libcpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 796e0addfb..ac1b0cfa06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -370,6 +370,7 @@ set(IO_SOURCE src/IO/JSON/JSONFilePosition.cpp src/IO/ADIOS/ADIOS2IOHandler.cpp src/IO/ADIOS/ADIOS2Auxiliary.cpp + src/IO/ADIOS/ADIOS2PreloadAttributes.cpp src/IO/InvalidatableFile.cpp) set(IO_ADIOS1_SEQUENTIAL_SOURCE src/auxiliary/Filesystem.cpp diff --git a/docs/source/backends/adios2.rst b/docs/source/backends/adios2.rst index ec4699a639..228a1f1383 100644 --- a/docs/source/backends/adios2.rst +++ b/docs/source/backends/adios2.rst @@ -75,6 +75,29 @@ A good number for substreams is usually the number of contributing nodes divided For fine-tuning at extreme scale or for exotic systems, please refer to the ADIOS2 manual and talk to your filesystem admins and the ADIOS2 authors. Be aware that extreme-scale I/O is a research topic after all. +Experimental new attribute layout +--------------------------------- + +We are experimenting with a breaking change to our layout of openPMD datasets in ADIOS2. +It is likely that we will in future use ADIOS attributes only for a handful of internal flags. +Actual openPMD attributes will be modeled by ADIOS variables of the same name. +In order to distinguish datasets from attributes, datasets will be suffixed by ``/__data__``. + +We hope that this will bring several advantages: + +* Unlike ADIOS attributes, ADIOS variables are mutable. +* ADIOS variables are more closely related to the concept of ADIOS steps. + An ADIOS variable that is not written to in one step is not seen by the reader. + This will bring more manageable amounts of metadata for readers to parse through. + +The new layout may be activated **for experimental purposes** in two ways: + +* Via the JSON parameter ``adios2.new_attribute_layout = true``. +* Via the environment variable ``export OPENPMD_NEW_ATTRIBUTE_LAYOUT=1``. + +The classical and the new layout are absolutely incompatible with one another. +The ADIOS2 backend will **not** (yet) automatically recognize the layout that has been used by a writer when reading a dataset. + Selected References ------------------- diff --git a/include/openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp b/include/openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp index c93b15ce28..eae572a7a5 100644 --- a/include/openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp +++ b/include/openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp @@ -23,12 +23,15 @@ #include "openPMD/config.hpp" #if openPMD_HAVE_ADIOS2 -#include "openPMD/Datatype.hpp" -#include -#include -#include -#include -#include +# include "openPMD/Dataset.hpp" +# include "openPMD/Datatype.hpp" + +# include + +# include +# include +# include +# include namespace openPMD { @@ -75,16 +78,25 @@ namespace detail */ Datatype fromADIOS2Type( std::string const & dt ); + enum class VariableOrAttribute : unsigned char + { + Variable, + Attribute + }; + template < typename T > struct AttributeInfoHelper { - static typename std::vector< T >::size_type - getSize( adios2::IO &, std::string const & attributeName ); + static Extent + getSize( + adios2::IO &, + std::string const & attributeName, + VariableOrAttribute ); }; template < > struct AttributeInfoHelper< std::complex< long double > > { - static typename std::vector< long double >::size_type - getSize( adios2::IO &, std::string const & ) + static Extent + getSize( adios2::IO &, std::string const &, VariableOrAttribute ) { throw std::runtime_error( "[ADIOS2] Internal error: no support for long double complex attribute types" ); @@ -93,14 +105,17 @@ namespace detail template < typename T > struct AttributeInfoHelper< std::vector< T > > { - static typename std::vector< T >::size_type - getSize( adios2::IO &, std::string const & attributeName ); + static Extent + getSize( + adios2::IO &, + std::string const & attributeName, + VariableOrAttribute ); }; template < > struct AttributeInfoHelper< std::vector< std::complex< long double > > > { - static typename std::vector< std::complex< long double > >::size_type - getSize( adios2::IO &, std::string const & ) + static Extent + getSize( adios2::IO &, std::string const &, VariableOrAttribute ) { throw std::runtime_error( "[ADIOS2] Internal error: no support for long double complex vector attribute types" ); @@ -110,27 +125,33 @@ namespace detail template < typename T, std::size_t n > struct AttributeInfoHelper< std::array< T, n > > { - static typename std::vector< T >::size_type - getSize( adios2::IO & IO, std::string const & attributeName ) + static Extent + getSize( + adios2::IO & IO, + std::string const & attributeName, + VariableOrAttribute voa ) { - return AttributeInfoHelper< T >::getSize( IO, attributeName ); + return AttributeInfoHelper< T >::getSize( IO, attributeName, voa ); } }; template <> struct AttributeInfoHelper< bool > { - static typename std::vector< bool_representation >::size_type - getSize( adios2::IO &, std::string const & attributeName ); + static Extent + getSize( + adios2::IO &, + std::string const & attributeName, + VariableOrAttribute ); }; struct AttributeInfo { - template < typename T > - typename std::vector< T >::size_type - operator( )( adios2::IO &, std::string const & attributeName ); + template< typename T, typename... Params > + Extent + operator()( Params &&... ); template < int n, typename... Params > - size_t operator( )( Params &&... ); + Extent operator( )( Params &&... ); }; /** @@ -146,7 +167,44 @@ namespace detail attributeInfo( adios2::IO & IO, std::string const & attributeName, - bool verbose ); + bool verbose, + VariableOrAttribute = VariableOrAttribute::Attribute ); + + template< typename T > + struct IsTrivialType + { + constexpr static bool val = true; + }; + + template< typename T > + struct IsTrivialType< std::vector< T > > + { + constexpr static bool val = false; + }; + + template< typename T, size_t n > + struct IsTrivialType< std::array< T, n > > + { + constexpr static bool val = false; + }; + + template<> + struct IsTrivialType< bool > + { + constexpr static bool val = false; + }; + + template<> + struct IsTrivialType< std::complex< long double > > + { + constexpr static bool val = false; + }; + + template<> + struct IsTrivialType< std::vector< std::complex< long double > > > + { + constexpr static bool val = false; + }; } // namespace detail } // namespace openPMD diff --git a/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp b/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp index 06276b6fb2..55795578a9 100644 --- a/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp +++ b/include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp @@ -25,6 +25,7 @@ #include "openPMD/IO/AbstractIOHandlerImplCommon.hpp" #include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp" #include "openPMD/IO/ADIOS/ADIOS2FilePosition.hpp" +#include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp" #include "openPMD/IO/IOTask.hpp" #include "openPMD/IO/InvalidatableFile.hpp" #include "openPMD/auxiliary/JSON.hpp" @@ -64,6 +65,8 @@ namespace detail struct DatasetReader; struct AttributeReader; struct AttributeWriter; + struct OldAttributeReader; + struct OldAttributeWriter; template < typename > struct AttributeTypes; struct DatasetOpener; template < typename > struct DatasetTypes; @@ -72,6 +75,7 @@ namespace detail struct BufferedPut; struct BufferedGet; struct BufferedAttributeRead; + struct BufferedAttributeWrite; } // namespace detail @@ -82,6 +86,8 @@ class ADIOS2IOHandlerImpl friend struct detail::DatasetReader; friend struct detail::AttributeReader; friend struct detail::AttributeWriter; + friend struct detail::OldAttributeReader; + friend struct detail::OldAttributeWriter; template < typename > friend struct detail::AttributeTypes; friend struct detail::DatasetOpener; template < typename > friend struct detail::DatasetTypes; @@ -197,6 +203,14 @@ class ADIOS2IOHandlerImpl */ std::string m_engineType; + enum class AttributeLayout : char + { + ByAdiosAttributes, + ByAdiosVariables + }; + + AttributeLayout m_attributeLayout = AttributeLayout::ByAdiosAttributes; + struct ParameterizedOperator { adios2::Operator op; @@ -364,11 +378,44 @@ namespace detail template < int T, typename... Params > void operator( )( Params &&... ); }; + struct OldAttributeReader + { + template< typename T > + Datatype + operator()( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ); + + template< int n, typename... Params > + Datatype + operator()( Params &&... ); + }; + + struct OldAttributeWriter + { + template< typename T > + void + operator()( + ADIOS2IOHandlerImpl * impl, + Writable * writable, + const Parameter< Operation::WRITE_ATT > & parameters ); + + + template< int n, typename... Params > + void + operator()( Params &&... ); + }; + struct AttributeReader { - template < typename T > - Datatype operator( )( adios2::IO & IO, std::string name, - std::shared_ptr< Attribute::resource > resource ); + template< typename T > + Datatype + operator()( + adios2::IO & IO, + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ); template < int n, typename... Params > Datatype operator( )( Params &&... ); @@ -378,8 +425,9 @@ namespace detail { template < typename T > void - operator( )( ADIOS2IOHandlerImpl * impl, Writable * writable, - const Parameter< Operation::WRITE_ATT > & parameters ); + operator()( + detail::BufferedAttributeWrite & params, + BufferedActions & fileData ); template < int n, typename... Params > void operator( )( Params &&... ); @@ -444,31 +492,47 @@ namespace detail * for vector and array types, as well as the boolean * type (which is not natively supported by ADIOS). */ - template < typename T > struct AttributeTypes + template< typename T > + struct AttributeTypes { - using Attr = adios2::Attribute< T >; - using BasicType = T; + static void + oldCreateAttribute( + adios2::IO & IO, + std::string name, + T value ); - static Attr createAttribute( adios2::IO & IO, std::string name, - BasicType value ); + static void + oldReadAttribute( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ); static void - readAttribute( adios2::IO & IO, std::string name, - std::shared_ptr< Attribute::resource > resource ); + createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + T value ); + + static void + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string name, + std::shared_ptr< Attribute::resource > resource ); /** * @brief Is the attribute given by parameters name and val already * defined exactly in that way within the given IO? */ static bool - attributeUnchanged( adios2::IO & IO, std::string name, BasicType val ) + attributeUnchanged( adios2::IO & IO, std::string name, T val ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< T >( name ); if( !attr ) { return false; } - std::vector< BasicType > data = attr.Data(); + std::vector< T > data = attr.Data(); if( data.size() != 1 ) { return false; @@ -479,19 +543,42 @@ namespace detail template< > struct AttributeTypes< std::complex< long double > > { - using Attr = adios2::Attribute< std::complex< double > >; - using BasicType = double; + static void + oldCreateAttribute( + adios2::IO &, + std::string, + std::complex< long double > ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: no support for long double complex attribute types" ); + } + + static void + oldReadAttribute( + adios2::IO &, + std::string, + std::shared_ptr< Attribute::resource > ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: no support for long double complex attribute types" ); + } - static Attr createAttribute( adios2::IO &, std::string, - std::complex< long double > ) + static void + createAttribute( + adios2::IO &, + adios2::Engine &, + detail::BufferedAttributeWrite &, + std::complex< long double > ) { throw std::runtime_error( "[ADIOS2] Internal error: no support for long double complex attribute types" ); } static void - readAttribute( adios2::IO &, std::string, - std::shared_ptr< Attribute::resource > ) + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string, + std::shared_ptr< Attribute::resource > ) { throw std::runtime_error( "[ADIOS2] Internal error: no support for long double complex attribute types" ); @@ -508,19 +595,42 @@ namespace detail template< > struct AttributeTypes< std::vector< std::complex< long double > > > { - using Attr = adios2::Attribute< std::complex< double > >; - using BasicType = double; + static void + oldCreateAttribute( + adios2::IO &, + std::string, + const std::vector< std::complex< long double > > & ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: no support for long double complex vector attribute types" ); + } - static Attr createAttribute( adios2::IO &, std::string, - const std::vector< std::complex< long double > > & ) + static void + oldReadAttribute( + adios2::IO &, + std::string, + std::shared_ptr< Attribute::resource > ) { throw std::runtime_error( "[ADIOS2] Internal error: no support for long double complex vector attribute types" ); } static void - readAttribute( adios2::IO &, std::string, - std::shared_ptr< Attribute::resource > ) + createAttribute( + adios2::IO &, + adios2::Engine &, + detail::BufferedAttributeWrite &, + const std::vector< std::complex< long double > > & ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: no support for long double complex vector attribute types" ); + } + + static void + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string, + std::shared_ptr< Attribute::resource > ) { throw std::runtime_error( "[ADIOS2] Internal error: no support for long double complex vector attribute types" ); @@ -539,15 +649,30 @@ namespace detail template < typename T > struct AttributeTypes< std::vector< T > > { - using Attr = adios2::Attribute< T >; - using BasicType = T; + static void + oldCreateAttribute( + adios2::IO & IO, + std::string name, + const std::vector< T > & value ); + + static void + oldReadAttribute( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ); - static Attr createAttribute( adios2::IO & IO, std::string name, - const std::vector< T > & value ); + static void + createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const std::vector< T > & value ); static void - readAttribute( adios2::IO & IO, std::string name, - std::shared_ptr< Attribute::resource > resource ); + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string name, + std::shared_ptr< Attribute::resource > resource ); static bool attributeUnchanged( @@ -555,12 +680,67 @@ namespace detail std::string name, std::vector< T > val ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< T >( name ); + if( !attr ) + { + return false; + } + std::vector< T > data = attr.Data(); + if( data.size() != val.size() ) + { + return false; + } + for( size_t i = 0; i < val.size(); ++i ) + { + if( data[ i ] != val[ i ] ) + { + return false; + } + } + return true; + } + }; + + template<> + struct AttributeTypes< std::vector< std::string > > + { + static void + oldCreateAttribute( + adios2::IO & IO, + std::string name, + const std::vector< std::string > & value ); + + static void + oldReadAttribute( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ); + + static void + createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const std::vector< std::string > & vec ); + + static void + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string name, + std::shared_ptr< Attribute::resource > resource ); + + static bool + attributeUnchanged( + adios2::IO & IO, + std::string name, + std::vector< std::string > val ) + { + auto attr = IO.InquireAttribute< std::string >( name ); if( !attr ) { return false; } - std::vector< BasicType > data = attr.Data(); + std::vector< std::string > data = attr.Data(); if( data.size() != val.size() ) { return false; @@ -579,15 +759,30 @@ namespace detail template < typename T, size_t n > struct AttributeTypes< std::array< T, n > > { - using Attr = adios2::Attribute< T >; - using BasicType = T; + static void + oldCreateAttribute( + adios2::IO & IO, + std::string name, + const std::array< T, n > & value ); + + static void + oldReadAttribute( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ); - static Attr createAttribute( adios2::IO & IO, std::string name, - const std::array< T, n > & value ); + static void + createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const std::array< T, n > & value ); static void - readAttribute( adios2::IO & IO, std::string name, - std::shared_ptr< Attribute::resource > resource ); + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string name, + std::shared_ptr< Attribute::resource > resource ); static bool attributeUnchanged( @@ -595,12 +790,12 @@ namespace detail std::string name, std::array< T, n > val ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< T >( name ); if( !attr ) { return false; } - std::vector< BasicType > data = attr.Data(); + std::vector< T > data = attr.Data(); if( data.size() != n ) { return false; @@ -619,15 +814,28 @@ namespace detail template <> struct AttributeTypes< bool > { using rep = detail::bool_representation; - using Attr = adios2::Attribute< rep >; - using BasicType = rep; - static Attr createAttribute( adios2::IO & IO, std::string name, - bool value ); + static void + oldCreateAttribute( adios2::IO & IO, std::string name, bool value ); static void - readAttribute( adios2::IO & IO, std::string name, - std::shared_ptr< Attribute::resource > resource ); + oldReadAttribute( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ); + + static void + createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + bool value ); + + static void + readAttribute( + detail::PreloadAdiosAttributes const &, + std::string name, + std::shared_ptr< Attribute::resource > resource ); static constexpr rep toRep( bool b ) @@ -644,12 +852,12 @@ namespace detail static bool attributeUnchanged( adios2::IO & IO, std::string name, bool val ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< rep >( name ); if( !attr ) { return false; } - std::vector< BasicType > data = attr.Data(); + std::vector< rep > data = attr.Data(); if( data.size() != 1 ) { return false; @@ -802,7 +1010,7 @@ namespace detail void run( BufferedActions & ) override; }; - struct BufferedAttributeRead : BufferedAction + struct OldBufferedAttributeRead : BufferedAction { Parameter< Operation::READ_ATT > param; std::string name; @@ -810,6 +1018,26 @@ namespace detail void run( BufferedActions & ) override; }; + struct BufferedAttributeRead + { + Parameter< Operation::READ_ATT > param; + std::string name; + + void + run( BufferedActions & ); + }; + + struct BufferedAttributeWrite + { + std::string name; + Datatype dtype; + Attribute::resource resource; + std::vector< char > bufferForVecString; + + void + run( BufferedActions & ); + }; + /* * Manages per-file information about * (1) the file's IO and Engine objects @@ -849,10 +1077,15 @@ namespace detail adios2::ADIOS & m_ADIOS; adios2::IO m_IO; std::vector< std::unique_ptr< BufferedAction > > m_buffer; + std::map< std::string, BufferedAttributeWrite > m_attributeWrites; + std::vector< BufferedAttributeRead > m_attributeReads; adios2::Mode m_mode; detail::WriteDataset const m_writeDataset; detail::DatasetReader const m_readDataset; detail::AttributeReader const m_attributeReader; + PreloadAdiosAttributes preloadAttributes; + using AttributeLayout = ADIOS2IOHandlerImpl::AttributeLayout; + AttributeLayout m_attributeLayout = AttributeLayout::ByAdiosAttributes; /* * We call an attribute committed if the step during which it was @@ -903,12 +1136,17 @@ namespace detail * * adios2::Engine::EndStep * * adios2::Engine::Perform(Puts|Gets) * * adios2::Engine::Close + * @param writeAttributes If using the new attribute layout, perform + * deferred attribute writes now. * @param flushUnconditionally Whether to run the functor even if no * deferred IO tasks had been queued. */ template< typename F > void - flush( F && performPutsGets, bool flushUnconditionally ); + flush( + F && performPutsGets, + bool writeAttributes, + bool flushUnconditionally ); /** * Overload of flush() that uses adios2::Engine::Perform(Puts|Gets) @@ -916,7 +1154,7 @@ namespace detail * */ void - flush(); + flush( bool writeAttributes = false ); /** * @brief Begin or end an ADIOS step. diff --git a/include/openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp b/include/openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp new file mode 100644 index 0000000000..99bc4c03d3 --- /dev/null +++ b/include/openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp @@ -0,0 +1,164 @@ +/* Copyright 2020 Franz Poeschel + * + * This file is part of openPMD-api. + * + * openPMD-api is free software: you can redistribute it and/or modify + * it under the terms of of either the GNU General Public License or + * the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * openPMD-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License and the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * and the GNU Lesser General Public License along with openPMD-api. + * If not, see . + */ +#pragma once + +#include "openPMD/config.hpp" +#if openPMD_HAVE_ADIOS2 + +#include +#include +#include +#include + +#include "openPMD/Datatype.hpp" + +namespace openPMD +{ +namespace detail +{ + /** + * @brief Pointer to an attribute's data along with its shape. + * + * @tparam T Underlying attribute data type. + */ + template< typename T > + struct AttributeWithShape + { + adios2::Dims shape; + T const * data; + }; + + /** + * Class that is responsible for scheduling and buffering openPMD attribute + * loads from ADIOS2, if using ADIOS variables to store openPMD attributes. + * + * Reasoning: ADIOS variables can be of any shape and size, and ADIOS cannot + * know which variables to buffer. While it will preload and buffer scalar + * variables, openPMD also stores vector-type attributes which are not + * preloaded. Since in Streaming setups, every variable load requires full + * communication back to the writer, this can quickly become very expensive. + * Hence, do this manually. + * + */ + class PreloadAdiosAttributes + { + public: + /** + * Internally used struct to store meta information on a buffered + * attribute. Public for simplicity (helper struct in the + * implementation uses it). + */ + struct AttributeLocation + { + adios2::Dims shape; + size_t offset; + Datatype dt; + char *destroy = nullptr; + + AttributeLocation() = delete; + AttributeLocation( adios2::Dims shape, size_t offset, Datatype dt ); + + AttributeLocation( AttributeLocation const & other ) = delete; + AttributeLocation & + operator=( AttributeLocation const & other ) = delete; + + AttributeLocation( AttributeLocation && other ); + AttributeLocation & operator=( AttributeLocation && other ); + + ~AttributeLocation(); + }; + + private: + /* + * Allocate one large buffer instead of hundreds of single heap + * allocations. + * This will comply with alignment requirements, since + * std::allocator::allocate() will call the untyped new operator + * ::operator new(std::size_t) + * https://en.cppreference.com/w/cpp/memory/allocator/allocate + */ + std::vector< char > m_rawBuffer; + std::map< std::string, AttributeLocation > m_offsets; + + public: + explicit PreloadAdiosAttributes() = default; + PreloadAdiosAttributes( PreloadAdiosAttributes const & other ) = delete; + PreloadAdiosAttributes & + operator=( PreloadAdiosAttributes const & other ) = delete; + + PreloadAdiosAttributes( PreloadAdiosAttributes && other ) = default; + PreloadAdiosAttributes & + operator=( PreloadAdiosAttributes && other ) = default; + + /** + * @brief Schedule attributes for preloading. + * + * This will invalidate all previously buffered attributes. + * This will *not* flush the scheduled loads. This way, attributes can + * be loaded along with the next adios2::Engine flush. + * + * @param IO + * @param engine + */ + void + preloadAttributes( adios2::IO & IO, adios2::Engine & engine ); + + /** + * @brief Get an attribute that has been buffered previously. + * + * @tparam T The underlying primitive datatype of the attribute. + * Will fail if the type found in ADIOS does not match. + * @param name The full name of the attribute. + * @return Pointer to the buffered attribute along with information on + * the attribute's shape. Valid only until any non-const member + * of PreloadAdiosAttributes is called. + */ + template< typename T > + AttributeWithShape< T > + getAttribute( std::string const & name ) const; + }; + + template< typename T > + AttributeWithShape< T > + PreloadAdiosAttributes::getAttribute( std::string const & name ) const + { + auto it = m_offsets.find( name ); + if( it == m_offsets.end() ) + { + throw std::runtime_error( + "[ADIOS2] Requested attribute not found: " + name ); + } + AttributeLocation const & location = it->second; + if( location.dt != determineDatatype< T >() ) + { + throw std::runtime_error( + "[ADIOS2] Wrong datatype for attribute: " + name ); + } + AttributeWithShape< T > res; + res.shape = location.shape; + res.data = + reinterpret_cast< T const * >( &m_rawBuffer[ location.offset ] ); + return res; + } +} // namespace detail +} // namespace openPMD + +#endif // openPMD_HAVE_ADIOS2 diff --git a/src/IO/ADIOS/ADIOS2Auxiliary.cpp b/src/IO/ADIOS/ADIOS2Auxiliary.cpp index f455b9aab7..119791c585 100644 --- a/src/IO/ADIOS/ADIOS2Auxiliary.cpp +++ b/src/IO/ADIOS/ADIOS2Auxiliary.cpp @@ -119,61 +119,98 @@ namespace detail } template< typename T > - typename std::vector< T >::size_type + Extent AttributeInfoHelper< T >::getSize( adios2::IO & IO, - std::string const & attributeName ) + std::string const & attributeName, + VariableOrAttribute voa ) { - auto attribute = IO.InquireAttribute< T >( attributeName ); - if( !attribute ) + switch( voa ) { - throw std::runtime_error( - "[ADIOS2] Internal error: Attribute not present." ); + case VariableOrAttribute::Attribute: + { + auto attribute = IO.InquireAttribute< T >( attributeName ); + if( !attribute ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Attribute not present." ); + } + return { attribute.Data().size() }; + } + case VariableOrAttribute::Variable: + { + auto variable = IO.InquireVariable< T >( attributeName ); + if( !variable ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Variable not present." ); + } + auto shape = variable.Shape(); + Extent res; + res.reserve( shape.size() ); + for( auto val : shape ) + { + res.push_back( val ); + } + return res; + } + default: + throw std::runtime_error( "[ADIOS2] Unreachable!" ); } - return attribute.Data().size(); } template< typename T > - typename std::vector< T >::size_type + Extent AttributeInfoHelper< std::vector< T > >::getSize( adios2::IO & IO, - std::string const & attributeName ) + std::string const & attributeName, + VariableOrAttribute voa ) { - return AttributeInfoHelper< T >::getSize( IO, attributeName ); + return AttributeInfoHelper< T >::getSize( IO, attributeName, voa ); } - typename std::vector< bool_representation >::size_type + Extent AttributeInfoHelper< bool >::getSize( adios2::IO & IO, - std::string const & attributeName ) + std::string const & attributeName, + VariableOrAttribute voa ) { return AttributeInfoHelper< bool_representation >::getSize( - IO, attributeName ); + IO, attributeName, voa ); } - template< typename T > - typename std::vector< T >::size_type - AttributeInfo::operator()( - adios2::IO & IO, - std::string const & attributeName ) + template< typename T, typename... Params > + Extent + AttributeInfo::operator()( Params &&... params ) { - return AttributeInfoHelper< T >::getSize( IO, attributeName ); + return AttributeInfoHelper< T >::getSize( + std::forward< Params >( params )... ); } template< int n, typename... Params > - size_t + Extent AttributeInfo::operator()( Params &&... ) { - return 0; + return { 0 }; } Datatype attributeInfo( adios2::IO & IO, std::string const & attributeName, - bool verbose ) + bool verbose, + VariableOrAttribute voa ) { - std::string type = IO.AttributeType( attributeName ); + std::string type; + switch( voa ) + { + case VariableOrAttribute::Attribute: + type = IO.AttributeType( attributeName ); + break; + case VariableOrAttribute::Variable: + type = IO.VariableType( attributeName ); + break; + } if( type.empty() ) { if( verbose ) @@ -188,14 +225,69 @@ namespace detail { static AttributeInfo ai; Datatype basicType = fromADIOS2Type( type ); - auto size = - switchType< size_t >( basicType, ai, IO, attributeName ); - Datatype openPmdType = size == 1 - ? basicType - : size == 7 && basicType == Datatype::DOUBLE - ? Datatype::ARR_DBL_7 - : toVectorType( basicType ); - return openPmdType; + Extent shape = + switchType< Extent >( basicType, ai, IO, attributeName, voa ); + + switch( voa ) + { + case VariableOrAttribute::Attribute: + { + auto size = shape[ 0 ]; + Datatype openPmdType = size == 1 + ? basicType + : size == 7 && basicType == Datatype::DOUBLE + ? Datatype::ARR_DBL_7 + : toVectorType( basicType ); + return openPmdType; + } + case VariableOrAttribute::Variable: + { + if( shape.size() == 0 || + ( shape.size() == 1 && shape[ 0 ] == 1 ) ) + { + // global single value variable + return basicType; + } + else if( shape.size() == 1 ) + { + auto size = shape[ 0 ]; + Datatype openPmdType = + size == 7 && basicType == Datatype::DOUBLE + ? Datatype::ARR_DBL_7 + : toVectorType( basicType ); + return openPmdType; + } + else if( shape.size() == 2 && basicType == Datatype::CHAR ) + { + return Datatype::VEC_STRING; + } + else + { + throw std::runtime_error( + "[ADIOS2] Unexpected shape for " + attributeName ); + } + } + } + if( shape.size() <= 1 ) + { + // size == 0 <=> global single value variable + auto size = shape.size() == 0 ? 1 : shape[ 0 ]; + Datatype openPmdType = size == 1 + ? basicType + : size == 7 && basicType == Datatype::DOUBLE + ? Datatype::ARR_DBL_7 + : toVectorType( basicType ); + return openPmdType; + } + else if( shape.size() == 2 && basicType == Datatype::CHAR ) + { + return Datatype::VEC_STRING; + } + else + { + throw std::runtime_error( + "[ADIOS2] Unexpected shape for " + attributeName ); + } } } } // namespace detail diff --git a/src/IO/ADIOS/ADIOS2IOHandler.cpp b/src/IO/ADIOS/ADIOS2IOHandler.cpp index 5124341576..5ae6d24c4c 100644 --- a/src/IO/ADIOS/ADIOS2IOHandler.cpp +++ b/src/IO/ADIOS/ADIOS2IOHandler.cpp @@ -122,32 +122,46 @@ ADIOS2IOHandlerImpl::~ADIOS2IOHandlerImpl() void ADIOS2IOHandlerImpl::init( nlohmann::json cfg ) { - if( !cfg.contains( "adios2" ) ) + if( cfg.contains( "adios2" ) ) { - return; - } - m_config = std::move( cfg[ "adios2" ] ); - auto engineConfig = config( ADIOS2Defaults::str_engine ); - if( !engineConfig.json().is_null() ) - { - auto engineTypeConfig = - config( ADIOS2Defaults::str_type, engineConfig ).json(); - if( !engineTypeConfig.is_null() ) + m_config = std::move( cfg[ "adios2" ] ); + + if( m_config.json().contains( "new_attribute_layout" ) ) { - // convert to string - m_engineType = engineTypeConfig; - std::transform( - m_engineType.begin(), - m_engineType.end(), - m_engineType.begin(), - []( unsigned char c ) { return std::tolower( c ); } ); + m_attributeLayout = + static_cast< bool >( m_config[ "new_attribute_layout" ].json() ) + ? AttributeLayout::ByAdiosVariables + : AttributeLayout::ByAdiosAttributes; + } + + auto engineConfig = config( ADIOS2Defaults::str_engine ); + if( !engineConfig.json().is_null() ) + { + auto engineTypeConfig = + config( ADIOS2Defaults::str_type, engineConfig ).json(); + if( !engineTypeConfig.is_null() ) + { + // convert to string + m_engineType = engineTypeConfig; + std::transform( + m_engineType.begin(), + m_engineType.end(), + m_engineType.begin(), + []( unsigned char c ) { return std::tolower( c ); } ); + } + } + auto operators = getOperators(); + if( operators ) + { + defaultOperators = std::move( operators.get() ); } } - auto operators = getOperators(); - if( operators ) - { - defaultOperators = std::move( operators.get() ); - } + // environment-variable based configuration + int useNewLayout = auxiliary::getEnvNum( + "OPENPMD_NEW_ATTRIBUTE_LAYOUT", + m_attributeLayout == AttributeLayout::ByAdiosVariables ); + m_attributeLayout = useNewLayout == 0 ? AttributeLayout::ByAdiosAttributes + : AttributeLayout::ByAdiosVariables; } auxiliary::Option< std::vector< ADIOS2IOHandlerImpl::ParameterizedOperator > > @@ -210,7 +224,7 @@ ADIOS2IOHandlerImpl::fileSuffix() const static std::map< std::string, std::string > endings{ { "sst", "" }, { "staging", "" }, { "bp4", ".bp" }, { "bp3", ".bp" }, { "file", ".bp" }, { "hdf5", ".h5" }, - { "nullcore", ".nullcore" } + { "nullcore", ".nullcore" }, { "ssc", ".ssc" } }; auto it = endings.find( m_engineType ); if( it != endings.end() ) @@ -231,7 +245,7 @@ ADIOS2IOHandlerImpl::flush() { if ( m_dirty.find( p.first ) != m_dirty.end( ) ) { - p.second->flush( ); + p.second->flush( /* writeAttributes = */ false ); } else { @@ -335,7 +349,7 @@ void ADIOS2IOHandlerImpl::createDataset( auto const file = refreshFileFromParent( writable ); auto filePos = setAndGetFilePosition( writable, name ); filePos->gd = ADIOS2FilePosition::GD::DATASET; - auto const varName = filePositionToString( filePos ); + auto const varName = nameOfVariable( writable ); std::vector< ParameterizedOperator > operators; nlohmann::json options = nlohmann::json::parse( parameters.options ); @@ -445,6 +459,7 @@ ADIOS2IOHandlerImpl::closeFile( []( detail::BufferedActions & ba, adios2::Engine & ) { ba.finalize(); }, + /* writeAttributes = */ true, /* flushUnconditionally = */ false ); m_fileData.erase( it ); } @@ -477,7 +492,7 @@ void ADIOS2IOHandlerImpl::openDataset( auto pos = setAndGetFilePosition( writable, name ); pos->gd = ADIOS2FilePosition::GD::DATASET; auto file = refreshFileFromParent( writable ); - auto varName = filePositionToString( pos ); + auto varName = nameOfVariable( writable ); *parameters.dtype = detail::fromADIOS2Type( getFileData( file ).m_IO.VariableType( varName ) ); switchType( *parameters.dtype, detail::DatasetOpener( this ), file, varName, @@ -533,8 +548,40 @@ void ADIOS2IOHandlerImpl::writeDataset( void ADIOS2IOHandlerImpl::writeAttribute( Writable * writable, const Parameter< Operation::WRITE_ATT > & parameters ) { - switchType( parameters.dtype, detail::AttributeWriter( ), this, writable, + switch( m_attributeLayout ) + { + case AttributeLayout::ByAdiosAttributes: + switchType( + parameters.dtype, + detail::OldAttributeWriter(), + this, + writable, parameters ); + break; + case AttributeLayout::ByAdiosVariables: + { + VERIFY_ALWAYS( + m_handler->m_backendAccess != Access::READ_ONLY, + "[ADIOS2] Cannot write attribute in read-only mode." ); + auto pos = setAndGetFilePosition( writable ); + auto file = refreshFileFromParent( writable ); + auto fullName = nameOfAttribute( writable, parameters.name ); + auto prefix = filePositionToString( pos ); + + auto & filedata = getFileData( file ); + filedata.invalidateAttributesMap(); + m_dirty.emplace( std::move( file ) ); + + // this intentionally overwrites previous writes + auto & bufferedWrite = filedata.m_attributeWrites[ fullName ]; + bufferedWrite.name = fullName; + bufferedWrite.dtype = parameters.dtype; + bufferedWrite.resource = parameters.resource; + break; + } + default: + throw std::runtime_error( "Unreachable!" ); + } } void ADIOS2IOHandlerImpl::readDataset( @@ -556,10 +603,28 @@ void ADIOS2IOHandlerImpl::readAttribute( auto file = refreshFileFromParent( writable ); auto pos = setAndGetFilePosition( writable ); detail::BufferedActions & ba = getFileData( file ); - detail::BufferedAttributeRead bar; - bar.name = nameOfAttribute( writable, parameters.name ); - bar.param = parameters; - ba.enqueue( std::move( bar ) ); + switch( m_attributeLayout ) + { + using AL = AttributeLayout; + case AL::ByAdiosAttributes: + { + detail::OldBufferedAttributeRead bar; + bar.name = nameOfAttribute( writable, parameters.name ); + bar.param = parameters; + ba.enqueue( std::move( bar ) ); + break; + } + case AL::ByAdiosVariables: + { + detail::BufferedAttributeRead bar; + bar.name = nameOfAttribute( writable, parameters.name ); + bar.param = parameters; + ba.m_attributeReads.push_back( std::move( bar ) ); + break; + } + default: + throw std::runtime_error( "Unreachable!" ); + } m_dirty.emplace( std::move( file ) ); } @@ -598,35 +663,78 @@ void ADIOS2IOHandlerImpl::listPaths( * inspected. */ std::vector< std::string > delete_me; - auto f = [myName, &subdirs, &delete_me]( - std::vector< std::string > & varsOrAttrs, bool variables ) { - for( auto var : varsOrAttrs ) + + switch( m_attributeLayout ) + { + using AL = AttributeLayout; + case AL::ByAdiosVariables: + { + std::vector< std::string > vars = + fileData.availableVariablesPrefixed( myName ); + for( auto var : vars ) + { + // since current Writable is a group and no dataset, + // var == "__data__" is not possible + if( auxiliary::ends_with( var, "/__data__" ) ) + { + // here be datasets + var = auxiliary::replace_last( var, "/__data__", "" ); + auto firstSlash = var.find_first_of( '/' ); + if( firstSlash != std::string::npos ) + { + var = var.substr( 0, firstSlash ); + subdirs.emplace( std::move( var ) ); + } + else + { // var is a dataset at the current level + delete_me.push_back( std::move( var ) ); + } + } + else + { + // here be attributes + auto firstSlash = var.find_first_of( '/' ); + if( firstSlash != std::string::npos ) + { + var = var.substr( 0, firstSlash ); + subdirs.emplace( std::move( var ) ); + } + } + } + break; + } + case AL::ByAdiosAttributes: { - auto firstSlash = var.find_first_of( '/' ); - if( firstSlash != std::string::npos ) + std::vector< std::string > vars = + fileData.availableVariablesPrefixed( myName ); + for( auto var : vars ) { - var = var.substr( 0, firstSlash ); - subdirs.emplace( std::move( var ) ); + auto firstSlash = var.find_first_of( '/' ); + if( firstSlash != std::string::npos ) + { + var = var.substr( 0, firstSlash ); + subdirs.emplace( std::move( var ) ); + } + else + { // var is a dataset at the current level + delete_me.push_back( std::move( var ) ); + } } - else if( variables ) - { // var is a dataset at the current level - delete_me.push_back( std::move( var ) ); + std::vector< std::string > attributes = + fileData.availableAttributesPrefixed( myName ); + for( auto attr : attributes ) + { + auto firstSlash = attr.find_first_of( '/' ); + if( firstSlash != std::string::npos ) + { + attr = attr.substr( 0, firstSlash ); + subdirs.emplace( std::move( attr ) ); + } } + break; } - }; - std::vector< std::string > vars; - for( auto const & p : fileData.availableVariablesPrefixed( myName ) ) - { - vars.emplace_back( std::move( p ) ); } - std::vector< std::string > attrs; - for( auto const & p : fileData.availableAttributesPrefixed( myName ) ) - { - attrs.emplace_back( std::move( p ) ); - } - f( vars, true ); - f( attrs, false ); for ( auto & d : delete_me ) { subdirs.erase( d ); @@ -659,18 +767,30 @@ void ADIOS2IOHandlerImpl::listDatasets( auto & fileData = getFileData( file ); fileData.requireActiveStep(); - std::map< std::string, adios2::Params > vars = - fileData.availableVariables(); std::unordered_set< std::string > subdirs; - for( auto & var : fileData.availableVariablesPrefixed( myName ) ) + for( auto var : fileData.availableVariablesPrefixed( myName ) ) { + if( m_attributeLayout == AttributeLayout::ByAdiosVariables ) + { + // since current Writable is a group and no dataset, + // var == "__data__" is not possible + if( !auxiliary::ends_with( var, "/__data__" ) ) + { + continue; + } + // variable is now definitely a dataset, let's strip the suffix + var = auxiliary::replace_last( var, "/__data__", "" ); + } + // if string still contains a slash, variable is a dataset below the + // current group + // we only want datasets contained directly within the current group + // let's ensure that auto firstSlash = var.find_first_of( '/' ); if( firstSlash == std::string::npos ) { subdirs.emplace( std::move( var ) ); - } // else: var is a path or a dataset in a group below the current - // group + } } for( auto & dataset : subdirs ) { @@ -694,15 +814,28 @@ void ADIOS2IOHandlerImpl::listAttributes( auto & ba = getFileData( file ); ba.requireActiveStep(); // make sure that the attributes are present - auto const & attrs = ba.availableAttributesPrefixed( attributePrefix ); + std::vector< std::string > attrs; + switch( m_attributeLayout ) + { + using AL = AttributeLayout; + case AL::ByAdiosAttributes: + attrs = ba.availableAttributesPrefixed( attributePrefix ); + break; + case AL::ByAdiosVariables: + attrs = ba.availableVariablesPrefixed( attributePrefix ); + break; + } for( auto & rawAttr : attrs ) { + if( m_attributeLayout == AttributeLayout::ByAdiosVariables && + ( auxiliary::ends_with( rawAttr, "/__data__" ) || + rawAttr == "__data__" ) ) + { + continue; + } auto attr = auxiliary::removeSlashes( rawAttr ); if( attr.find_last_of( '/' ) == std::string::npos ) { - // std::cout << "ATTRIBUTE at " << attributePrefix << ": " << attr - // << - // std::endl; parameters.attributes->push_back( std::move( attr ) ); } } @@ -841,7 +974,30 @@ ADIOS2IOHandlerImpl::getCompressionOperator( std::string const & compression ) std::string ADIOS2IOHandlerImpl::nameOfVariable( Writable * writable ) { - return filePositionToString( setAndGetFilePosition( writable ) ); + auto filepos = setAndGetFilePosition( writable ); + auto res = filePositionToString( filepos ); + if( m_attributeLayout == AttributeLayout::ByAdiosAttributes ) + { + return res; + } + switch( filepos->gd ) + { + case ADIOS2FilePosition::GD::GROUP: + return res; + case ADIOS2FilePosition::GD::DATASET: + if( auxiliary::ends_with( res, '/' ) ) + { + return res + "__data__"; + } + else + { + // By convention, this path should always be taken + // But let's be safe + return res + "/__data__"; + } + default: + throw std::runtime_error( "[ADIOS2IOHandlerImpl] Unreachable!" ); + } } std::string ADIOS2IOHandlerImpl::nameOfAttribute( Writable * writable, @@ -954,10 +1110,65 @@ namespace detail "[ADIOS2] Internal error: Unknown datatype trying to read a dataset." ); } - template < typename T > - Datatype AttributeReader:: - operator( )( adios2::IO & IO, std::string name, - std::shared_ptr< Attribute::resource > resource ) + template< typename T > + Datatype + OldAttributeReader::operator()( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ) + { + /* + * If we store an attribute of boolean type, we store an additional + * attribute prefixed with '__is_boolean__' to indicate this information + * that would otherwise be lost. Check whether this has been done. + */ + using rep = AttributeTypes< bool >::rep; + if +# if __cplusplus > 201402L + constexpr +# endif + ( std::is_same< T, rep >::value ) + { + std::string metaAttr = "__is_boolean__" + name; + /* + * In verbose mode, attributeInfo will yield a warning if not + * finding the requested attribute. Since we expect the attribute + * not to be present in many cases (i.e. when it is actually not + * a boolean), let's tell attributeInfo to be quiet. + */ + auto type = attributeInfo( + IO, "__is_boolean__" + name, /* verbose = */ false ); + if( type == determineDatatype< rep >() ) + { + auto attr = IO.InquireAttribute< rep >( metaAttr ); + if( attr.Data().size() == 1 && attr.Data()[ 0 ] == 1 ) + { + AttributeTypes< bool >::oldReadAttribute( + IO, name, resource ); + return determineDatatype< bool >(); + } + } + } + AttributeTypes< T >::oldReadAttribute( IO, name, resource ); + return determineDatatype< T >(); + } + + template< int n, typename... Params > + Datatype + OldAttributeReader::operator()( Params &&... ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Unknown datatype while " + "trying to read an attribute." ); + } + + template< typename T > + Datatype + AttributeReader::operator()( + adios2::IO & IO, + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ) { /* * If we store an attribute of boolean type, we store an additional @@ -985,12 +1196,14 @@ namespace detail auto attr = IO.InquireAttribute< rep >( metaAttr ); if (attr.Data().size() == 1 && attr.Data()[0] == 1) { - AttributeTypes< bool >::readAttribute( IO, name, resource ); + AttributeTypes< bool >::readAttribute( + preloadedAttributes, name, resource ); return determineDatatype< bool >(); } } } - AttributeTypes< T >::readAttribute( IO, name, resource ); + AttributeTypes< T >::readAttribute( + preloadedAttributes, name, resource ); return determineDatatype< T >(); } @@ -1001,12 +1214,13 @@ namespace detail "trying to read an attribute." ); } - template < typename T > - void AttributeWriter:: - operator( )( ADIOS2IOHandlerImpl * impl, Writable * writable, - const Parameter< Operation::WRITE_ATT > & parameters ) + template< typename T > + void + OldAttributeWriter::operator()( + ADIOS2IOHandlerImpl * impl, + Writable * writable, + const Parameter< Operation::WRITE_ATT > & parameters ) { - VERIFY_ALWAYS( impl->m_handler->m_backendAccess != Access::READ_ONLY, "[ADIOS2] Cannot write attribute in read-only mode." ); @@ -1054,10 +1268,30 @@ namespace detail filedata.uncommittedAttributes.emplace( fullName ); } - typename AttributeTypes< T >::Attr attr = - AttributeTypes< T >::createAttribute( - IO, fullName, variantSrc::get< T >( parameters.resource ) ); - VERIFY_ALWAYS( attr, "[ADIOS2] Failed creating attribute." ) + AttributeTypes< T >::oldCreateAttribute( + IO, fullName, variantSrc::get< T >( parameters.resource ) ); + } + + template< int n, typename... Params > + void + OldAttributeWriter::operator()( Params &&... ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Unknown datatype while " + "trying to write an attribute." ); + } + + template< typename T > + void + AttributeWriter::operator()( + detail::BufferedAttributeWrite & params, + BufferedActions & fileData ) + { + AttributeTypes< T >::createAttribute( + fileData.m_IO, + fileData.requireActiveStep(), + params, + variantSrc::get< T >( params.resource ) ); } template < int n, typename... Params > @@ -1133,82 +1367,292 @@ namespace detail // variable has not been found, so we don't fill in any blocks } - template < typename T > - typename AttributeTypes< T >::Attr - AttributeTypes< T >::createAttribute( adios2::IO & IO, std::string name, - const T value ) + template< typename T > + void + AttributeTypes< T >::oldCreateAttribute( + adios2::IO & IO, + std::string name, + const T value ) { auto attr = IO.DefineAttribute( name, value ); + if( !attr ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Failed defining attribute '" + name + + "'." ); + } + } + + template< typename T > + void + AttributeTypes< T >::oldReadAttribute( + adios2::IO & IO, + std::string name, + std::shared_ptr< Attribute::resource > resource ) + { + auto attr = IO.InquireAttribute< T >( name ); if ( !attr ) { throw std::runtime_error( - "[ADIOS2] Internal error: Failed defining attribute '" + name + "'." ); + "[ADIOS2] Internal error: Failed reading attribute '" + name + + "'." ); } - return attr; + *resource = attr.Data()[ 0 ]; } - template < typename T > - void AttributeTypes< T >::readAttribute( - adios2::IO & IO, std::string name, + template< typename T > + void + AttributeTypes< T >::createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const T value ) + { + auto attr = IO.InquireVariable< T >( params.name ); + // @todo check size + if( !attr ) + { + // std::cout << "DATATYPE OF " << name << ": " + // << IO.VariableType( name ) << std::endl; + attr = IO.DefineVariable< T >( params.name ); + } + if( !attr ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Failed defining variable '" + + params.name + "'." ); + } + engine.Put( attr, value, adios2::Mode::Deferred ); + } + + template< typename T > + void + AttributeTypes< T >::readAttribute( + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ) + { + detail::AttributeWithShape< T > attr = + preloadedAttributes.getAttribute< T >( name ); + if( !( attr.shape.size() == 0 || + ( attr.shape.size() == 1 && attr.shape[ 0 ] == 1 ) ) ) + { + throw std::runtime_error( + "[ADIOS2] Expecting scalar ADIOS variable, got " + + std::to_string( attr.shape.size() ) + "D: " + name ); + } + *resource = *attr.data; + } + + template< typename T > + void + AttributeTypes< std::vector< T > >::oldCreateAttribute( + adios2::IO & IO, + std::string name, + const std::vector< T > & value ) + { + auto attr = IO.DefineAttribute( name, value.data(), value.size() ); + if( !attr ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Failed defining attribute '" + name + + "'." ); + } + } + + template< typename T > + void + AttributeTypes< std::vector< T > >::oldReadAttribute( + adios2::IO & IO, + std::string name, std::shared_ptr< Attribute::resource > resource ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< T >( name ); if ( !attr ) { throw std::runtime_error( "[ADIOS2] Internal error: Failed reading attribute '" + name + "'." ); } - *resource = attr.Data( )[0]; + *resource = attr.Data(); } template < typename T > - typename AttributeTypes< std::vector< T > >::Attr + void AttributeTypes< std::vector< T > >::createAttribute( - adios2::IO & IO, std::string name, const std::vector< T > & value ) + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const std::vector< T > & value ) + { + auto size = value.size(); + auto attr = IO.InquireVariable< T >( params.name ); + // @todo check size + if( !attr ) + { + attr = IO.DefineVariable< T >( + params.name, { size }, { 0 }, { size } ); + } + if( !attr ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Failed defining variable '" + + params.name + "'." ); + } + engine.Put( attr, value.data(), adios2::Mode::Deferred ); + } + + template< typename T > + void + AttributeTypes< std::vector< T > >::readAttribute( + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ) { - auto attr = IO.DefineAttribute( name, value.data( ), value.size( ) ); - if ( !attr ) + detail::AttributeWithShape< T > attr = + preloadedAttributes.getAttribute< T >( name ); + if( attr.shape.size() != 1 ) + { + throw std::runtime_error( "[ADIOS2] Expecting 1D ADIOS variable" ); + } + + std::vector< T > res( attr.shape[ 0 ] ); + std::copy_n( attr.data, attr.shape[ 0 ], res.data() ); + *resource = std::move( res ); + } + + void + AttributeTypes< std::vector< std::string > >::oldCreateAttribute( + adios2::IO & IO, + std::string name, + const std::vector< std::string > & value ) + { + auto attr = IO.DefineAttribute( name, value.data(), value.size() ); + if( !attr ) { throw std::runtime_error( - "[ADIOS2] Internal error: Failed defining attribute '" + name + "'." ); + "[ADIOS2] Internal error: Failed defining attribute '" + name + + "'." ); } - return attr; } - template < typename T > - void AttributeTypes< std::vector< T > >::readAttribute( - adios2::IO & IO, std::string name, + void + AttributeTypes< std::vector< std::string > >::oldReadAttribute( + adios2::IO & IO, + std::string name, std::shared_ptr< Attribute::resource > resource ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< std::string >( name ); if ( !attr ) { throw std::runtime_error( - "[ADIOS2] Internal error: Failed reading attribute '" + name + "'." ); + "[ADIOS2] Internal error: Failed reading attribute '" + name + + "'." ); } - *resource = attr.Data( ); + *resource = attr.Data(); } - template < typename T, size_t n > - typename AttributeTypes< std::array< T, n > >::Attr - AttributeTypes< std::array< T, n > >::createAttribute( - adios2::IO & IO, std::string name, const std::array< T, n > & value ) + void + AttributeTypes< std::vector< std::string > >::createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const std::vector< std::string > & vec ) + { + size_t width = 0; + for( auto const & str : vec ) + { + width = std::max( width, str.size() ); + } + ++width; // null delimiter + size_t const height = vec.size(); + + auto attr = IO.InquireVariable< char >( params.name ); + // @todo check size + if( !attr ) + { + attr = IO.DefineVariable< char >( + params.name, { height, width }, { 0, 0 }, { height, width } ); + } + if( !attr ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Failed defining variable '" + + params.name + "'." ); + } + + // write this thing to the params, so we don't get a use after free + // due to deferred writing + params.bufferForVecString = std::vector< char >( width * height, 0 ); + for( size_t i = 0; i < height; ++i ) + { + size_t start = i * width; + std::string const & str = vec[ i ]; + std::copy( + str.begin(), + str.end(), + params.bufferForVecString.begin() + start ); + } + + engine.Put( + attr, params.bufferForVecString.data(), adios2::Mode::Deferred ); + } + + void + AttributeTypes< std::vector< std::string > >::readAttribute( + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ) { - auto attr = IO.DefineAttribute( name, value.data( ), n ); - if ( !attr ) + detail::AttributeWithShape< char > attr = + preloadedAttributes.getAttribute< char >( name ); + if( attr.shape.size() != 2 ) + { + throw std::runtime_error( "[ADIOS2] Expecting 2D ADIOS variable" ); + } + size_t height = attr.shape[ 0 ]; + size_t width = attr.shape[ 1 ]; + + std::vector< std::string > res( height ); + for( size_t i = 0; i < height; ++i ) + { + size_t start = i * width; + char const * start_ptr = attr.data + start; + size_t j = 0; + while( j < width && start_ptr[ j ] != 0 ) + { + ++j; + } + std::string & str = res[ i ]; + str.append( start_ptr, start_ptr + j ); + } + + *resource = res; + } + + template< typename T, size_t n > + void + AttributeTypes< std::array< T, n > >::oldCreateAttribute( + adios2::IO & IO, + std::string name, + const std::array< T, n > & value ) + { + auto attr = IO.DefineAttribute( name, value.data(), n ); + if( !attr ) { throw std::runtime_error( - "[ADIOS2] Internal error: Failed defining attribute '" + name + "'." ); + "[ADIOS2] Internal error: Failed defining attribute '" + name + + "'." ); } - return attr; } - template < typename T, size_t n > - void AttributeTypes< std::array< T, n > >::readAttribute( - adios2::IO & IO, std::string name, + template< typename T, size_t n > + void + AttributeTypes< std::array< T, n > >::oldReadAttribute( + adios2::IO & IO, + std::string name, std::shared_ptr< Attribute::resource > resource ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< T >( name ); if ( !attr ) { throw std::runtime_error( @@ -1223,26 +1667,108 @@ namespace detail *resource = res; } - typename AttributeTypes< bool >::Attr - AttributeTypes< bool >::createAttribute( adios2::IO & IO, std::string name, - const bool value ) + template< typename T, size_t n > + void + AttributeTypes< std::array< T, n > >::createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const std::array< T, n > & value ) + { + auto attr = IO.InquireVariable< T >( params.name ); + // @todo check size + if( !attr ) + { + attr = IO.DefineVariable< T >( params.name, { n }, { 0 }, { n } ); + } + if( !attr ) + { + throw std::runtime_error( + "[ADIOS2] Internal error: Failed defining variable '" + + params.name + "'." ); + } + engine.Put( attr, value.data(), adios2::Mode::Deferred ); + } + + template< typename T, size_t n > + void + AttributeTypes< std::array< T, n > >::readAttribute( + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ) + { + detail::AttributeWithShape< T > attr = + preloadedAttributes.getAttribute< T >( name ); + if( attr.shape.size() != 1 || attr.shape[ 0 ] != n ) + { + throw std::runtime_error( + "[ADIOS2] Expecting 1D ADIOS variable of extent " + + std::to_string( n ) ); + } + + std::array< T, n > res; + std::copy_n( attr.data, n, res.data() ); + *resource = std::move( res ); + } + + void + AttributeTypes< bool >::oldCreateAttribute( + adios2::IO & IO, + std::string name, + const bool value ) { IO.DefineAttribute< bool_representation >( "__is_boolean__" + name, 1 ); - return AttributeTypes< bool_representation >::createAttribute( + AttributeTypes< bool_representation >::oldCreateAttribute( IO, name, toRep( value ) ); } - void AttributeTypes< bool >::readAttribute( - adios2::IO & IO, std::string name, + void + AttributeTypes< bool >::oldReadAttribute( + adios2::IO & IO, + std::string name, std::shared_ptr< Attribute::resource > resource ) { - auto attr = IO.InquireAttribute< BasicType >( name ); + auto attr = IO.InquireAttribute< rep >( name ); if ( !attr ) { throw std::runtime_error( - "[ADIOS2] Internal error: Failed reading attribute '" + name + "'." ); + "[ADIOS2] Internal error: Failed reading attribute '" + name + + "'." ); } - *resource = fromRep( attr.Data( )[0] ); + *resource = fromRep( attr.Data()[ 0 ] ); + } + + + void + AttributeTypes< bool >::createAttribute( + adios2::IO & IO, + adios2::Engine & engine, + detail::BufferedAttributeWrite & params, + const bool value ) + { + IO.DefineAttribute< bool_representation >( + "__is_boolean__" + params.name, 1 ); + AttributeTypes< bool_representation >::createAttribute( + IO, engine, params, toRep( value ) ); + } + + void + AttributeTypes< bool >::readAttribute( + detail::PreloadAdiosAttributes const & preloadedAttributes, + std::string name, + std::shared_ptr< Attribute::resource > resource ) + { + detail::AttributeWithShape< rep > attr = + preloadedAttributes.getAttribute< rep >( name ); + if( !( attr.shape.size() == 0 || + ( attr.shape.size() == 1 && attr.shape[ 0 ] == 1 ) ) ) + { + throw std::runtime_error( + "[ADIOS2] Expecting scalar ADIOS variable, got " + + std::to_string( attr.shape.size() ) + "D: " + name ); + } + + *resource = fromRep( *attr.data ); } template < typename T > @@ -1438,37 +1964,71 @@ namespace detail void BufferedGet::run( BufferedActions & ba ) { - switchType( param.dtype, ba.m_readDataset, *this, ba.m_IO, - ba.getEngine( ), ba.m_file ); + switchType( + param.dtype, + ba.m_readDataset, + *this, + ba.m_IO, + ba.getEngine(), + ba.m_file ); } - void BufferedPut::run( BufferedActions & ba ) + void + BufferedPut::run( BufferedActions & ba ) { switchType( param.dtype, ba.m_writeDataset, *this, ba.m_IO, ba.getEngine( ) ); } void - BufferedAttributeRead::run( BufferedActions & ba ) + OldBufferedAttributeRead::run( BufferedActions & ba ) { auto type = attributeInfo( ba.m_IO, name, /* verbose = */ true ); - if ( type == Datatype::UNDEFINED ) + if( type == Datatype::UNDEFINED ) { - throw std::runtime_error( "[ADIOS2] Requested attribute (" + name + - ") not found in backend." ); + throw std::runtime_error( + "[ADIOS2] Requested attribute (" + name + + ") not found in backend." ); } - auto ret = - switchType< Datatype >( - type, - detail::AttributeReader{}, - ba.m_IO, - name, - param.resource ); + Datatype ret = switchType< Datatype >( + type, detail::OldAttributeReader{}, ba.m_IO, name, param.resource ); *param.dtype = ret; } + void + BufferedAttributeRead::run( BufferedActions & ba ) + { + auto type = attributeInfo( + ba.m_IO, + name, + /* verbose = */ true, + VariableOrAttribute::Variable ); + + if( type == Datatype::UNDEFINED ) + { + throw std::runtime_error( + "[ADIOS2] Requested attribute (" + name + + ") not found in backend." ); + } + + Datatype ret = switchType< Datatype >( + type, + detail::AttributeReader{}, + ba.m_IO, + ba.preloadAttributes, + name, + param.resource ); + *param.dtype = ret; + } + + void + BufferedAttributeWrite::run( BufferedActions & fileData ) + { + switchType( dtype, detail::AttributeWriter(), *this, fileData ); + } + BufferedActions::BufferedActions( ADIOS2IOHandlerImpl & impl, InvalidatableFile file ) @@ -1480,6 +2040,7 @@ namespace detail , m_writeDataset( &impl ) , m_readDataset( &impl ) , m_attributeReader() + , m_attributeLayout( impl.m_attributeLayout ) , m_engineType( impl.m_engineType ) { if( !m_IO ) @@ -1506,9 +2067,22 @@ namespace detail return; } // if write accessing, ensure that the engine is opened - if( !m_engine && m_mode != adios2::Mode::Read ) + // and that all attributes are written + // (attributes are written upon closing a step or a file + // which users might never do) + bool needToWriteAttributes = !m_attributeWrites.empty(); + if( ( needToWriteAttributes || !m_engine ) && + m_mode != adios2::Mode::Read ) { - getEngine(); + auto & engine = getEngine(); + if( needToWriteAttributes ) + { + for( auto & pair : m_attributeWrites ) + { + pair.second.run( *this ); + } + engine.PerformPuts(); + } } if( m_engine ) { @@ -1527,10 +2101,12 @@ namespace detail finalized = true; } - void BufferedActions::configure_IO(ADIOS2IOHandlerImpl& impl){ + void + BufferedActions::configure_IO( ADIOS2IOHandlerImpl & impl ) + { ( void )impl; static std::set< std::string > streamingEngines = { - "sst", "insitumpi", "inline", "staging", "nullcore" + "sst", "insitumpi", "inline", "staging", "nullcore", "ssc" }; static std::set< std::string > fileEngines = { "bp4", "bp3", "hdf5", "file" @@ -1551,7 +2127,8 @@ namespace detail auto it = streamingEngines.find( m_engineType ); if( it != streamingEngines.end() ) { - optimizeAttributesStreaming = true; + optimizeAttributesStreaming = + m_attributeLayout == AttributeLayout::ByAdiosAttributes; streamStatus = StreamStatus::OutsideOfStep; } else @@ -1563,7 +2140,8 @@ namespace detail { case adios2::Mode::Read: streamStatus = StreamStatus::Undecided; - delayOpeningTheFirstStep = true; + delayOpeningTheFirstStep = m_attributeLayout == + AttributeLayout::ByAdiosAttributes; break; case adios2::Mode::Write: streamStatus = StreamStatus::NoStream; @@ -1736,6 +2314,11 @@ namespace detail throw std::runtime_error( "[ADIOS2] Control flow error!" ); } + if( m_attributeLayout == AttributeLayout::ByAdiosVariables ) + { + preloadAttributes.preloadAttributes( + m_IO, m_engine.get() ); + } break; } default: @@ -1757,6 +2340,11 @@ namespace detail if( streamStatus == StreamStatus::OutsideOfStep ) { m_lastStepStatus = eng.BeginStep(); + if( m_mode == adios2::Mode::Read && + m_attributeLayout == AttributeLayout::ByAdiosVariables ) + { + preloadAttributes.preloadAttributes( m_IO, m_engine.get() ); + } streamStatus = StreamStatus::DuringStep; } return eng; @@ -1778,7 +2366,10 @@ namespace detail template< typename F > void - BufferedActions::flush( F && performPutGets, bool flushUnconditionally ) + BufferedActions::flush( + F && performPutGets, + bool writeAttributes, + bool flushUnconditionally ) { if( streamStatus == StreamStatus::StreamOver ) { @@ -1795,7 +2386,9 @@ namespace detail */ if( streamStatus == StreamStatus::OutsideOfStep ) { - if( m_buffer.empty() ) + if( m_buffer.empty() && + ( !writeAttributes || m_attributeWrites.empty() ) && + m_attributeReads.empty() ) { if( flushUnconditionally ) { @@ -1812,14 +2405,31 @@ namespace detail { ba->run( *this ); } + if( writeAttributes ) + { + for( auto & pair : m_attributeWrites ) + { + pair.second.run( *this ); + } + } performPutGets( *this, eng ); m_buffer.clear(); + + for( BufferedAttributeRead & task : m_attributeReads ) + { + task.run( *this ); + } + m_attributeReads.clear(); + if( writeAttributes ) + { + m_attributeWrites.clear(); + } } void - BufferedActions::flush() + BufferedActions::flush( bool writeAttributes ) { flush( []( BufferedActions & ba, adios2::Engine & eng ) { @@ -1840,6 +2450,7 @@ namespace detail break; } }, + writeAttributes, /* flushUnconditionally = */ false ); } @@ -1854,7 +2465,7 @@ namespace detail // sic! no else if( streamStatus == StreamStatus::NoStream ) { - flush(); + flush( /* writeAttributes = */ false ); return AdvanceStatus::OK; } switch( mode ) @@ -1878,6 +2489,7 @@ namespace detail []( BufferedActions &, adios2::Engine & eng ) { eng.EndStep(); }, + /* writeAttributes = */ true, /* flushUnconditionally = */ true ); uncommittedAttributes.clear(); streamStatus = StreamStatus::OutsideOfStep; @@ -1898,7 +2510,15 @@ namespace detail BufferedActions &, adios2::Engine & engine ) { adiosStatus = engine.BeginStep(); }, + /* writeAttributes = */ false, /* flushUnconditionally = */ true ); + if( adiosStatus == adios2::StepStatus::OK && + m_mode == adios2::Mode::Read && + m_attributeLayout == AttributeLayout::ByAdiosVariables ) + { + preloadAttributes.preloadAttributes( + m_IO, m_engine.get() ); + } } AdvanceStatus res = AdvanceStatus::OK; switch( adiosStatus ) diff --git a/src/IO/ADIOS/ADIOS2PreloadAttributes.cpp b/src/IO/ADIOS/ADIOS2PreloadAttributes.cpp new file mode 100644 index 0000000000..db46d5324c --- /dev/null +++ b/src/IO/ADIOS/ADIOS2PreloadAttributes.cpp @@ -0,0 +1,368 @@ +/* Copyright 2020 Franz Poeschel + * + * This file is part of openPMD-api. + * + * openPMD-api is free software: you can redistribute it and/or modify + * it under the terms of of either the GNU General Public License or + * the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * openPMD-api is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License and the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * and the GNU Lesser General Public License along with openPMD-api. + * If not, see . + */ + +#include "openPMD/config.hpp" +#if openPMD_HAVE_ADIOS2 + +#include "openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp" + +#include "openPMD/Datatype.hpp" +#include "openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp" +#include "openPMD/auxiliary/StringManip.hpp" + +#include +#include +#include +#include + +namespace openPMD +{ +namespace detail +{ + namespace + { + template< typename T, typename = void > + struct AttributeTypes; + + template< typename T > + struct AttributeTypes< + T, + typename std::enable_if< IsTrivialType< T >::val >::type > + { + static size_t + alignment() + { + return alignof( T ); + } + + static size_t + size() + { + return sizeof( T ); + } + + static adios2::Dims + shape( adios2::IO & IO, std::string const & name ) + { + auto var = IO.InquireVariable< T >( name ); + if( !var ) + { + throw std::runtime_error( + "[ADIOS2] Variable not found: " + name ); + } + return var.Shape(); + } + + static void + scheduleLoad( + adios2::IO & IO, + adios2::Engine & engine, + std::string const & name, + char * buffer, + PreloadAdiosAttributes::AttributeLocation & location ) + { + adios2::Variable< T > var = IO.InquireVariable< T >( name ); + if( !var ) + { + throw std::runtime_error( + "[ADIOS2] Variable not found: " + name ); + } + adios2::Dims const & shape = location.shape; + adios2::Dims offset( shape.size(), 0 ); + if( shape.size() > 0 ) + { + var.SetSelection( { offset, shape } ); + } + T * dest = reinterpret_cast< T * >( buffer ); + size_t numItems = 1; + for( auto extent : shape ) + { + numItems *= extent; + } + new( dest ) T[ numItems ]{}; + location.destroy = buffer; + engine.Get( var, dest, adios2::Mode::Deferred ); + } + + static void attributeLocationDestroy( char *ptr, size_t numItems ) + { + T *destroy = reinterpret_cast< T * >( ptr ); + for( size_t i = 0; i < numItems; ++i ) + { + destroy[ i ].~T(); + } + } + }; + + template< typename T > + struct AttributeTypes< + T, + typename std::enable_if< !IsTrivialType< T >::val >::type > + { + static size_t + alignment() + { + return 0; // we're not interested in those + } + + static size_t + size() + { + return 0; // we're not interested in those + } + + template< typename... Args > + static adios2::Dims + shape( Args &&... ) + { + throw std::runtime_error( "[ADIOS2] Control Flow Error!" ); + } + + template< typename... Args > + static void + scheduleLoad( Args &&... ) + { + throw std::runtime_error( "[ADIOS2] Control Flow Error!" ); + } + + template< typename... Args > + static void attributeLocationDestroy( Args &&... ) + { + throw std::runtime_error( "[ADIOS2] Control Flow Error!" ); + } + }; + + struct GetAlignment + { + template< typename T > + constexpr size_t + operator()() const + { + return AttributeTypes< T >::alignment(); + } + + template< unsigned long, typename... Args > + constexpr size_t + operator()( Args &&... ) const + { + return 0; + } + }; + + struct GetSize + { + template< typename T > + constexpr size_t + operator()() const + { + return AttributeTypes< T >::size(); + } + + template< unsigned long, typename... Args > + constexpr size_t + operator()( Args &&... ) const + { + return 0; + } + }; + + struct ScheduleLoad + { + template< typename T, typename... Args > + void + operator()( Args &&... args ) + { + AttributeTypes< T >::scheduleLoad( + std::forward< Args >( args )... ); + } + + template< unsigned long, typename... Args > + void + operator()( Args &&... ) + { + throw std::runtime_error( "[ADIOS2] Unknown datatype." ); + } + }; + + struct VariableShape + { + template< typename T, typename... Args > + adios2::Dims + operator()( Args &&... args ) + { + return AttributeTypes< T >::shape( + std::forward< Args >( args )... ); + } + + template< unsigned long n, typename... Args > + adios2::Dims + operator()( Args &&... ) + { + return {}; + } + }; + + struct AttributeLocationDestroy + { + template< typename T, typename... Args > + void operator()( Args &&...args ) + { + return AttributeTypes< T >::attributeLocationDestroy( + std::forward< Args >( args )... ); + } + + template< unsigned long n, typename... Args > + void operator()( Args &&... ) + { + } + }; + } // namespace + + using AttributeLocation = PreloadAdiosAttributes::AttributeLocation; + + AttributeLocation::AttributeLocation( + adios2::Dims shape_in, size_t offset_in, Datatype dt_in ) + : shape( std::move( shape_in ) ), offset( offset_in ), dt( dt_in ) + { + } + + AttributeLocation::AttributeLocation( AttributeLocation && other ) + : shape{ std::move( other.shape ) } + , offset{ std::move( other.offset ) } + , dt{ std::move( other.dt ) } + , destroy{ std::move( other.destroy ) } + { + other.destroy = nullptr; + } + + AttributeLocation & + AttributeLocation::operator=( AttributeLocation && other ) + { + this->shape = std::move( other.shape ); + this->offset = std::move( other.offset ); + this->dt = std::move( other.dt ); + this->destroy = std::move( other.destroy ); + other.destroy = nullptr; + return *this; + } + + PreloadAdiosAttributes::AttributeLocation::~AttributeLocation() + { + /* + * If the object has been moved from, this may be empty. + * Or else, if no custom destructor has been emplaced. + */ + if( destroy ) + { + size_t length = 1; + for( auto ext : shape ) + { + length *= ext; + } + static AttributeLocationDestroy ald; + switchType( dt, ald, destroy, length ); + } + } + + void + PreloadAdiosAttributes::preloadAttributes( + adios2::IO & IO, + adios2::Engine & engine ) + { + m_offsets.clear(); + std::map< Datatype, std::vector< std::string > > attributesByType; + auto addAttribute = + [ &attributesByType ]( Datatype dt, std::string name ) { + constexpr size_t reserve = 10; + auto it = attributesByType.find( dt ); + if( it == attributesByType.end() ) + { + it = attributesByType.emplace_hint( + it, dt, std::vector< std::string >() ); + it->second.reserve( reserve ); + } + it->second.push_back( std::move( name ) ); + }; + // PHASE 1: collect names of available attributes by ADIOS datatype + for( auto & variable : IO.AvailableVariables() ) + { + if( auxiliary::ends_with( variable.first, "/__data__" ) ) + { + continue; + } + // this will give us basic types only, no fancy vectors or similar + Datatype dt = fromADIOS2Type( IO.VariableType( variable.first ) ); + addAttribute( dt, std::move( variable.first ) ); + } + + // PHASE 2: get offsets for attributes in buffer + std::map< Datatype, size_t > offsets; + size_t currentOffset = 0; + GetAlignment switchAlignment; + GetSize switchSize; + VariableShape switchShape; + for( auto & pair : attributesByType ) + { + size_t alignment = switchType< size_t >( pair.first, switchAlignment ); + size_t size = switchType< size_t >( pair.first, switchSize ); + // go to next offset with valid alignment + size_t modulus = currentOffset % alignment; + if( modulus > 0 ) + { + currentOffset += alignment - modulus; + } + for( std::string & name : pair.second ) + { + adios2::Dims shape = + switchType< adios2::Dims >( pair.first, switchShape, IO, name ); + size_t elements = 1; + for( auto extent : shape ) + { + elements *= extent; + } + m_offsets.emplace( + std::piecewise_construct, + std::forward_as_tuple( std::move( name ) ), + std::forward_as_tuple( + std::move( shape ), currentOffset, pair.first ) ); + currentOffset += elements * size; + } + } + // now, currentOffset is the number of bytes that we need to allocate + // PHASE 3: allocate new buffer and schedule loads + m_rawBuffer.resize( currentOffset ); + ScheduleLoad switchSchedule; + for( auto & pair : m_offsets ) + { + switchType( + pair.second.dt, + switchSchedule, + IO, + engine, + pair.first, + &m_rawBuffer[ pair.second.offset ], + pair.second ); + } + } +} // namespace detail +} // namespace openPMD + +#endif // openPMD_HAVE_ADIOS2 diff --git a/test/ParallelIOTest.cpp b/test/ParallelIOTest.cpp index 84d3bafb05..008afaad81 100644 --- a/test/ParallelIOTest.cpp +++ b/test/ParallelIOTest.cpp @@ -1018,4 +1018,107 @@ TEST_CASE( "parallel_adios2_json_config", "[parallel][adios2]" ) read( "../samples/jsonConfiguredBP3Parallel.bp", readConfigBP3 ); read( "../samples/jsonConfiguredBP4Parallel.bp", readConfigBP4 ); } + +void +adios2_ssc() +{ + int global_size{ -1 }; + int global_rank{ -1 }; + MPI_Comm_size( MPI_COMM_WORLD, &global_size ); + MPI_Comm_rank( MPI_COMM_WORLD, &global_rank ); + if( auxiliary::getEnvString( "OPENPMD_BP_BACKEND", "NOT_SET" ) == "ADIOS1" ) + { + // run this test for ADIOS2 only + return; + } + + if( global_size < 2 ) + { + return; + } + + int color = global_rank % 2; + MPI_Comm local_comm; + MPI_Comm_split( MPI_COMM_WORLD, color, global_rank, &local_comm ); + int local_size{ -1 }; + int local_rank{ -1 }; + MPI_Comm_size( local_comm, &local_size ); + MPI_Comm_rank( local_comm, &local_rank ); + + constexpr size_t extent = 10; + + std::string options = R"( + { + "adios2": { + "engine": { + "type": "ssc" + } + } + })"; + + if( color == 0 ) + { + // write + Series writeSeries( + "../samples/adios2_stream.bp", + Access::CREATE, + local_comm, + options ); + auto iterations = writeSeries.writeIterations(); + for( size_t i = 0; i < 10; ++i ) + { + auto iteration = iterations[ i ]; + auto E_x = iteration.meshes[ "E" ][ "x" ]; + E_x.resetDataset( openPMD::Dataset( + openPMD::Datatype::INT, { unsigned( local_size ), extent } ) ); + std::vector< int > data( extent, i ); + E_x.storeChunk( + data, { unsigned( local_rank ), 0 }, { 1, extent } ); + + iteration.close(); + } + } + else if( color == 1 ) + { + // read + Series readSeries( + "../samples/adios2_stream.bp", + Access::READ_ONLY, + local_comm, + options ); + + size_t last_iteration_index = 0; + for( auto iteration : readSeries.readIterations() ) + { + auto E_x = iteration.meshes[ "E" ][ "x" ]; + REQUIRE( E_x.getDimensionality() == 2 ); + REQUIRE( E_x.getExtent()[ 1 ] == extent ); + auto chunk = E_x.loadChunk< int >( + { unsigned( local_rank ), 0 }, { 1, extent } ); + + iteration.close(); + + for( size_t i = 0; i < extent; ++i ) + { + REQUIRE( chunk.get()[ i ] == iteration.iterationIndex ); + } + last_iteration_index = iteration.iterationIndex; + } + REQUIRE( last_iteration_index == 9 ); + } +} + +TEST_CASE( "adios2_ssc", "[parallel][adios2]" ) +{ + /* + * @todo Activate this test as soon as we rely upon an ADIOS2 version + * including this fix https://github.com/ornladios/ADIOS2/pull/2568 + * (e.g. ADIOS 2.7.0). + */ + constexpr bool testAdiosSSC = false; + if( testAdiosSSC ) + { + adios2_ssc(); + } +} #endif diff --git a/test/SerialIOTest.cpp b/test/SerialIOTest.cpp index e91ca2e04a..95050e89b1 100644 --- a/test/SerialIOTest.cpp +++ b/test/SerialIOTest.cpp @@ -3148,6 +3148,48 @@ TEST_CASE( "bp4_steps", "[serial][adios2]" ) bp4_steps( "../samples/bp4steps_no_no.bp", dontUseSteps, dontUseSteps ); bp4_steps( "../samples/nullcore.bp", nullcore, "" ); bp4_steps( "../samples/bp4steps_default.bp", "{}", "{}" ); + + /* + * Do this whole thing once more, but this time use the new attribute + * layout. + */ + useSteps = R"( + { + "adios2": { + "new_attribute_layout": true, + "engine": { + "type": "bp4", + "usesteps": true + } + } + } + )"; + dontUseSteps = R"( + { + "adios2": { + "new_attribute_layout": true, + "engine": { + "type": "bp4", + "usesteps": false + } + } + } + )"; + /* + * @todo Activate these tests for Windows as soon as we bump the required + * ADIOS2 version to 2.7.0. Read here: + * https://github.com/openPMD/openPMD-api/pull/813#issuecomment-762235260 + */ +#ifndef _WIN32 + // sing the yes no song + bp4_steps( "../samples/newlayout_bp4steps_yes_yes.bp", useSteps, useSteps ); + bp4_steps( + "../samples/newlayout_bp4steps_no_yes.bp", dontUseSteps, useSteps ); + bp4_steps( + "../samples/newlayout_bp4steps_yes_no.bp", useSteps, dontUseSteps ); + bp4_steps( + "../samples/newlayout_bp4steps_no_no.bp", dontUseSteps, dontUseSteps ); +#endif } #endif