From 1ee61935c081e554bed54f61b9fa84110f1b104a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20P=C3=B6schel?= Date: Tue, 20 Oct 2020 15:51:35 +0200 Subject: [PATCH] Implement new variable-based attribute layout in ADIOS2 May be opted in to via OPENPMD_NEW_ATTRIBUTE_LAYOUT or JSON parameter adios2.new_attribute_layout = true Original commits: Write datasets as /.../__data__ (don't read yet) Read datasets written as /.../__data__ back correctly Write attributes as global single values Read attributes back from single global values Make attribute/variable puts Sync for now Enable stream-based processing of file-based engines Keep track of attributes written during the running step Write VEC_STRING as 2D variable Read 2D string vector attributes Fix indexing in VEC_STRING attributes Buffer attribute writes Still write them in sync mode tho Use deferred attribute writes Perform buffered attribute writes only upon advance/close_file Preload attributes Use preloaded attributes Fix loading of complex types Properly call destructors of all types Remove special handling for strings Fix struct visibility Fix initialization order fiasco Make the CI happy 1. const stuff 2. allow building when openPMD_HAVE_ADIOS2=0 Avoid some needless copying Adhere to rule of 5 Somewhat fix Attribute loading Parameterize flush by performputs implementation Allow old and new mode via JSON adios2.newAttributeLayout = true Reimplement BP4 workaround Some documentation Some testing Use OPENPMD_NEW_ATTRIBUTES_LAYOUT env var Two little fixes Undo unnecessary whitespace changes Add some documentation Add comments to ADIOS2PreloadAttributes header Add CI run for new attribute layout Support ADIOS2 SSC engine Deactivate new layout tests on Windows See here https://github.com/openPMD/openPMD-api/pull/813#issuecomment-762235260 Undo some unnecessary whitespace changes Some cleanup Don't use std::function destructors --- .github/workflows/unix.yml | 24 + CMakeLists.txt | 1 + docs/source/backends/adios2.rst | 23 + include/openPMD/IO/ADIOS/ADIOS2Auxiliary.hpp | 106 +- include/openPMD/IO/ADIOS/ADIOS2IOHandler.hpp | 346 +++++-- .../IO/ADIOS/ADIOS2PreloadAttributes.hpp | 164 ++++ src/IO/ADIOS/ADIOS2Auxiliary.cpp | 154 ++- src/IO/ADIOS/ADIOS2IOHandler.cpp | 912 +++++++++++++++--- src/IO/ADIOS/ADIOS2PreloadAttributes.cpp | 368 +++++++ test/ParallelIOTest.cpp | 103 ++ test/SerialIOTest.cpp | 42 + 11 files changed, 1988 insertions(+), 255 deletions(-) create mode 100644 include/openPMD/IO/ADIOS/ADIOS2PreloadAttributes.hpp create mode 100644 src/IO/ADIOS/ADIOS2PreloadAttributes.cpp 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