diff --git a/.github/workflows/compile_test.yaml b/.github/workflows/compile_test.yaml index 20fcea2b..b44d0785 100644 --- a/.github/workflows/compile_test.yaml +++ b/.github/workflows/compile_test.yaml @@ -75,7 +75,8 @@ jobs: lz4 \ pkgconf \ libzmq5 \ - sqlite3 + sqlite3 \ + libboost-all-dev pip3 --version && python3 --version gcc --version sudo pip3 install jsonschema cffi ply pyyaml @@ -199,6 +200,11 @@ jobs: # externals: # - spec: "pkg-config@0.29.1" # prefix: /usr + boost: + buildable: False + externals: + - spec: "boost@1.83.0" + prefix: /usr gcc: externals: - spec: gcc@${GCC_VERSION} languages=c,c++ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ff3cfec..65681b58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -300,6 +300,25 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # public header add_subdirectory(src/dyad) #cmake_policy(SET CMP0079 NEW) # In case that we need more control over the target building order +if (DEFINED BOOST_ROOT) + message(STATUS "BOOST_ROOT: " ${BOOST_ROOT}) + set(Boost_NO_SYSTEM_PATHS ON) +else () + if (DEFINED ENV{BOOST_ROOT}) + message(STATUS "ENV BOOST_ROOT: " $ENV{BOOST_ROOT}) + set(Boost_NO_SYSTEM_PATHS ON) + endif () +endif () + +# boost::multi_index is needed and is header-only +find_package(Boost + # HINTS ${BOOST_ROOT} $ENV{BOOST_ROOT} + REQUIRED COMPONENTS) + # regex filesystem system program_options) + +message(STATUS "Boost_INCLUDE_DIRS: " ${Boost_INCLUDE_DIRS}) +message(STATUS "Boost_LIBRARY_DIRS: " ${Boost_LIBRARY_DIRS}) + # Write the configure file configure_file("${CMAKE_SOURCE_DIR}/cmake/configure_files/dyad_config.hpp.in" "${CMAKE_INCLUDE_OUTPUT_DIRECTORY}/dyad/dyad_config.hpp" @ONLY) diff --git a/src/dyad/client/dyad_client.c b/src/dyad/client/dyad_client.c index 42dad921..6dcee5a7 100644 --- a/src/dyad/client/dyad_client.c +++ b/src/dyad/client/dyad_client.c @@ -580,9 +580,7 @@ dyad_rc_t dyad_produce (dyad_ctx_t *restrict ctx, const char *restrict fname) // If the producer-managed path is NULL or empty, then the context is not // valid for a producer operation. So, return DYAD_BADMANAGEDPATH if (ctx->prod_managed_path == NULL) { - DYAD_LOG_ERROR (ctx, - "DYAD CLIENT: No or empty producer managed path was found %s", - ctx->prod_managed_path); + DYAD_LOG_ERROR (ctx, "DYAD CLIENT: No producer managed path was found"); rc = DYAD_RC_BADMANAGEDPATH; goto produce_done; } diff --git a/src/dyad/dtl/margo_dtl.c b/src/dyad/dtl/margo_dtl.c index b757f84d..22d9e334 100644 --- a/src/dyad/dtl/margo_dtl.c +++ b/src/dyad/dtl/margo_dtl.c @@ -370,7 +370,7 @@ dyad_rc_t dyad_dtl_margo_send (const dyad_ctx_t* ctx, void* buf, size_t buflen) margo_free_output (h, &resp); margo_destroy (h); - DYAD_LOG_DEBUG (ctx, "[MARGO DTL] margo_send completed.", buflen); + DYAD_LOG_DEBUG (ctx, "[MARGO DTL] margo_send (buflen=%lu) completed.", buflen); DYAD_C_FUNCTION_END (); return rc; diff --git a/src/dyad/service/CMakeLists.txt b/src/dyad/service/CMakeLists.txt index 8f10d0f5..0d041cf2 100644 --- a/src/dyad/service/CMakeLists.txt +++ b/src/dyad/service/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(flux_module) +add_subdirectory(residency) set(DYAD_EXE "dyad_exe") set(DYAD_EXE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/dyad_exe.c) diff --git a/src/dyad/service/residency/CMakeLists.txt b/src/dyad/service/residency/CMakeLists.txt new file mode 100644 index 00000000..f3a9efdf --- /dev/null +++ b/src/dyad/service/residency/CMakeLists.txt @@ -0,0 +1,57 @@ +set(DYAD_FCACHE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/fcache.cpp) +set(DYAD_FCACHE_PRIVATE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/murmur3.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../utils/utils.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../include/dyad/common/dyad_rc.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../common/dyad_logging.h) +set(DYAD_FCACHE_PUBLIC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/fcache.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/fcache_impl.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/dyad_cache.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../include/dyad/common/dyad_structures.h) + + +add_library(${PROJECT_NAME}_fcache SHARED ${DYAD_FCACHE_SRC} + ${DYAD_FCACHE_PRIVATE_HEADERS} ${DYAD_FCACHE_PUBLIC_HEADERS}) +set_target_properties(${PROJECT_NAME}_fcache PROPERTIES CMAKE_INSTALL_RPATH + "${CMAKE_INSTALL_PREFIX}/${DYAD_LIBDIR}") + +if(DYAD_LOGGER STREQUAL "CPP_LOGGER") + target_link_libraries(${PROJECT_NAME}_fcache PRIVATE ${CPP_LOGGER_LIBRARIES}) +endif() +if(DYAD_PROFILER STREQUAL "DLIO_PROFILER") + target_link_libraries(${PROJECT_NAME}_fcache PRIVATE ${DLIO_PROFILER_LIBRARIES}) +endif() + +target_compile_definitions(${PROJECT_NAME}_fcache PUBLIC DYAD_HAS_CONFIG) +target_include_directories(${PROJECT_NAME}_fcache PUBLIC + ${Boost_INCLUDE_DIRS} + $ + $) + +add_executable(test_fcache test_fcache.cpp) +target_compile_definitions(test_fcache PUBLIC DYAD_HAS_CONFIG) +target_link_libraries(test_fcache PUBLIC ${PROJECT_NAME}_fcache) +target_link_libraries(test_fcache PRIVATE ${PROJECT_NAME}_utils) +target_link_libraries(test_fcache PRIVATE ${PROJECT_NAME}_murmur3) + +if(DYAD_LOGGER STREQUAL "CPP_LOGGER") + target_link_libraries(test_fcache PRIVATE ${CPP_LOGGER_LIBRARIES}) +endif() +if(DYAD_PROFILER STREQUAL "DLIO_PROFILER") + target_link_libraries(test_cmp_fcache PRIVATE ${DLIO_PROFILER_LIBRARIES}) +endif() + + +if (TARGET DYAD_CXX_FLAGS_werror) + target_link_libraries(${PROJECT_NAME}_fcache PRIVATE DYAD_CXX_FLAGS_werror) +endif () + +install( + TARGETS ${PROJECT_NAME}_fcache + EXPORT ${DYAD_EXPORTED_TARGETS} + LIBRARY DESTINATION ${DYAD_INSTALL_LIB_DIR} + ARCHIVE DESTINATION ${DYAD_INSTALL_LIB_DIR} + RUNTIME DESTINATION ${DYAD_INSTALL_BIN_DIR} +) +if(NOT "${DYAD_FCACHE_PUBLIC_HEADERS}" STREQUAL "") + dyad_install_headers("${DYAD_FCACHE_PUBLIC_HEADERS}" ${CMAKE_CURRENT_SOURCE_DIR}) +endif() diff --git a/src/dyad/service/residency/dyad_cache.hpp b/src/dyad/service/residency/dyad_cache.hpp new file mode 100644 index 00000000..d4033980 --- /dev/null +++ b/src/dyad/service/residency/dyad_cache.hpp @@ -0,0 +1,114 @@ +#ifndef DYAD_RESIDENCY_DYAD_CACHE_H +#define DYAD_RESIDENCY_DYAD_CACHE_H + +#if defined(DYAD_HAS_CONFIG) +#include +#else +#error "no config" +#endif + +#include + +#include + +namespace dyad_residency +{ + +template +class DYAD_Cache : public Cache +{ + public: + using Cache::Sets; + using Cache::size; + using Cache::get_num_ways; + using Cache::get_num_sets; + using Cache::get_num_access; + using Cache::get_num_miss; + using Cache::reset_cnts; + using Cache::set_seed; + using Cache::get_level; + using Cache::set_level; + using Cache::set_space_max; + using Cache::get_space_max; + using Cache::get_space_used; + using PermSet = typename std::unordered_set; + + protected: + using Cache::m_ctx; + using Cache::m_set; + PermSet m_perm; ///< Permanent resident set + + virtual unsigned int get_cache_set_id (const std::string& fname) const override; + + public: + DYAD_Cache (unsigned int sz, const dyad_ctx_t* ctx) : Cache (sz, 0, ctx) + { + } + + virtual ~DYAD_Cache () override + { + } + + /// Initialize with a set of permant resident file names + virtual bool init (const std::vector& files) + { + m_perm.clear (); + for (const auto& f : files) { + m_perm.insert (f); + } + return Cache::init (); + } + + /// Allow adding a permant resident file + virtual bool add_perm (const std::string& f) + { + auto res = m_perm.emplace (f); + return res.second; + } + + /// Allow removing a permant resident file + virtual bool del_perm (const std::string& f) + { + remove (f.c_str (), m_ctx); + return (m_perm.erase (f) > 0ul); + } + + virtual bool access (const std::string& fname, size_t fsize = 0ul) override; + + virtual std::ostream& print (std::ostream& os) const override; +}; + +/** + * Hard-coded to use only one set that is fully-associative. + */ +template +unsigned int DYAD_Cache::get_cache_set_id (const std::string& fname) const +{ + return 0u; +} + +template +bool DYAD_Cache::access (const std::string& fname, size_t fsize) +{ + if (m_perm.count (fname) > 0u) { + return true; + } + return Cache::access (fname, fsize); +} + +template +std::ostream& DYAD_Cache::print (std::ostream& os) const +{ + Cache::print (os); + return os; +} + +template +std::ostream& operator<< (std::ostream& os, const DYAD_Cache& cc) +{ + return cc.print (os); +} + +} // end of namespace dyad_residency + +#endif // DYAD_RESIDENCY_DYAD_CACHE_H diff --git a/src/dyad/service/residency/fcache.cpp b/src/dyad/service/residency/fcache.cpp new file mode 100644 index 00000000..69b1e197 --- /dev/null +++ b/src/dyad/service/residency/fcache.cpp @@ -0,0 +1,334 @@ +#include +#include + +#define DYAD_UTIL_LOGGER +#include +#include +#include +#include +#include +#include // unlink +#include + +namespace dyad_residency +{ + +int remove (const char* fname, const dyad_ctx_t* ctx) +{ + dyad_rc_t rc = DYAD_RC_OK; + int lock_fd = -1; + struct flock exclusive_lock; + + if (fname == nullptr) { + return DYAD_RC_BADFIO; + } + + lock_fd = open (fname, O_RDWR); + // DYAD_C_FUNCTION_UPDATE_INT ("lock_fd", lock_fd); + if (lock_fd == -1) { + DYAD_LOG_ERROR (ctx, + "Cannot evict file (%s) that does not exist or that with no permission " + "for!", + fname); + return DYAD_RC_BADFIO; + } + + rc = dyad_excl_flock (ctx, lock_fd, &exclusive_lock); + if (DYAD_IS_ERROR (rc)) { + goto failed_rm; + } + + if (unlink (fname) != 0) { + DYAD_LOG_INFO (ctx, "Error deleting file %s : %s", fname, strerror (errno)); + goto failed_rm; + } + + dyad_release_flock (ctx, lock_fd, &exclusive_lock); + close (lock_fd); + + return DYAD_RC_OK; + +failed_rm:; + dyad_release_flock (ctx, lock_fd, &exclusive_lock); + close (lock_fd); + + return DYAD_RC_BADFIO; +} + +//============================================================================= +// Associative Cache Set +//============================================================================= + +// -------------------------- LRU ------------------------ +bool Set_LRU::lookup (const std::string& fname, id_iterator_t& it) +{ + id_idx_t& index_id = boost::multi_index::get (m_block_set); + it = index_id.find (fname); + return (it != index_id.end ()); +} + +bool Set_LRU::is_full (size_t size_to_add) const +{ + return (m_size == m_block_set.size ()) || (m_space_used + size_to_add > m_space_max); +} + +bool Set_LRU::evict (void) +{ // LRU + if (m_block_set.size () == 0) + return false; + priority_idx_t& index_priority = boost::multi_index::get (m_block_set); + if (!index_priority.empty ()) { + priority_iterator_t it = index_priority.begin (); + const char* fname = it->m_id.c_str (); + DYAD_LOG_INFO (m_ctx, " %s evicts %s from set %u\n", m_level.c_str (), fname, m_id); + // Physically remove the file + if (remove (fname, m_ctx) != DYAD_RC_OK) { + // NOTE: The subtraction of the file size below is to enable testing without actual file + // I/O. Otherwise, load_and_access () should actually load the file into the cache + // storage where it added the file size. Here, removal failed as there is no actual + // file. + m_space_used -= it->m_size; + index_priority.erase (it); + return false; + } else { + if (m_space_used < it->m_size) { + DYAD_LOG_ERROR (m_ctx, + "space_used (%ll)) is less than space being released (%lu)", + m_space_used, + it->m_size); + } + m_space_used -= it->m_size; + } + index_priority.erase (it); + } + return true; +} + +unsigned int Set_LRU::get_priority () +{ + return m_seqno; +} + +void Set_LRU::load_and_access (const std::string& fname, size_t fsize) +{ + m_num_miss++; + + DYAD_LOG_INFO (m_ctx, " %s adds %s to set %u\n", m_level.c_str (), fname.c_str (), m_id); + while (is_full (fsize) && evict ()) { + } + + m_space_used += fsize; + m_block_set.insert (Simple_Block (fname, fsize)); + m_seqno++; +} + +void Set_LRU::access (id_iterator_t& it) +{ + Simple_Block blk = *it; + m_block_set.erase (it); + m_block_set.insert (blk); + m_seqno++; +} + +bool Set_LRU::access (const std::string& fname, size_t fsize) +{ + id_iterator_t it; + if (lookup (fname, it)) { // hit + DYAD_LOG_INFO (m_ctx, + " %s reuses %s from set %u\n", + m_level.c_str (), + fname.c_str (), + m_id); + access (it); + return true; + } else { // miss + load_and_access (fname, fsize); + return false; + } +} + +void Set_LRU::set_space_max (size_t max_space) +{ + m_space_max = max_space; +} + +size_t Set_LRU::get_space_max () const +{ + return m_space_max; +} + +size_t Set_LRU::get_space_used () const +{ + return m_space_used; +} + +std::ostream& Set_LRU::print (std::ostream& os) const +{ + os << "size : " << m_size << std::endl; + os << "space_max : " << m_space_max << std::endl; + os << "space_used : " << m_space_used << std::endl; + os << "num accesses : " << m_seqno << std::endl; + os << "num misses : " << m_num_miss << std::endl; + os << "items : " << std::endl; + + const priority_idx_t& index_priority = boost::multi_index::get (m_block_set); + priority_citerator_t it = index_priority.begin (); + priority_citerator_t itend = index_priority.end (); + + for (; it != itend; it++) { + os << it->m_id << std::endl; + } + return os; +} + +std::ostream& operator<< (std::ostream& os, const Set_LRU& cc) +{ + return cc.print (os); +} + +// -------------------------- MRU ------------------------ +bool Set_MRU::evict (void) +{ // MRU + if (m_block_set.size () == 0) + return false; + priority_idx_t& index_priority = boost::multi_index::get (m_block_set); + if (!index_priority.empty ()) { + auto it = index_priority.end (); + --it; + const char* fname = it->m_id.c_str (); + DYAD_LOG_INFO (m_ctx, " %s evicts %s from set %u\n", m_level.c_str (), fname, m_id); + // Physically remove the file + if (remove (fname, m_ctx) != DYAD_RC_OK) { + m_space_used -= it->m_size; + index_priority.erase (it); + return false; + } else { + if (m_space_used < it->m_size) { + DYAD_LOG_ERROR (m_ctx, + "space_used (%ll)) is less than space being released (%lu)", + m_space_used, + it->m_size); + } + m_space_used -= it->m_size; + } + index_priority.erase (it); + } + return true; +} + +bool Set_MRU::access (const std::string& fname, size_t fsize) +{ + return Set_LRU::access (fname, fsize); +} + +std::ostream& operator<< (std::ostream& os, const Set_MRU& cc) +{ + return cc.print (os); +} + +// -------------------------- Prioritied ------------------------ +bool Set_Prioritized::lookup (const std::string& fname, id_iterator_t& it) +{ + id_idx_t& index_id = boost::multi_index::get (m_block_set); + it = index_id.find (fname); + return (it != index_id.end ()); +} + +bool Set_Prioritized::evict (void) +{ + if (m_block_set.size () == 0) + return false; + priority_idx_t& index_priority = boost::multi_index::get (m_block_set); + if (!index_priority.empty ()) { + priority_iterator_t it = index_priority.begin (); + const char* fname = it->m_id.c_str (); + DYAD_LOG_INFO (m_ctx, " %s evicts %s from set %u\n", m_level.c_str (), fname, m_id); + // Physically remove the file + if (remove (fname, m_ctx) != DYAD_RC_OK) { + m_space_used -= it->m_size; + index_priority.erase (it); + return false; + } else { + if (m_space_used < it->m_size) { + DYAD_LOG_ERROR (m_ctx, + "space_used (%ll)) is less than space being released (%lu)", + m_space_used, + it->m_size); + } + m_space_used -= it->m_size; + } + index_priority.erase (it); + } + return true; +} + +unsigned int Set_Prioritized::get_priority () +{ + return m_seqno; +} + +void Set_Prioritized::load_and_access (const std::string& fname, size_t fsize) +{ + m_num_miss++; + + DYAD_LOG_INFO (m_ctx, " %s adds %s to set %u\n", m_level.c_str (), fname.c_str (), m_id); + while (is_full (fsize) && evict ()) { + } + + m_space_used += fsize; + m_block_set.insert (Ranked_Block (fname, get_priority (), fsize)); + m_seqno++; +} + +void Set_Prioritized::access (id_iterator_t& it) +{ + Ranked_Block blk = *it; + // reassigning the priority + blk.m_priority = get_priority (); + m_block_set.erase (it); + m_block_set.insert (blk); + m_seqno++; +} + +bool Set_Prioritized::access (const std::string& fname, size_t fsize) +{ + id_iterator_t it; + if (lookup (fname, it)) { // hit + DYAD_LOG_INFO (m_ctx, + " %s reuses %s from set %u\n", + m_level.c_str (), + fname.c_str (), + m_id); + access (it); + return true; + } else { // miss + load_and_access (fname, fsize); + return false; + } +} + +std::ostream& Set_Prioritized::print (std::ostream& os) const +{ + os << "size : " << m_size << std::endl; + os << "space_max : " << m_space_max << std::endl; + os << "space_used : " << m_space_used << std::endl; + os << "num accesses : " << m_seqno << std::endl; + os << "num misses : " << m_num_miss << std::endl; + os << "items :" << std::endl; + + const priority_idx_t& index_priority = boost::multi_index::get (m_block_set); + priority_citerator_t it = index_priority.begin (); + priority_citerator_t itend = index_priority.end (); + + for (; it != itend; it++) { + os << it->m_priority << ", " << it->m_id << std::endl; + } + return os; +} + +std::ostream& operator<< (std::ostream& os, const Set_Prioritized& cc) +{ + return cc.print (os); +} + +} // end of namespace dyad_residency diff --git a/src/dyad/service/residency/fcache.hpp b/src/dyad/service/residency/fcache.hpp new file mode 100644 index 00000000..2e808522 --- /dev/null +++ b/src/dyad/service/residency/fcache.hpp @@ -0,0 +1,393 @@ +#ifndef DYAD_RESIDENCY_FCACHE_H +#define DYAD_RESIDENCY_FCACHE_H + +#if defined(DYAD_HAS_CONFIG) +#include +#else +#error "no config" +#endif + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace dyad_residency +{ + +//============================================================================= +// Cache Block (cache line) for LRU +//============================================================================= + +struct Simple_Block { + std::string m_id; // unique id, file name + size_t m_size; // byte size of block or file + + Simple_Block (const std::string& id, size_t sz = 0ul) : m_id (id), m_size (sz) + { + } +}; + +struct Ranked_Block { + std::string m_id; // unique id, file name + unsigned int m_priority; // priority + size_t m_size; // byte size of block or file + + Ranked_Block (const std::string& id, unsigned int priority, size_t sz = 0ul) + : m_id (id), m_priority (priority), m_size (sz) + { + } +}; + +//============================================================================= +// Associative Cache Set +//============================================================================= + +class Set_LRU +{ + protected: + struct id { + }; + struct priority { + }; + typedef boost::multi_index_container< + Simple_Block, + boost::multi_index::indexed_by< + boost::multi_index::hashed_unique< + boost::multi_index::tag, + BOOST_MULTI_INDEX_MEMBER (Simple_Block, std::string, m_id)>, + boost::multi_index::sequenced > > > + LRU_Blocks; + + typedef boost::multi_index::index::type id_idx_t; + typedef boost::multi_index::index::type::iterator id_iterator_t; + typedef boost::multi_index::index::type::const_iterator id_citerator_t; + + typedef boost::multi_index::index::type priority_idx_t; + typedef boost::multi_index::index::type::iterator priority_iterator_t; + typedef boost::multi_index::index::type::const_iterator + priority_citerator_t; + + protected: + /** Cache set capacity (n-way in a set associative cache) in terms of the + * maximum number of blocks */ + unsigned int m_size; + /// Total number of sets in the cache to which this set belongs + unsigned int m_num_sets; + /// Id of this set + unsigned int m_id; + + /** Current access sequence number. This can be used to calculate the numbeer + * of accesses */ + unsigned int m_seqno; + /** In case of cache miss counter reset, keep a record of the current seqno */ + unsigned int m_seqno0; + /// Number of cache misses + unsigned int m_num_miss; + /// Level info of this cache. e,g., L1, or L2. This does not affect operation + std::string m_level; + + /// Total amount of space being used in terms of bytes of data in the files cached + size_t m_space_used; + /// Max amount of space in terms of bytes that can be used to cache data files + size_t m_space_max; + /// DYAD context, used in DYAD APIs for locking, logging, profiling etc. + const dyad_ctx_t* m_ctx; + /// Cache set that can be looked up via the id or the priority of an item + LRU_Blocks m_block_set; + + /// See if a block or item (e.g., filename) is present in this set + virtual bool lookup (const std::string& fname, id_iterator_t& it); + /// Check if the cache space is full. e.g., by the number of items or by the space used + virtual bool is_full (size_t size_to_add) const; + /// Evict a block or item out of the set + virtual bool evict (void); + /// Obtain the priority value to assign to a new block or item being added + virtual unsigned int get_priority (); + /// Add a new block or item into the set + virtual void load_and_access (const std::string& fname, size_t fsize); + /// Update the priority of the block or item being accessed + virtual void access (id_iterator_t& it); + + public: + Set_LRU (unsigned int sz, unsigned int n_sets, unsigned int id, const dyad_ctx_t* ctx) + : m_size (sz), + m_num_sets (n_sets), + m_id (id), + m_seqno (0u), + m_seqno0 (0u), + m_num_miss (0u), + m_space_used (0ul), + m_space_max (0ul), + m_ctx (ctx) + { + } + virtual ~Set_LRU () + { + } + + /// Report the number of cache blocks/items in this set + unsigned int size (void) const + { + return m_size; + } + /// Set the max space in bytes that can host file caches + void set_space_max (size_t max_space); + /// Report the max space in bytes that can host file caches + size_t get_space_max () const; + /// Report the space in bytes currently used by file caches + size_t get_space_used () const; + + /// Report the number of cache accesses since the last reset or beginning + unsigned int get_num_access (void) const + { + return m_seqno - m_seqno0; + } + /// Report the number of cache misses since the last reset or beginning + unsigned int get_num_miss (void) const + { + return m_num_miss; + } + /// Reset the cache miss counter + void reset_cnts (void) + { + m_seqno0 = m_seqno; // Keep a record of when the miss counter is reset + m_num_miss = 0u; // Reset the miss counter + } + /// Report the level, which is simply a meta info that does not affect the operation + std::string get_level (void) const + { + return m_level; + } + /// Set the level, which is simply a meta info that does not affect the operation + void set_level (const std::string& level) + { + m_level = level; + } + + /** Access by a block index, and returns true if hit. Otherwise false. + * And there must be a following access at the lower cache layer. */ + virtual bool access (const std::string& fname, size_t fsize); + + virtual std::ostream& print (std::ostream& os) const; +}; + +std::ostream& operator<< (std::ostream& os, const Set_LRU& sl); + +class Set_MRU : public Set_LRU +{ + protected: + using Set_LRU::id; + using Set_LRU::LRU_Blocks; + using Set_LRU::priority; + + using Set_LRU::id_citerator_t; + using Set_LRU::id_idx_t; + using Set_LRU::id_iterator_t; + using Set_LRU::priority_citerator_t; + using Set_LRU::priority_idx_t; + using Set_LRU::priority_iterator_t; + + using Set_LRU::m_block_set; + using Set_LRU::m_ctx; + + using Set_LRU::access; + using Set_LRU::get_priority; + using Set_LRU::is_full; + using Set_LRU::load_and_access; + using Set_LRU::lookup; + + virtual bool evict (void) override; + + public: + using Set_LRU::get_level; + using Set_LRU::get_num_access; + using Set_LRU::get_num_miss; + using Set_LRU::get_space_max; + using Set_LRU::get_space_used; + using Set_LRU::print; + using Set_LRU::reset_cnts; + using Set_LRU::set_level; + using Set_LRU::set_space_max; + using Set_LRU::size; + + Set_MRU (unsigned int sz, unsigned int n_sets, unsigned int id, const dyad_ctx_t* ctx) + : Set_LRU (sz, n_sets, id, ctx) + { + } + virtual ~Set_MRU () override + { + } + virtual bool access (const std::string& fname, size_t fsize) override; +}; + +std::ostream& operator<< (std::ostream& os, const Set_MRU& sm); + +class Set_Prioritized : public Set_LRU +{ + protected: + struct id { + }; + struct priority { + }; + + typedef boost::multi_index_container< + Ranked_Block, + boost::multi_index::indexed_by< + boost::multi_index::hashed_unique< + boost::multi_index::tag, + BOOST_MULTI_INDEX_MEMBER (Ranked_Block, std::string, m_id)>, + boost::multi_index::ordered_non_unique< + boost::multi_index::tag, + BOOST_MULTI_INDEX_MEMBER (Ranked_Block, unsigned int, m_priority)> > > + Prio_Blocks; + + typedef boost::multi_index::index::type id_idx_t; + typedef boost::multi_index::index::type::iterator id_iterator_t; + typedef boost::multi_index::index::type::const_iterator id_citerator_t; + + typedef boost::multi_index::index::type priority_idx_t; + typedef boost::multi_index::index::type::iterator priority_iterator_t; + typedef boost::multi_index::index::type::const_iterator + priority_citerator_t; + + using Set_LRU::is_full; + using Set_LRU::m_ctx; + + private: + using Set_LRU::access; + using Set_LRU::lookup; + + protected: + Prio_Blocks m_block_set; + + virtual bool lookup (const std::string& fname, id_iterator_t& it); + virtual bool evict (void) override; + virtual unsigned int get_priority () override; + virtual void load_and_access (const std::string& fname, size_t fsize) override; + virtual void access (id_iterator_t& it); + + public: + using Set_LRU::get_level; + using Set_LRU::get_num_access; + using Set_LRU::get_num_miss; + using Set_LRU::get_space_max; + using Set_LRU::get_space_used; + using Set_LRU::print; + using Set_LRU::reset_cnts; + using Set_LRU::set_level; + using Set_LRU::set_space_max; + using Set_LRU::size; + + Set_Prioritized (unsigned int sz, unsigned int n_sets, unsigned int id, const dyad_ctx_t* ctx) + : Set_LRU (sz, n_sets, id, ctx) + { + } + virtual ~Set_Prioritized () override + { + } + virtual bool access (const std::string& fname, size_t fsize) override; + virtual std::ostream& print (std::ostream& os) const override; +}; + +std::ostream& operator<< (std::ostream& os, const Set_Prioritized& sp); + +//============================================================================= +// Set Associative Cache +//============================================================================= + +/// Set associative cache model +template +class Cache +{ + public: + using Sets = typename std::vector; + + protected: + unsigned int m_size; ///< Capacity of cache in terms of the number of blocks + unsigned int m_ways; ///< For x-way set-associativity + unsigned int m_num_sets; ///< Number of sets + unsigned int m_seed; ///< Seed used to compute hash in determining cache set + std::string m_level; ///< Level of this cache. e,g., L2. + const dyad_ctx_t* m_ctx; ///< DYAD context + + Sets m_set; + + /** + * Choose the set to search for the item. It is based on the modulo operation on + * the hashing value of filename to search. + */ + virtual unsigned int get_cache_set_id (const std::string& fname) const; + + public: + Cache (unsigned int sz, unsigned int w, const dyad_ctx_t* ctx); + virtual ~Cache () + { + } + + /// Initialize. Currently only checks if the dyad context pointer is valid. + virtual bool init () + { + if (m_ctx == nullptr) { + return false; + } + return true; + } + unsigned int size (void) const + { + return m_size; + } + unsigned int get_num_ways (void) const + { + return m_ways; + } + unsigned int get_num_sets (void) const + { + return m_num_sets; + } + unsigned int get_num_access (void) const; + unsigned int get_num_miss (void) const; + + void reset_cnts (void); + void set_seed (unsigned int seed) + { + m_seed = seed; + } + std::string get_level (void) const + { + return m_level; + } + void set_level (const std::string& level); + + /// Specify the max cache space for each set + virtual bool set_space_max (const std::vector& space_max); + /// Report the max cache space defined for each set + std::vector get_space_max () const; + /// Report the current cache space used by each set + std::vector get_space_used () const; + + /// Returns true if hit. Otherwise false. + virtual bool access (const std::string& fname, size_t fsize = 0ul); + + virtual std::ostream& print (std::ostream& os) const; +}; + +template +std::ostream& operator<< (std::ostream& os, const Cache& cc); + +/// Physically remove a file +int remove (const char* fname, const dyad_ctx_t* ctx); + +} // end of namespace dyad_residency + +#include + +#endif // DYAD_RESIDENCY_FCACHE_H diff --git a/src/dyad/service/residency/fcache_impl.hpp b/src/dyad/service/residency/fcache_impl.hpp new file mode 100644 index 00000000..53d8ac84 --- /dev/null +++ b/src/dyad/service/residency/fcache_impl.hpp @@ -0,0 +1,172 @@ +#ifndef DYAD_RESIDENCY_FCACHE_IMPL_H +#define DYAD_RESIDENCY_FCACHE_IMPL_H +#include +#include + +namespace dyad_residency +{ + +//============================================================================= +// Cache +//============================================================================= + +template +Cache::Cache (unsigned int sz, unsigned int w, const dyad_ctx_t* ctx) + : m_size (sz), m_ways (w), m_seed (104729u), m_ctx (ctx) +{ + if (m_ways == 0) { // fully associative + m_num_sets = 1; + m_ways = m_size; + } else { + m_num_sets = m_size / m_ways; + } + + assert (m_size == m_ways * m_num_sets); + + m_set.reserve (m_num_sets); + for (unsigned int i = 0u; i < m_num_sets; i++) { + m_set.emplace_back (Set (m_ways, m_num_sets, i, ctx)); + } +} + +template +unsigned int Cache::get_num_access (void) const +{ + unsigned int tot_num_access = 0u; + for (unsigned int i = 0; i < m_num_sets; i++) { + tot_num_access += m_set[i].get_num_access (); + } + return tot_num_access; +} + +template +unsigned int Cache::get_num_miss (void) const +{ + unsigned int tot_num_miss = 0u; + for (unsigned int i = 0; i < m_num_sets; i++) { + tot_num_miss += m_set[i].get_num_miss (); + } + return tot_num_miss; +} + +template +void Cache::reset_cnts (void) +{ + for (unsigned int i = 0; i < m_num_sets; i++) { + m_set[i].reset_cnts (); + } +} + +template +unsigned int Cache::get_cache_set_id (const std::string& fname) const +{ +#if 0 + return std::stoi (fname) % m_num_sets; +#else + uint32_t hash[4] = {0u}; // Output for the hash + + if (fname.empty ()) { + // TODO: report the exception + return 0u; + } + const char* str = fname.c_str (); + size_t str_len = fname.size (); + char buf[256] = {'\0'}; + + if (str_len < 128ul) { + memcpy (buf, str, str_len); + memset (buf + str_len, '@', 128ul - str_len); + buf[128u] = '\0'; + str_len = 128ul; + str = buf; + } + + MurmurHash3_x64_128 (str, strlen (str), m_seed, hash); + return (hash[0] ^ hash[1] ^ hash[2] ^ hash[3]) % m_num_sets; +#endif +} + +template +void Cache::set_level (const std::string& level) +{ + m_level = level; + for (unsigned int i = 0; i < m_num_sets; i++) { + m_set[i].set_level (level); + } +} + +template +bool Cache::set_space_max (const std::vector& space_max) +{ + if (space_max.size () != m_num_sets) { + return false; + } + for (unsigned int i = 0; i < m_num_sets; i++) { + m_set[i].set_space_max (space_max[i]); + } + return true; +} + +template +std::vector Cache::get_space_max () const +{ + std::vector space_max (m_num_sets, 0ul); + for (unsigned int i = 0; i < m_num_sets; i++) { + space_max[i] = m_set[i].get_space_max (); + } + return space_max; +} + +template +std::vector Cache::get_space_used () const +{ + std::vector space_used (m_num_sets, 0ul); + for (unsigned int i = 0; i < m_num_sets; i++) { + space_used[i] = m_set[i].get_space_used (); + } + return space_used; +} + +template +bool Cache::access (const std::string& fname, size_t fsize) +{ + const unsigned int set_id = get_cache_set_id (fname); + + return m_set[set_id].access (fname, fsize); +} + +template +std::ostream& Cache::print (std::ostream& os) const +{ + const auto space_max = get_space_max (); + const auto space_used = get_space_used (); + + os << "==========================================" << std::endl; + os << "size: " << m_size << std::endl; + os << "space: "; + for (unsigned int i = 0; i < m_num_sets; ++i) { + os << std::string ("[") + std::to_string (space_used[i]) + '/' + + std::to_string (space_max[i]) + "], "; + } + os << std::endl; + os << "nAcc: " << get_num_access () << std::endl; + os << "nMiss: " << get_num_miss () << std::endl; + + typename Sets::const_iterator it = m_set.begin (); + typename Sets::const_iterator itend = m_set.end (); + for (unsigned int i = 0; it != itend; it++, i++) { + os << "-------- set " << i << " ----------" << std::endl; + os << *it << std::endl; + } + os << "==========================================" << std::endl; + return os; +} + +template +std::ostream& operator<< (std::ostream& os, const Cache& cc) +{ + return cc.print (os); +} + +} // end of namespace dyad_residency +#endif // DYAD_RESIDENCY_FCACHE_IMPL_H diff --git a/src/dyad/service/residency/test_fcache.cpp b/src/dyad/service/residency/test_fcache.cpp new file mode 100644 index 00000000..6f8c7337 --- /dev/null +++ b/src/dyad/service/residency/test_fcache.cpp @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace dyad_residency +{ + +enum Set_Type { Set_LRU_, Set_MRU_, Set_Prio_, Set_End_ }; + +struct file_item { + std::string fname; + size_t fsize; + file_item (const std::string& fn, size_t fsz) : fname (fn), fsize (fsz) + { + } + + operator std::string const () + { + return fname; + } +}; +typedef std::vector Access; + +template