From b914fbb8bfedf9ada9fd4881c02d4f3a205e5638 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Tue, 3 Dec 2024 23:10:58 -0800 Subject: [PATCH 01/16] Implement eviction for persistent cache --- sycl/include/sycl/detail/os_util.hpp | 11 + sycl/source/detail/config.hpp | 50 +++++ sycl/source/detail/os_util.cpp | 62 +++++- .../detail/persistent_device_code_cache.cpp | 209 +++++++++++++++++- .../detail/persistent_device_code_cache.hpp | 14 ++ sycl/unittests/config/ConfigTests.cpp | 62 ++++++ 6 files changed, 402 insertions(+), 6 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index c77cdb4913c36..afc4c4b0b48c1 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -15,6 +15,7 @@ #include // for size_t #include // for string #include // for stat +#include // for vector #ifdef _WIN32 #define __SYCL_RT_OS_WINDOWS @@ -88,6 +89,16 @@ class __SYCL_EXPORT OSUtil { return !stat(Path.c_str(), &Stat); #endif } + + // Get size of directory in bytes. + static size_t getDirectorySize(const std::string &Path); + + // Get size of file in bytes. + static size_t getFileSize(const std::string &Path); + + // Get list of all files in the directory along with its last access time. + static std::vector> + getFilesWithAccessTime(const std::string &Path); }; } // namespace detail diff --git a/sycl/source/detail/config.hpp b/sycl/source/detail/config.hpp index ace69d0a9420e..f92744e90c852 100644 --- a/sycl/source/detail/config.hpp +++ b/sycl/source/detail/config.hpp @@ -806,6 +806,56 @@ template <> class SYCLConfig { } }; +// SYCL_CACHE_MAX_SIZE accepts an integer that specifies +// the maximum size of the on-disk Program cache. +// Cache eviction is performed when the cache size exceeds the threshold. +// The thresholds are specified in bytes. +// The default value is "0" which means that eviction is disabled. +template <> class SYCLConfig { + using BaseT = SYCLConfigBase; + +public: + static int get() { return getCachedValue(); } + static void reset() { (void)getCachedValue(true); } + + static int getProgramCacheSize() { return getCachedValue(); } + + static bool isPersistentCacheEvictionEnabled() { + return getProgramCacheSize() > 0; + } + +private: + static int getCachedValue(bool ResetCache = false) { + const auto Parser = []() { + const char *ValStr = BaseT::getRawValue(); + + // Disable eviction by default. + if (!ValStr) + return 0; + + int CacheSize = 0; + try { + CacheSize = std::stoi(ValStr); + if (CacheSize < 0) + throw INVALID_CONFIG_EXCEPTION(BaseT, "Value must be non-negative"); + } catch (...) { + std::string Msg = + std::string{"Invalid input to SYCL_CACHE_MAX_SIZE. Please try " + "a positive integer."}; + throw exception(make_error_code(errc::runtime), Msg); + } + + return CacheSize; + }; + + static auto EvictionThresholds = Parser(); + if (ResetCache) + EvictionThresholds = Parser(); + + return EvictionThresholds; + } +}; + #undef INVALID_CONFIG_EXCEPTION } // namespace detail diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 018ba97cff05c..a55c3f201e32e 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -10,7 +10,6 @@ #include #include - #if __GNUC__ && __GNUC__ < 8 // Don't include for GCC versions less than 8 #else @@ -277,6 +276,67 @@ int OSUtil::makeDir(const char *Dir) { return 0; } +// Get size of a directory in bytes. +size_t OSUtil::getDirectorySize(const std::string &Path) { + + size_t Size = 0; +#if __GNUC__ && __GNUC__ < 8 + // Should we worry about this case? + assert(false && "getDirectorySize is not implemented for GCC < 8"); +#else + // Use C++17 filesystem API to get the size of the directory. + for (const auto &entry : + std::filesystem::recursive_directory_iterator(Path)) { + if (entry.is_regular_file()) + Size += entry.file_size(); + } +#endif + return Size; +} + +// Get size of file in bytes. +size_t OSUtil::getFileSize(const std::string &Path) { + size_t Size = 0; +#if __GNUC__ && __GNUC__ < 8 + // Should we worry about this case? + assert(false && "getFileSize is not implemented for GCC < 8"); +#else + std::filesystem::path FilePath(Path); + if (std::filesystem::exists(FilePath) && + std::filesystem::is_regular_file(FilePath)) + Size = std::filesystem::file_size(FilePath); +#endif + return Size; +} + +// Get list of all files in the directory along with its last access time. +std::vector> +OSUtil::getFilesWithAccessTime(const std::string &Path) { + std::vector> Files; +#if __GNUC__ && __GNUC__ < 8 + // Should we worry about this case? + assert(false && "getFilesWithAccessTime is not implemented for GCC < 8"); +#else + for (const auto &entry : + std::filesystem::recursive_directory_iterator(Path)) { + if (entry.is_regular_file()) { +#if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) + struct stat StatBuf; + if (stat(entry.path().c_str(), &StatBuf) == 0) + Files.push_back({StatBuf.st_atime, entry.path().string()}); +#elif defined(__SYCL_RT_OS_WINDOWS) + WIN32_FILE_ATTRIBUTE_DATA FileData; + if (GetFileAttributesEx(entry.path().c_str(), GetFileExInfoStandard, + &FileData)) + Files.push_back( + {FileData.ftLastAccessTime.dwLowDateTime, entry.path().string()}); +#endif // __SYCL_RT_OS + } + } +#endif + return Files; +} + } // namespace detail } // namespace _V1 } // namespace sycl diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 205ebd7d42d26..d16dd0a0f92ae 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -178,6 +178,183 @@ getProgramBinaryData(const ur_program_handle_t &NativePrg, return Result; } +// Check if cache_size.lock file is present in the cache root directory. +// If not, create it and populate it with the size of the cache directory. +void PersistentDeviceCodeCache::repopulateCacheSizeFile( + const std::string &CacheRoot) { + const std::string CacheSizeFileName = "cache_size.txt"; + const std::string CacheSizeFile = CacheRoot + "/" + CacheSizeFileName; + + // If the cache size file is not present, calculate the size of the cache size + // directory and write it to the file. + if (!OSUtil::isPathPresent(CacheSizeFile)) { + PersistentDeviceCodeCache::trace( + "Cache size file not present. Creating one."); + // Calculate the size of the cache directory. + size_t CacheSize = OSUtil::getDirectorySize(CacheRoot); + + std::cerr << "Cache size: " << CacheSize << std::endl; + + // Take the lock to write the cache size to the file. + { + LockCacheItem Lock{CacheSizeFile}; + if (!Lock.isOwned()) { + // If some other process is writing the cache size, do not write it. + PersistentDeviceCodeCache::trace("Didnot create the cache size file. " + "Some other process is creating one."); + return; + } else { + std::ofstream FileStream{CacheSizeFile}; + FileStream << CacheSize; + FileStream.close(); + } + } + } +} + +void PersistentDeviceCodeCache::evictItemsFromCache( + const std::string &CacheRoot, size_t CacheSize, size_t MaxCacheSize) { + PersistentDeviceCodeCache::trace("Cache eviction triggered."); + + // Create a file eviction_in_progress.lock to indicate that eviction is in + // progress. This file is used to prevent two processes from evicting the + // cache at the same time. + LockCacheItem Lock{CacheRoot + "eviction_in_progress"}; + if (!Lock.isOwned()) { + // If some other process is evicting the cache, return. + PersistentDeviceCodeCache::trace( + "Another process is evicting the cache. Returning."); + return; + } + + // Get the list of all files in the cache directory along with their last + // access time. + auto FilesWithAccessTime = OSUtil::getFilesWithAccessTime(CacheRoot); + + // Sort the files in the cache directory based on their last access time. + std::sort(FilesWithAccessTime.begin(), FilesWithAccessTime.end(), + [](const std::pair &A, + const std::pair &B) { + return A.first < B.first; + }); + + // Evict files from the cache directory until the cache size is less than the + // threshold. + size_t CurrCacheSize = CacheSize; + for (const auto &File : FilesWithAccessTime) { + + // Remove .bin/.src/.lock extension from the file name. + const std::string FileNameWOExt = + File.second.substr(0, File.second.find_last_of(".")); + const std::string BinFile = FileNameWOExt + ".bin"; + const std::string SrcFile = FileNameWOExt + ".src"; + + while (OSUtil::isPathPresent(BinFile) && OSUtil::isPathPresent(SrcFile)) { + + // This is used to prevent race between processes trying to read the file + // while it is being evicted. + if (LockCacheItem::isLocked(FileNameWOExt + "_reader")) { + // If some other process is reading the file, spin and wait. + continue; + } + + // Take a lock on the file to prevent other processes from reading the + // file. + LockCacheItem Lock{FileNameWOExt}; + + auto RemoveFileAndSubtractSize = + [&CurrCacheSize](const std::string &FileName) { + auto FileSize = OSUtil::getFileSize(FileName); + if (std::remove(FileName.c_str())) { + PersistentDeviceCodeCache::trace("Failed to remove file: " + + FileName); + } else { + PersistentDeviceCodeCache::trace("File removed: " + FileName); + CurrCacheSize -= FileSize; + } + }; + + RemoveFileAndSubtractSize(SrcFile); + RemoveFileAndSubtractSize(BinFile); + } + + // If the cache size is less than the threshold, break. + if (CurrCacheSize <= MaxCacheSize) + break; + } + + // Update the cache size file with the new cache size. + { + const std::string CacheSizeFileName = "cache_size.txt"; + const std::string CacheSizeFile = CacheRoot + "/" + CacheSizeFileName; + while (true) { + LockCacheItem Lock{CacheSizeFile}; + if (!Lock.isOwned()) { + // If some other process is writing the cache size, spin lock. + continue; + } else { + std::fstream FileStream; + FileStream.open(CacheSizeFile, std::ios::out | std::ios::trunc); + FileStream << CurrCacheSize; + FileStream.close(); + + PersistentDeviceCodeCache::trace( + "Updating the cache size file after eviction. New size: " + + std::to_string(CurrCacheSize)); + break; + } + } + } +} + +// Update the cache size file and trigger cache eviction if needed. +void PersistentDeviceCodeCache::updateCacheFileSizeAndTriggerEviction( + const std::string &CacheRoot, size_t ItemSize) { + + const std::string CacheSizeFileName = "cache_size.txt"; + const std::string CacheSizeFile = CacheRoot + "/" + CacheSizeFileName; + size_t CurrentCacheSize = 0; + // Read the cache size from the file. + while (true) { + LockCacheItem Lock{CacheSizeFile}; + if (!Lock.isOwned()) { + // If some other process is writing the cache size, spin lock. + continue; + } else { + PersistentDeviceCodeCache::trace("Updating the cache size file."); + std::fstream FileStream; + FileStream.open(CacheSizeFile, std::ios::in); + + // Read the cache size from the file; + std::string line; + if (std::getline(FileStream, line)) { + CurrentCacheSize = std::stoull(line); + } + FileStream.close(); + + CurrentCacheSize += ItemSize; + + // Write the updated cache size to the file. + FileStream.open(CacheSizeFile, std::ios::out | std::ios::trunc); + std::cerr << "Current cache size: " << CurrentCacheSize << std::endl; + FileStream << CurrentCacheSize; + FileStream.close(); + break; + } + } + + // Check if the cache size exceeds the threshold and trigger cache eviction if + // needed. + if (!SYCLConfig::isPersistentCacheEvictionEnabled()) + return; + + size_t MaxCacheSize = SYCLConfig::getProgramCacheSize(); + if (CurrentCacheSize > MaxCacheSize) { + // Trigger cache eviction. + evictItemsFromCache(CacheRoot, CurrentCacheSize, MaxCacheSize); + } +} + /* Stores built program in persistent cache. We will put the binary for each * device in the list to a separate file. */ @@ -190,8 +367,13 @@ void PersistentDeviceCodeCache::putItemToDisc( if (!areImagesCacheable(Imgs)) return; + repopulateCacheSizeFile(getRootDir()); + std::vector SortedImgs = getSortedImages(Imgs); auto BinaryData = getProgramBinaryData(NativePrg, Devices); + + // Total size of the item that we just wrote to the cache. + size_t TotalSize = 0; for (size_t DeviceIndex = 0; DeviceIndex < Devices.size(); DeviceIndex++) { // If we don't have binary for the device, skip it. if (BinaryData[DeviceIndex].empty()) @@ -202,9 +384,11 @@ void PersistentDeviceCodeCache::putItemToDisc( if (DirName.empty()) return; + std::string FileName; + bool IsWriteSuccess = false; try { OSUtil::makeDir(DirName.c_str()); - std::string FileName = getUniqueFilename(DirName); + FileName = getUniqueFilename(DirName); LockCacheItem Lock{FileName}; if (Lock.isOwned()) { std::string FullFileName = FileName + ".bin"; @@ -212,6 +396,7 @@ void PersistentDeviceCodeCache::putItemToDisc( trace("device binary has been cached: " + FullFileName); writeSourceItem(FileName + ".src", Devices[DeviceIndex], SortedImgs, SpecConsts, BuildOptionsString); + IsWriteSuccess = true; } else { PersistentDeviceCodeCache::trace("cache lock not owned " + FileName); } @@ -224,7 +409,16 @@ void PersistentDeviceCodeCache::putItemToDisc( std::string("error outputting persistent cache: ") + std::strerror(errno)); } + + if (IsWriteSuccess) { + TotalSize += OSUtil::getFileSize(FileName + ".src"); + TotalSize += OSUtil::getFileSize(FileName + ".bin"); + } } + + // Update the cache size file and trigger cache eviction if needed. + if (TotalSize) + updateCacheFileSizeAndTriggerEviction(getRootDir(), TotalSize); } void PersistentDeviceCodeCache::putCompiledKernelToDisc( @@ -292,6 +486,11 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( while (OSUtil::isPathPresent(FileName + ".bin") || OSUtil::isPathPresent(FileName + ".src")) { + // Create a file, _reader.lock, to indicate that the file is + // being read. This file is used to prevent another process from evicting + // the cache entry while it is being read. + LockCacheItem Lock{FileName + "_reader"}; + if (!LockCacheItem::isLocked(FileName) && isCacheItemSrcEqual(FileName + ".src", Devices[DeviceIndex], SortedImgs, SpecConsts, BuildOptionsString)) { @@ -379,8 +578,8 @@ void PersistentDeviceCodeCache::writeBinaryDataToFile( trace("Failed to write to binary file " + FileName); } -/* Read built binary from persistent cache. Each persistent cache file contains - * binary for a single device. Format: BinarySize, Binary +/* Read built binary from persistent cache. Each persistent cache file + * contains binary for a single device. Format: BinarySize, Binary */ std::vector PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) { @@ -401,8 +600,8 @@ PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) { } /* Writing cache item key sources to be used for reliable identification - * Format: Four pairs of [size, value] for device, build options, specialization - * constant values, device code SPIR-V images. + * Format: Four pairs of [size, value] for device, build options, + * specialization constant values, device code SPIR-V images. */ void PersistentDeviceCodeCache::writeSourceItem( const std::string &FileName, const device &Device, diff --git a/sycl/source/detail/persistent_device_code_cache.hpp b/sycl/source/detail/persistent_device_code_cache.hpp index 78441a251aa75..ff248cac7648c 100644 --- a/sycl/source/detail/persistent_device_code_cache.hpp +++ b/sycl/source/detail/persistent_device_code_cache.hpp @@ -214,6 +214,20 @@ class PersistentDeviceCodeCache { if (traceEnabled) std::cerr << "[kernel_compiler Persistent Cache]: " << msg << std::endl; } + +private: + // Check if cache_size.lock file is present in the cache root directory. + // If not, create it and populate it with the size of the cache directory. + static void repopulateCacheSizeFile(const std::string &CacheRoot); + + // Update the cache size file and trigger cache eviction if needed. + static void + updateCacheFileSizeAndTriggerEviction(const std::string &CacheRoot, + size_t CacheSize); + + // Evict LRU items from the cache to make space for new items. + static void evictItemsFromCache(const std::string &CacheRoot, + size_t CacheSize, size_t MaxCacheSize); }; } // namespace detail } // namespace _V1 diff --git a/sycl/unittests/config/ConfigTests.cpp b/sycl/unittests/config/ConfigTests.cpp index 756a340c8f82d..0f990bc3c9847 100644 --- a/sycl/unittests/config/ConfigTests.cpp +++ b/sycl/unittests/config/ConfigTests.cpp @@ -387,3 +387,65 @@ TEST(ConfigTests, CheckSyclCacheEvictionThresholdTest) { InMemEvicType::reset(); TestConfig(0); } + +// SYCL_CACHE_MAX_SIZE accepts an integer that specifies +// the maximum size of the persistent Program cache. +// Cache eviction is performed when the cache size exceeds the threshold. +// The thresholds are specified in bytes. +// The default value is "0" which means that eviction is disabled. +TEST(ConfigTests, CheckPersistentCacheEvictionThresholdTest) { + + using OnDiskEvicType = sycl::detail::SYCLConfig; + + // Lambda to test parsing of SYCL_CACHE_MAX_SIZE. + auto TestConfig = [](int expectedProgramCacheSize) { + EXPECT_EQ(expectedProgramCacheSize, OnDiskEvicType::getProgramCacheSize()); + EXPECT_EQ(expectedProgramCacheSize > 0, + OnDiskEvicType::isPersistentCacheEvictionEnabled()); + }; + + // Lambda to set SYCL_CACHE_MAX_SIZE. + auto SetSyclDiskCacheEvictionThresholdEnv = [](const char *value) { +#ifdef _WIN32 + _putenv_s("SYCL_CACHE_MAX_SIZE", value); +#else + setenv("SYCL_CACHE_MAX_SIZE", value, 1); +#endif + }; + + // Lambda to test invalid inputs. An exception should be thrown + // when parsing invalid values. + auto TestInvalidValues = [&](const char *value, const char *errMsg) { + SetSyclDiskCacheEvictionThresholdEnv(value); + try { + OnDiskEvicType::reset(); + TestConfig(0); + FAIL() << errMsg; + } catch (...) { + } + }; + + // Test eviction threshold with zero. + SetSyclDiskCacheEvictionThresholdEnv("0"); + sycl::detail::readConfig(true); + TestConfig(0); + + // Test invalid values. + TestInvalidValues("-1", "Should throw exception for negative value"); + TestInvalidValues("a", "Should throw exception for non-integer value"); + + // Test valid values. + SetSyclDiskCacheEvictionThresholdEnv("1024"); + OnDiskEvicType::reset(); + TestConfig(1024); + + // When SYCL_CACHE_MAX_SIZE is not set, it should default to + // 0:0:0. +#ifdef _WIN32 + _putenv_s("SYCL_CACHE_MAX_SIZE", ""); +#else + unsetenv("SYCL_CACHE_MAX_SIZE"); +#endif + OnDiskEvicType::reset(); + TestConfig(0); +} From e1294421d54e33a97d8fa9aad9b67d18e6eb5457 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Thu, 5 Dec 2024 09:59:30 -0800 Subject: [PATCH 02/16] Add unit tests --- sycl/include/sycl/detail/os_util.hpp | 2 + sycl/source/detail/os_util.cpp | 28 ++-- .../detail/persistent_device_code_cache.cpp | 15 ++- .../detail/persistent_device_code_cache.hpp | 11 +- .../PersistentDeviceCodeCache.cpp | 120 ++++++++++++++++++ 5 files changed, 155 insertions(+), 21 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index afc4c4b0b48c1..f5abc19597155 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -99,6 +99,8 @@ class __SYCL_EXPORT OSUtil { // Get list of all files in the directory along with its last access time. static std::vector> getFilesWithAccessTime(const std::string &Path); + + static size_t DirSizeVar; }; } // namespace detail diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index a55c3f201e32e..e6872a0025373 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -26,6 +26,7 @@ #include #include #include +#include // for ftw - file tree walk #include // for dirname #include #include // for PATH_MAX @@ -276,22 +277,25 @@ int OSUtil::makeDir(const char *Dir) { return 0; } +size_t OSUtil::DirSizeVar = 0; // Get size of a directory in bytes. size_t OSUtil::getDirectorySize(const std::string &Path) { - size_t Size = 0; -#if __GNUC__ && __GNUC__ < 8 - // Should we worry about this case? - assert(false && "getDirectorySize is not implemented for GCC < 8"); -#else - // Use C++17 filesystem API to get the size of the directory. - for (const auto &entry : - std::filesystem::recursive_directory_iterator(Path)) { - if (entry.is_regular_file()) - Size += entry.file_size(); - } + DirSizeVar = 0; +// Use ftw for Linux and darwin as they support posix. +#if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) + auto SumSize = + [](const char *Fpath, const struct stat *StatBuf, int TypeFlag) { + if (TypeFlag == FTW_F) + DirSizeVar += StatBuf->st_size; + return 0; + }; + + if (ftw(Path.c_str(),SumSize, 1) == -1) + std::cerr << "Failed to get directory size: " << Path << std::endl; #endif - return Size; + + return DirSizeVar; } // Get size of file in bytes. diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index d16dd0a0f92ae..9f26ca173ade0 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -182,6 +182,11 @@ getProgramBinaryData(const ur_program_handle_t &NativePrg, // If not, create it and populate it with the size of the cache directory. void PersistentDeviceCodeCache::repopulateCacheSizeFile( const std::string &CacheRoot) { + + // No need to store cache size if eviction is disabled. + if (!isEvictionEnabled()) + return; + const std::string CacheSizeFileName = "cache_size.txt"; const std::string CacheSizeFile = CacheRoot + "/" + CacheSizeFileName; @@ -193,8 +198,6 @@ void PersistentDeviceCodeCache::repopulateCacheSizeFile( // Calculate the size of the cache directory. size_t CacheSize = OSUtil::getDirectorySize(CacheRoot); - std::cerr << "Cache size: " << CacheSize << std::endl; - // Take the lock to write the cache size to the file. { LockCacheItem Lock{CacheSizeFile}; @@ -311,6 +314,10 @@ void PersistentDeviceCodeCache::evictItemsFromCache( void PersistentDeviceCodeCache::updateCacheFileSizeAndTriggerEviction( const std::string &CacheRoot, size_t ItemSize) { + // No need to store cache size if eviction is disabled. + if (!isEvictionEnabled()) + return; + const std::string CacheSizeFileName = "cache_size.txt"; const std::string CacheSizeFile = CacheRoot + "/" + CacheSizeFileName; size_t CurrentCacheSize = 0; @@ -336,7 +343,6 @@ void PersistentDeviceCodeCache::updateCacheFileSizeAndTriggerEviction( // Write the updated cache size to the file. FileStream.open(CacheSizeFile, std::ios::out | std::ios::trunc); - std::cerr << "Current cache size: " << CurrentCacheSize << std::endl; FileStream << CurrentCacheSize; FileStream.close(); break; @@ -345,9 +351,6 @@ void PersistentDeviceCodeCache::updateCacheFileSizeAndTriggerEviction( // Check if the cache size exceeds the threshold and trigger cache eviction if // needed. - if (!SYCLConfig::isPersistentCacheEvictionEnabled()) - return; - size_t MaxCacheSize = SYCLConfig::getProgramCacheSize(); if (CurrentCacheSize > MaxCacheSize) { // Trigger cache eviction. diff --git a/sycl/source/detail/persistent_device_code_cache.hpp b/sycl/source/detail/persistent_device_code_cache.hpp index ff248cac7648c..392335b9d0604 100644 --- a/sycl/source/detail/persistent_device_code_cache.hpp +++ b/sycl/source/detail/persistent_device_code_cache.hpp @@ -118,9 +118,6 @@ class PersistentDeviceCodeCache { const std::vector &SortedImgs, const SerializedObj &SpecConsts, const std::string &BuildOptionsString); - /* Returns the path to directory storing persistent device code cache.*/ - static std::string getRootDir(); - /* Form string representing device version */ static std::string getDeviceIDString(const device &Device); @@ -152,6 +149,9 @@ class PersistentDeviceCodeCache { 1024 * 1024 * 1024; public: + /* Returns the path to directory storing persistent device code cache.*/ + static std::string getRootDir(); + /* Check if on-disk cache enabled. */ static bool isEnabled(); @@ -228,6 +228,11 @@ class PersistentDeviceCodeCache { // Evict LRU items from the cache to make space for new items. static void evictItemsFromCache(const std::string &CacheRoot, size_t CacheSize, size_t MaxCacheSize); + + // Check if eviction is enabled. + static bool isEvictionEnabled() { + return SYCLConfig::isPersistentCacheEvictionEnabled(); + } }; } // namespace detail } // namespace _V1 diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index 1cd0fcee45dc7..78383d8d43562 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -135,6 +135,12 @@ class PersistentDeviceCodeCache SYCLCachePersistentChanged = true; } + // Set SYCL_CACHE_MAX_SIZE. + void SetDiskCacheEvictionEnv(const char *NewValue) { + set_env("SYCL_CACHE_MAX_SIZE", NewValue); + sycl::detail::SYCLConfig::reset(); + } + void AppendToSYCLCacheDirEnv(const char *SubDir) { std::string NewSYCLCacheDirPath{RootSYCLCacheDir}; if (NewSYCLCacheDirPath.back() != '\\' && NewSYCLCacheDirPath.back() != '/') @@ -144,6 +150,24 @@ class PersistentDeviceCodeCache sycl::detail::SYCLConfig::reset(); } + // Get the list of binary files in the cache directory. + std::vector getBinaryFileNames(std::string CachePath) { + + std::vector FileNames; + std::error_code EC; + for (llvm::sys::fs::directory_iterator DirIt(CachePath, EC); + DirIt != llvm::sys::fs::directory_iterator(); DirIt.increment(EC)) { + // Check if the file is a binary file. + std::string filename = DirIt->path(); + if (filename.find(".bin") != std::string::npos) { + // Just return the file name without the path. + FileNames.push_back(filename.substr(filename.find_last_of("/\\") + 1)); + } + } + + return FileNames; + } + void ResetSYCLCacheDirEnv() { set_env("SYCL_CACHE_DIR", RootSYCLCacheDir.c_str()); sycl::detail::SYCLConfig::reset(); @@ -169,6 +193,9 @@ class PersistentDeviceCodeCache SetSYCLCachePersistentEnv(SYCLCachePersistentBefore ? SYCLCachePersistentBefore->c_str() : nullptr); + + // Reset SYCL_CACHE_MAX_SIZE. + SetDiskCacheEvictionEnv(nullptr); ResetSYCLCacheDirEnv(); } @@ -519,6 +546,99 @@ TEST_P(PersistentDeviceCodeCache, AccessDeniedForCacheDir) { } #endif //_WIN32 +// Unit tests for testing eviction in persistent cache. +TEST_P(PersistentDeviceCodeCache, BasicEviction) { + + // Cleanup the cache directory. + std::string CacheRoot = detail::PersistentDeviceCodeCache::getRootDir(); + ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(CacheRoot)); + ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); + + // Disable eviction for the time being. + SetDiskCacheEvictionEnv("0"); + + std::string BuildOptions{"--eviction"}; + // Put 3 items to the cache. + // Sleeping for 1 second between each put to ensure that the items are + // written to the cache with different timestamps. After that, we will + // have three binary files in the cache with different timestamps. This is + // required to keep this unit test deterministic. + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, + BuildOptions, NativeProg); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, + BuildOptions, NativeProg); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, + BuildOptions, NativeProg); + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Retrieve 0.bin from the cache. + auto Res = detail::PersistentDeviceCodeCache::getItemFromDisc( + {Dev}, {&Img}, {}, BuildOptions); + + // Get the number of binary files in the cached item folder. + std::string ItemDir = detail::PersistentDeviceCodeCache::getCacheItemPath( + Dev, {&Img}, {}, BuildOptions); + auto BinFiles = getBinaryFileNames(ItemDir); + + EXPECT_EQ(BinFiles.size(), static_cast(3)) + << "Missing binary files. Eviction should not have happened."; + + // Get Cache size and size of each entry. Set eviction threshold so that + // just one item is evicted. + size_t CurrentCacheSize = 0; + size_t SizeOfOneEntry = + (size_t)(detail::OSUtil::getDirectorySize(CacheRoot)) + 10; + + // Set SYCL_CACHE_MAX_SIZE. + SetDiskCacheEvictionEnv(std::to_string(SizeOfOneEntry).c_str()); + + // Put 4th item to the cache. This should trigger eviction. Only the first + // item should be evicted. + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, + BuildOptions, NativeProg); + + // We should have three binary files: 0.bin, 2.bin, 3.bin. + BinFiles = getBinaryFileNames(ItemDir); + EXPECT_EQ(BinFiles.size(), static_cast(3)) + << "Eviction failed. Wrong number of binary files in the cache."; + + // Check that 1.bin was evicted. + for (const auto &File : BinFiles) { + EXPECT_NE(File, "1.bin") + << "Eviction failed. 1.bin should have been evicted."; + } + + ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(ItemDir)); +} + +// Unit test for testing size file creation and update, concurrently. +TEST_P(PersistentDeviceCodeCache, ConcurentReadWriteCacheFileSize) { + // Cleanup the cache directory. + std::string CacheRoot = detail::PersistentDeviceCodeCache::getRootDir(); + ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(CacheRoot)); + ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); + + // Insanely large value to not trigger eviction. This test just checks + // for deadlocks/crashes when updating the size file concurrently. + SetDiskCacheEvictionEnv("10000000"); + ConcurentReadWriteCache(1, 50); +} + +// Unit test for adding and evicting cache, concurrently. +TEST_P(PersistentDeviceCodeCache, ConcurentReadWriteCacheEviction) { + // Cleanup the cache directory. + std::string CacheRoot = detail::PersistentDeviceCodeCache::getRootDir(); + ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(CacheRoot)); + ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); + + SetDiskCacheEvictionEnv("1000"); + ConcurentReadWriteCache(2, 40); +} + INSTANTIATE_TEST_SUITE_P(PersistentDeviceCodeCacheImpl, PersistentDeviceCodeCache, ::testing::Values(SYCL_DEVICE_BINARY_TYPE_SPIRV, From 6f151d34e73a0c03950a3baa184caf86e06afdee Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Thu, 5 Dec 2024 10:36:30 -0800 Subject: [PATCH 03/16] Minor fixes --- sycl/source/detail/os_util.cpp | 13 +++++++------ .../PersistentDeviceCodeCache.cpp | 1 - 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index e6872a0025373..5adb307b9fdae 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #if __GNUC__ && __GNUC__ < 8 // Don't include for GCC versions less than 8 @@ -284,12 +285,12 @@ size_t OSUtil::getDirectorySize(const std::string &Path) { DirSizeVar = 0; // Use ftw for Linux and darwin as they support posix. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - auto SumSize = - [](const char *Fpath, const struct stat *StatBuf, int TypeFlag) { - if (TypeFlag == FTW_F) - DirSizeVar += StatBuf->st_size; - return 0; - }; + auto SumSize = []([[maybe_unused]] const char *Fpath, + const struct stat *StatBuf, [[maybe_unused]] int TypeFlag) { + if (TypeFlag == FTW_F) + DirSizeVar += StatBuf->st_size; + return 0; + }; if (ftw(Path.c_str(),SumSize, 1) == -1) std::cerr << "Failed to get directory size: " << Path << std::endl; diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index 78383d8d43562..d22965812cc81 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -589,7 +589,6 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { // Get Cache size and size of each entry. Set eviction threshold so that // just one item is evicted. - size_t CurrentCacheSize = 0; size_t SizeOfOneEntry = (size_t)(detail::OSUtil::getDirectorySize(CacheRoot)) + 10; From 145e81fb52047e4deca628977f8e6c316bcebe63 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Thu, 5 Dec 2024 22:06:30 -0800 Subject: [PATCH 04/16] More fixes --- sycl/include/sycl/detail/os_util.hpp | 1 + sycl/source/detail/os_util.cpp | 63 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index f5abc19597155..2c65dacb31aee 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -101,6 +101,7 @@ class __SYCL_EXPORT OSUtil { getFilesWithAccessTime(const std::string &Path); static size_t DirSizeVar; + static std::vector> Files; }; } // namespace detail diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 5adb307b9fdae..44f1a8636b178 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -286,13 +286,13 @@ size_t OSUtil::getDirectorySize(const std::string &Path) { // Use ftw for Linux and darwin as they support posix. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) auto SumSize = []([[maybe_unused]] const char *Fpath, - const struct stat *StatBuf, [[maybe_unused]] int TypeFlag) { + const struct stat *StatBuf, int TypeFlag) { if (TypeFlag == FTW_F) DirSizeVar += StatBuf->st_size; return 0; }; - if (ftw(Path.c_str(),SumSize, 1) == -1) + if (ftw(Path.c_str(), SumSize, 1) == -1) std::cerr << "Failed to get directory size: " << Path << std::endl; #endif @@ -302,43 +302,44 @@ size_t OSUtil::getDirectorySize(const std::string &Path) { // Get size of file in bytes. size_t OSUtil::getFileSize(const std::string &Path) { size_t Size = 0; -#if __GNUC__ && __GNUC__ < 8 - // Should we worry about this case? - assert(false && "getFileSize is not implemented for GCC < 8"); -#else - std::filesystem::path FilePath(Path); - if (std::filesystem::exists(FilePath) && - std::filesystem::is_regular_file(FilePath)) - Size = std::filesystem::file_size(FilePath); -#endif + + // For POSIX, use stats to get file size. +#if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) + struct stat StatBuf; + if (stat(Path.c_str(), &StatBuf) == 0) + Size = StatBuf.st_size; + + // For Windows, use GetFileAttributesEx to get file size. +#elif defined(__SYCL_RT_OS_WINDOWS) + WIN32_FILE_ATTRIBUTE_DATA FileData; + if (GetFileAttributesEx(Path.c_str(), GetFileExInfoStandard, &FileData)) + Size = (static_cast(FileData.nFileSizeHigh) << 32) | + FileData.nFileSizeLow; +#endif // __SYCL_RT_OS + return Size; } +std::vector> OSUtil::Files = {}; // Get list of all files in the directory along with its last access time. std::vector> OSUtil::getFilesWithAccessTime(const std::string &Path) { - std::vector> Files; -#if __GNUC__ && __GNUC__ < 8 - // Should we worry about this case? - assert(false && "getFilesWithAccessTime is not implemented for GCC < 8"); -#else - for (const auto &entry : - std::filesystem::recursive_directory_iterator(Path)) { - if (entry.is_regular_file()) { + + Files.clear(); + +// Use ftw for posix. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - struct stat StatBuf; - if (stat(entry.path().c_str(), &StatBuf) == 0) - Files.push_back({StatBuf.st_atime, entry.path().string()}); -#elif defined(__SYCL_RT_OS_WINDOWS) - WIN32_FILE_ATTRIBUTE_DATA FileData; - if (GetFileAttributesEx(entry.path().c_str(), GetFileExInfoStandard, - &FileData)) - Files.push_back( - {FileData.ftLastAccessTime.dwLowDateTime, entry.path().string()}); -#endif // __SYCL_RT_OS - } - } + auto GetFiles = [](const char *Fpath, const struct stat *StatBuf, + int TypeFlag) { + if (TypeFlag == FTW_F) + Files.push_back({StatBuf->st_atime, std::string(Fpath)}); + return 0; + }; + + if (ftw(Path.c_str(), GetFiles, 1) == -1) + std::cerr << "Failed to get files with access time: " << Path << std::endl; #endif + return Files; } From 084e2d1e66af3f66ae663383caf66befd144307a Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Sun, 8 Dec 2024 17:49:09 -0800 Subject: [PATCH 05/16] Remove getDirectorySize getFileSize getFilesWithAccessTime from OSUtils --- sycl/include/sycl/detail/os_util.hpp | 35 +++++++++++++------ sycl/source/detail/os_util.cpp | 20 +++++------ .../detail/persistent_device_code_cache.cpp | 10 +++--- .../PersistentDeviceCodeCache.cpp | 3 +- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index 2c65dacb31aee..cb1cb1f467089 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -90,20 +90,35 @@ class __SYCL_EXPORT OSUtil { #endif } - // Get size of directory in bytes. - static size_t getDirectorySize(const std::string &Path); - - // Get size of file in bytes. - static size_t getFileSize(const std::string &Path); - - // Get list of all files in the directory along with its last access time. - static std::vector> - getFilesWithAccessTime(const std::string &Path); - +private: + // These static variables will be used by ftw POSIX function to + // calculate directory size and get files with access time. Other + // option is to make these global variables but that will pollute + // the sycl::detail namespace. + // QUESTION: Should we make these global variables? Or implement ftw-like + // function ourself for directory iteration? static size_t DirSizeVar; static std::vector> Files; + + // Friendship is required to access private static variables. + friend size_t getDirectorySize(const std::string &Path); + friend std::vector> + getFilesWithAccessTime(const std::string &Path); }; +// These functions are not a part of OSUtils class to prevent +// exporting them as ABI. They are only used in persistent cache +// implementation and should not be exposed to the end users. +// Get size of directory in bytes. +size_t getDirectorySize(const std::string &Path); + +// Get size of file in bytes. +size_t getFileSize(const std::string &Path); + +// Get list of all files in the directory along with its last access time. +std::vector> +getFilesWithAccessTime(const std::string &Path); + } // namespace detail } // namespace _V1 } // namespace sycl diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 44f1a8636b178..dbc38179d5908 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -280,15 +280,15 @@ int OSUtil::makeDir(const char *Dir) { size_t OSUtil::DirSizeVar = 0; // Get size of a directory in bytes. -size_t OSUtil::getDirectorySize(const std::string &Path) { +size_t getDirectorySize(const std::string &Path) { - DirSizeVar = 0; + OSUtil::DirSizeVar = 0; // Use ftw for Linux and darwin as they support posix. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) auto SumSize = []([[maybe_unused]] const char *Fpath, const struct stat *StatBuf, int TypeFlag) { if (TypeFlag == FTW_F) - DirSizeVar += StatBuf->st_size; + OSUtil::DirSizeVar += StatBuf->st_size; return 0; }; @@ -296,11 +296,11 @@ size_t OSUtil::getDirectorySize(const std::string &Path) { std::cerr << "Failed to get directory size: " << Path << std::endl; #endif - return DirSizeVar; + return OSUtil::DirSizeVar; } // Get size of file in bytes. -size_t OSUtil::getFileSize(const std::string &Path) { +size_t getFileSize(const std::string &Path) { size_t Size = 0; // For POSIX, use stats to get file size. @@ -309,8 +309,8 @@ size_t OSUtil::getFileSize(const std::string &Path) { if (stat(Path.c_str(), &StatBuf) == 0) Size = StatBuf.st_size; - // For Windows, use GetFileAttributesEx to get file size. #elif defined(__SYCL_RT_OS_WINDOWS) + // For Windows, use GetFileAttributesEx to get file size. WIN32_FILE_ATTRIBUTE_DATA FileData; if (GetFileAttributesEx(Path.c_str(), GetFileExInfoStandard, &FileData)) Size = (static_cast(FileData.nFileSizeHigh) << 32) | @@ -323,16 +323,16 @@ size_t OSUtil::getFileSize(const std::string &Path) { std::vector> OSUtil::Files = {}; // Get list of all files in the directory along with its last access time. std::vector> -OSUtil::getFilesWithAccessTime(const std::string &Path) { +getFilesWithAccessTime(const std::string &Path) { - Files.clear(); + OSUtil::Files.clear(); // Use ftw for posix. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) auto GetFiles = [](const char *Fpath, const struct stat *StatBuf, int TypeFlag) { if (TypeFlag == FTW_F) - Files.push_back({StatBuf->st_atime, std::string(Fpath)}); + OSUtil::Files.push_back({StatBuf->st_atime, std::string(Fpath)}); return 0; }; @@ -340,7 +340,7 @@ OSUtil::getFilesWithAccessTime(const std::string &Path) { std::cerr << "Failed to get files with access time: " << Path << std::endl; #endif - return Files; + return OSUtil::Files; } } // namespace detail diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 9f26ca173ade0..9bff9316eccec 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -196,7 +196,7 @@ void PersistentDeviceCodeCache::repopulateCacheSizeFile( PersistentDeviceCodeCache::trace( "Cache size file not present. Creating one."); // Calculate the size of the cache directory. - size_t CacheSize = OSUtil::getDirectorySize(CacheRoot); + size_t CacheSize = getDirectorySize(CacheRoot); // Take the lock to write the cache size to the file. { @@ -232,7 +232,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( // Get the list of all files in the cache directory along with their last // access time. - auto FilesWithAccessTime = OSUtil::getFilesWithAccessTime(CacheRoot); + auto FilesWithAccessTime = getFilesWithAccessTime(CacheRoot); // Sort the files in the cache directory based on their last access time. std::sort(FilesWithAccessTime.begin(), FilesWithAccessTime.end(), @@ -267,7 +267,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( auto RemoveFileAndSubtractSize = [&CurrCacheSize](const std::string &FileName) { - auto FileSize = OSUtil::getFileSize(FileName); + auto FileSize = getFileSize(FileName); if (std::remove(FileName.c_str())) { PersistentDeviceCodeCache::trace("Failed to remove file: " + FileName); @@ -414,8 +414,8 @@ void PersistentDeviceCodeCache::putItemToDisc( } if (IsWriteSuccess) { - TotalSize += OSUtil::getFileSize(FileName + ".src"); - TotalSize += OSUtil::getFileSize(FileName + ".bin"); + TotalSize += getFileSize(FileName + ".src"); + TotalSize += getFileSize(FileName + ".bin"); } } diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index d22965812cc81..02bbd62adeb80 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -589,8 +589,7 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { // Get Cache size and size of each entry. Set eviction threshold so that // just one item is evicted. - size_t SizeOfOneEntry = - (size_t)(detail::OSUtil::getDirectorySize(CacheRoot)) + 10; + size_t SizeOfOneEntry = (size_t)(detail::getDirectorySize(CacheRoot)) + 10; // Set SYCL_CACHE_MAX_SIZE. SetDiskCacheEvictionEnv(std::to_string(SizeOfOneEntry).c_str()); From 30928702cccc6ab14ed075bcd99cea8dce330623 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Mon, 9 Dec 2024 12:11:40 -0800 Subject: [PATCH 06/16] Fix compilation issues with MSVC --- sycl/include/sycl/detail/os_util.hpp | 17 +---- sycl/source/detail/os_util.cpp | 100 +++++++++++++-------------- 2 files changed, 48 insertions(+), 69 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index cb1cb1f467089..4b37b9b524ce3 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -89,21 +89,6 @@ class __SYCL_EXPORT OSUtil { return !stat(Path.c_str(), &Stat); #endif } - -private: - // These static variables will be used by ftw POSIX function to - // calculate directory size and get files with access time. Other - // option is to make these global variables but that will pollute - // the sycl::detail namespace. - // QUESTION: Should we make these global variables? Or implement ftw-like - // function ourself for directory iteration? - static size_t DirSizeVar; - static std::vector> Files; - - // Friendship is required to access private static variables. - friend size_t getDirectorySize(const std::string &Path); - friend std::vector> - getFilesWithAccessTime(const std::string &Path); }; // These functions are not a part of OSUtils class to prevent @@ -116,7 +101,7 @@ size_t getDirectorySize(const std::string &Path); size_t getFileSize(const std::string &Path); // Get list of all files in the directory along with its last access time. -std::vector> +std::vector> getFilesWithAccessTime(const std::string &Path); } // namespace detail diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index dbc38179d5908..daed01a2e4592 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -11,10 +11,16 @@ #include #include #include -#if __GNUC__ && __GNUC__ < 8 -// Don't include for GCC versions less than 8 + +// For GCC versions less than 8, use experimental/filesystem. +#if defined(__has_include) && __has_include() +#include +namespace fs = std::filesystem; +#elif defined(__has_include) && __has_include() +#include +namespace fs = std::experimental::filesystem; #else -#include // C++ 17 std::create_directories +#error "OSUtils requires C++ filesystem support" #endif #if defined(__SYCL_RT_OS_LINUX) @@ -27,7 +33,6 @@ #include #include #include -#include // for ftw - file tree walk #include // for dirname #include #include // for PATH_MAX @@ -278,69 +283,58 @@ int OSUtil::makeDir(const char *Dir) { return 0; } -size_t OSUtil::DirSizeVar = 0; // Get size of a directory in bytes. size_t getDirectorySize(const std::string &Path) { + size_t DirSizeVar = 0; + + // using fs::recursive_directory_iterator. + for (const auto &Entry : fs::recursive_directory_iterator(Path)) { + // Don't check file with .lock extension. + if (fs::is_regular_file(Entry.path()) && + Entry.path().extension() != ".lock") + DirSizeVar += getFileSize(Entry.path().string()); + } - OSUtil::DirSizeVar = 0; -// Use ftw for Linux and darwin as they support posix. -#if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - auto SumSize = []([[maybe_unused]] const char *Fpath, - const struct stat *StatBuf, int TypeFlag) { - if (TypeFlag == FTW_F) - OSUtil::DirSizeVar += StatBuf->st_size; - return 0; - }; - - if (ftw(Path.c_str(), SumSize, 1) == -1) - std::cerr << "Failed to get directory size: " << Path << std::endl; -#endif - - return OSUtil::DirSizeVar; + return DirSizeVar; } // Get size of file in bytes. size_t getFileSize(const std::string &Path) { - size_t Size = 0; - - // For POSIX, use stats to get file size. -#if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - struct stat StatBuf; - if (stat(Path.c_str(), &StatBuf) == 0) - Size = StatBuf.st_size; - -#elif defined(__SYCL_RT_OS_WINDOWS) - // For Windows, use GetFileAttributesEx to get file size. - WIN32_FILE_ATTRIBUTE_DATA FileData; - if (GetFileAttributesEx(Path.c_str(), GetFileExInfoStandard, &FileData)) - Size = (static_cast(FileData.nFileSizeHigh) << 32) | - FileData.nFileSizeLow; -#endif // __SYCL_RT_OS - - return Size; + return static_cast(fs::file_size(Path)); } -std::vector> OSUtil::Files = {}; // Get list of all files in the directory along with its last access time. -std::vector> +std::vector> getFilesWithAccessTime(const std::string &Path) { + std::vector> Files = {}; - OSUtil::Files.clear(); - -// Use ftw for posix. + // using fs::recursive_directory_iterator. + for (const auto &Entry : fs::recursive_directory_iterator(Path)) { + if (fs::is_regular_file(Entry.path())) { +// For Linux and Darwin, use stats. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - auto GetFiles = [](const char *Fpath, const struct stat *StatBuf, - int TypeFlag) { - if (TypeFlag == FTW_F) - OSUtil::Files.push_back({StatBuf->st_atime, std::string(Fpath)}); - return 0; - }; - - if (ftw(Path.c_str(), GetFiles, 1) == -1) - std::cerr << "Failed to get files with access time: " << Path << std::endl; -#endif + struct stat StatBuf; + if (stat(Entry.path().c_str(), &StatBuf) == 0) + Files.push_back({StatBuf.st_atime, Entry.path().string()}); +#elif defined(__SYCL_RT_OS_WINDOWS) + // For Windows, use GetFileAttributesEx to get file size. + WIN32_FILE_ATTRIBUTE_DATA FileData; + // Convert to wise string. + char *path = new char[Entry.path().string().length() + 1]; + strcpy(path, Entry.path().string().c_str()); + if (GetFileAttributesEx(path, GetFileExInfoStandard, &FileData)) { + // Convert FILETIME to uint64_t. + ULARGE_INTEGER Time; + Time.LowPart = FileData.ftLastAccessTime.dwLowDateTime; + Time.HighPart = FileData.ftLastAccessTime.dwHighDateTime; + Files.push_back({Time.QuadPart, Entry.path().string()}); + } + free(path); +#endif // __SYCL_RT_OS + } + } - return OSUtil::Files; + return Files; } } // namespace detail From e407410a3ad15d462953f496e47b4369d2118f91 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Mon, 9 Dec 2024 12:57:26 -0800 Subject: [PATCH 07/16] Use GetFileAttributesExA instead of GetFileAttributesEx --- sycl/source/detail/os_util.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index daed01a2e4592..753e167a1273d 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -322,7 +322,7 @@ getFilesWithAccessTime(const std::string &Path) { // Convert to wise string. char *path = new char[Entry.path().string().length() + 1]; strcpy(path, Entry.path().string().c_str()); - if (GetFileAttributesEx(path, GetFileExInfoStandard, &FileData)) { + if (GetFileAttributesExA(path, GetFileExInfoStandard, &FileData)) { // Convert FILETIME to uint64_t. ULARGE_INTEGER Time; Time.LowPart = FileData.ftLastAccessTime.dwLowDateTime; From cf9e1d01b0d38353a1c0c02cda493c80d8febb73 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Thu, 12 Dec 2024 09:37:06 -0800 Subject: [PATCH 08/16] Fix windows error. --- sycl/include/sycl/detail/os_util.hpp | 9 +- sycl/source/detail/config.hpp | 12 +- sycl/source/detail/os_util.cpp | 108 +++++++++++++----- .../detail/persistent_device_code_cache.cpp | 50 ++++++-- .../PersistentDeviceCodeCache.cpp | 19 +-- 5 files changed, 135 insertions(+), 63 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index 4b37b9b524ce3..5fcee1fc54ffd 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -95,14 +95,17 @@ class __SYCL_EXPORT OSUtil { // exporting them as ABI. They are only used in persistent cache // implementation and should not be exposed to the end users. // Get size of directory in bytes. -size_t getDirectorySize(const std::string &Path); +size_t getDirectorySize(const std::string &Path, bool ignoreError); // Get size of file in bytes. size_t getFileSize(const std::string &Path); -// Get list of all files in the directory along with its last access time. +// Get list of all files in the directory along with its last modification time. std::vector> -getFilesWithAccessTime(const std::string &Path); +getFilesWithLastModificationTime(const std::string &Path, bool ignoreError); + +// Function to update file modification time with current time. +void updateFileModificationTime(const std::string &Path); } // namespace detail } // namespace _V1 diff --git a/sycl/source/detail/config.hpp b/sycl/source/detail/config.hpp index f92744e90c852..3e0a591e27d14 100644 --- a/sycl/source/detail/config.hpp +++ b/sycl/source/detail/config.hpp @@ -815,27 +815,27 @@ template <> class SYCLConfig { using BaseT = SYCLConfigBase; public: - static int get() { return getCachedValue(); } + static long long get() { return getCachedValue(); } static void reset() { (void)getCachedValue(true); } - static int getProgramCacheSize() { return getCachedValue(); } + static long long getProgramCacheSize() { return getCachedValue(); } static bool isPersistentCacheEvictionEnabled() { return getProgramCacheSize() > 0; } private: - static int getCachedValue(bool ResetCache = false) { + static long long getCachedValue(bool ResetCache = false) { const auto Parser = []() { const char *ValStr = BaseT::getRawValue(); // Disable eviction by default. if (!ValStr) - return 0; + return (long long)0; - int CacheSize = 0; + long long CacheSize = 0; try { - CacheSize = std::stoi(ValStr); + CacheSize = std::stoll(ValStr); if (CacheSize < 0) throw INVALID_CONFIG_EXCEPTION(BaseT, "Value must be non-negative"); } catch (...) { diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 753e167a1273d..5aaf5958ca6df 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include +#include #include #include @@ -38,6 +39,7 @@ namespace fs = std::experimental::filesystem; #include // for PATH_MAX #include #include +#include #elif defined(__SYCL_RT_OS_WINDOWS) @@ -284,17 +286,20 @@ int OSUtil::makeDir(const char *Dir) { } // Get size of a directory in bytes. -size_t getDirectorySize(const std::string &Path) { +size_t getDirectorySize(const std::string &Path, bool IgnoreError = false) { size_t DirSizeVar = 0; - - // using fs::recursive_directory_iterator. - for (const auto &Entry : fs::recursive_directory_iterator(Path)) { - // Don't check file with .lock extension. - if (fs::is_regular_file(Entry.path()) && - Entry.path().extension() != ".lock") - DirSizeVar += getFileSize(Entry.path().string()); + std::error_code EC; + for (auto It = fs::recursive_directory_iterator(Path, EC); + It != fs::recursive_directory_iterator(); It.increment(EC)) { + // Errors can happen if a file was removed/added during the iteration. + if (EC && !IgnoreError) + throw sycl::exception(make_error_code(errc::runtime), + "Failed to get directory size: " + Path + "\n" + + EC.message()); + + if (fs::is_regular_file(It->path())) + DirSizeVar += getFileSize(It->path().string()); } - return DirSizeVar; } @@ -303,33 +308,39 @@ size_t getFileSize(const std::string &Path) { return static_cast(fs::file_size(Path)); } -// Get list of all files in the directory along with its last access time. +// Get list of all files in the directory along with its last modification time. std::vector> -getFilesWithAccessTime(const std::string &Path) { +getFilesWithLastModificationTime(const std::string &Path, + bool IgnoreError = false) { std::vector> Files = {}; - - // using fs::recursive_directory_iterator. - for (const auto &Entry : fs::recursive_directory_iterator(Path)) { - if (fs::is_regular_file(Entry.path())) { + std::error_code EC; + for (auto It = fs::recursive_directory_iterator(Path, EC); + It != fs::recursive_directory_iterator(); It.increment(EC)) { + // Errors can happen if a file was removed/added during the iteration. + if (EC && !IgnoreError) + throw sycl::exception(make_error_code(errc::runtime), + "Failed to get files with access time: " + Path + + "\n" + EC.message()); + + const std::string FileName = It->path().string(); + if (fs::is_regular_file(It->path())) { // For Linux and Darwin, use stats. #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) struct stat StatBuf; - if (stat(Entry.path().c_str(), &StatBuf) == 0) - Files.push_back({StatBuf.st_atime, Entry.path().string()}); + if (stat(FileName.c_str(), &StatBuf) == 0) + Files.push_back({StatBuf.st_mtime, FileName}); #elif defined(__SYCL_RT_OS_WINDOWS) - // For Windows, use GetFileAttributesEx to get file size. - WIN32_FILE_ATTRIBUTE_DATA FileData; - // Convert to wise string. - char *path = new char[Entry.path().string().length() + 1]; - strcpy(path, Entry.path().string().c_str()); - if (GetFileAttributesExA(path, GetFileExInfoStandard, &FileData)) { - // Convert FILETIME to uint64_t. - ULARGE_INTEGER Time; - Time.LowPart = FileData.ftLastAccessTime.dwLowDateTime; - Time.HighPart = FileData.ftLastAccessTime.dwHighDateTime; - Files.push_back({Time.QuadPart, Entry.path().string()}); - } - free(path); + // Use GetFileAttributeExA to get file modification time. + WIN32_FILE_ATTRIBUTE_DATA FileAttr; + if (GetFileAttributesExA(FileName.c_str(), GetFileExInfoStandard, + &FileAttr)) { + ULARGE_INTEGER AccessTime; + AccessTime.HighPart = FileAttr.ftLastWriteTime.dwHighDateTime; + AccessTime.LowPart = FileAttr.ftLastWriteTime.dwLowDateTime; + Files.push_back({AccessTime.QuadPart, FileName}); + } else + throw sycl::exception(make_error_code(errc::runtime), + "Failed to get file attributes for: " + FileName); #endif // __SYCL_RT_OS } } @@ -337,6 +348,43 @@ getFilesWithAccessTime(const std::string &Path) { return Files; } +// Function to update file modification time with current time. +void updateFileModificationTime(const std::string &Path) { + +#if defined(__SYCL_RT_OS_WINDOWS) + // For Windows, use SetFileTime to update file access time. + HANDLE hFile = CreateFileA(Path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + if (!SetFileTime(hFile, NULL, NULL, &ft)) { + // Print full error. + char *errorText = nullptr; + FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&errorText, 0, NULL); + + throw sycl::exception(make_error_code(errc::runtime), + "Failed to update file access time: " + Path); + } + CloseHandle(hFile); + } else { + throw sycl::exception(make_error_code(errc::runtime), + "Failed to open file: " + Path); + } + +#elif defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) + // For Linux and Darwin, use utime to update file modification time. + struct utimbuf UtimeBuf; + UtimeBuf.actime = UtimeBuf.actime; + UtimeBuf.modtime = time(nullptr); + utime(Path.c_str(), &UtimeBuf); +#endif // __SYCL_RT_OS +} + } // namespace detail } // namespace _V1 } // namespace sycl diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 9bff9316eccec..e25c3d08b2e84 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -178,7 +178,7 @@ getProgramBinaryData(const ur_program_handle_t &NativePrg, return Result; } -// Check if cache_size.lock file is present in the cache root directory. +// Check if cache_size.txt file is present in the cache root directory. // If not, create it and populate it with the size of the cache directory. void PersistentDeviceCodeCache::repopulateCacheSizeFile( const std::string &CacheRoot) { @@ -195,8 +195,6 @@ void PersistentDeviceCodeCache::repopulateCacheSizeFile( if (!OSUtil::isPathPresent(CacheSizeFile)) { PersistentDeviceCodeCache::trace( "Cache size file not present. Creating one."); - // Calculate the size of the cache directory. - size_t CacheSize = getDirectorySize(CacheRoot); // Take the lock to write the cache size to the file. { @@ -205,11 +203,22 @@ void PersistentDeviceCodeCache::repopulateCacheSizeFile( // If some other process is writing the cache size, do not write it. PersistentDeviceCodeCache::trace("Didnot create the cache size file. " "Some other process is creating one."); - return; + + // Stall until the other process creates the file. Stalling is important + // to prevent race between one process that's calculating the directory + // size and another process that's trying to create a new cache entry. + while (!OSUtil::isPathPresent(CacheSizeFile)) + continue; } else { + // Calculate the size of the cache directory. + // During directory size calculation, do not add anything + // in the cache. Otherwise, we'll get a std::fs_error. + size_t CacheSize = getDirectorySize(CacheRoot, true); + std::ofstream FileStream{CacheSizeFile}; FileStream << CacheSize; FileStream.close(); + PersistentDeviceCodeCache::trace("Cache size file created."); } } } @@ -222,7 +231,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( // Create a file eviction_in_progress.lock to indicate that eviction is in // progress. This file is used to prevent two processes from evicting the // cache at the same time. - LockCacheItem Lock{CacheRoot + "eviction_in_progress"}; + LockCacheItem Lock{CacheRoot + "/eviction_in_progress"}; if (!Lock.isOwned()) { // If some other process is evicting the cache, return. PersistentDeviceCodeCache::trace( @@ -231,13 +240,13 @@ void PersistentDeviceCodeCache::evictItemsFromCache( } // Get the list of all files in the cache directory along with their last - // access time. - auto FilesWithAccessTime = getFilesWithAccessTime(CacheRoot); + // modification time. + auto FilesWithAccessTime = getFilesWithLastModificationTime(CacheRoot, true); // Sort the files in the cache directory based on their last access time. std::sort(FilesWithAccessTime.begin(), FilesWithAccessTime.end(), - [](const std::pair &A, - const std::pair &B) { + [](const std::pair &A, + const std::pair &B) { return A.first < B.first; }); @@ -247,8 +256,13 @@ void PersistentDeviceCodeCache::evictItemsFromCache( for (const auto &File : FilesWithAccessTime) { // Remove .bin/.src/.lock extension from the file name. - const std::string FileNameWOExt = - File.second.substr(0, File.second.find_last_of(".")); + auto ExtLoc = File.second.find_last_of("."); + const std::string FileNameWOExt = File.second.substr(0, ExtLoc); + const std::string Extension = File.second.substr(ExtLoc); + + if (Extension != ".bin") + continue; + const std::string BinFile = FileNameWOExt + ".bin"; const std::string SrcFile = FileNameWOExt + ".src"; @@ -372,6 +386,14 @@ void PersistentDeviceCodeCache::putItemToDisc( repopulateCacheSizeFile(getRootDir()); + // Do not insert any new item if eviction is in progress. + // Since evictions are rare, we can afford to spin lock here. + const std::string EvictionInProgressFile = + getRootDir() + "/eviction_in_progress.lock"; + // Stall until the other process finishes eviction. + while (OSUtil::isPathPresent(EvictionInProgressFile)) + continue; + std::vector SortedImgs = getSortedImages(Imgs); auto BinaryData = getProgramBinaryData(NativePrg, Devices); @@ -500,6 +522,12 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( try { std::string FullFileName = FileName + ".bin"; Binaries[DeviceIndex] = readBinaryDataFromFile(FullFileName); + + // Explicitly update the access time of the file. This is required for + // eviction. + if (isEvictionEnabled()) + updateFileModificationTime(FileName + ".bin"); + FileNames += FullFileName + ";"; break; } catch (...) { diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index 02bbd62adeb80..be9c69ee31d76 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -555,25 +555,18 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); // Disable eviction for the time being. - SetDiskCacheEvictionEnv("0"); + SetDiskCacheEvictionEnv("9000000"); std::string BuildOptions{"--eviction"}; // Put 3 items to the cache. - // Sleeping for 1 second between each put to ensure that the items are - // written to the cache with different timestamps. After that, we will - // have three binary files in the cache with different timestamps. This is - // required to keep this unit test deterministic. detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - std::this_thread::sleep_for(std::chrono::seconds(1)); detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - std::this_thread::sleep_for(std::chrono::seconds(1)); detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - std::this_thread::sleep_for(std::chrono::seconds(1)); // Retrieve 0.bin from the cache. auto Res = detail::PersistentDeviceCodeCache::getItemFromDisc( @@ -589,7 +582,8 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { // Get Cache size and size of each entry. Set eviction threshold so that // just one item is evicted. - size_t SizeOfOneEntry = (size_t)(detail::getDirectorySize(CacheRoot)) + 10; + size_t SizeOfOneEntry = + (size_t)(detail::getDirectorySize(CacheRoot, false)) + 10; // Set SYCL_CACHE_MAX_SIZE. SetDiskCacheEvictionEnv(std::to_string(SizeOfOneEntry).c_str()); @@ -605,10 +599,9 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { << "Eviction failed. Wrong number of binary files in the cache."; // Check that 1.bin was evicted. - for (const auto &File : BinFiles) { + for (const auto &File : BinFiles) EXPECT_NE(File, "1.bin") << "Eviction failed. 1.bin should have been evicted."; - } ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(ItemDir)); } @@ -620,9 +613,9 @@ TEST_P(PersistentDeviceCodeCache, ConcurentReadWriteCacheFileSize) { ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(CacheRoot)); ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); - // Insanely large value to not trigger eviction. This test just checks + // Insanely large value (1GB) to not trigger eviction. This test just checks // for deadlocks/crashes when updating the size file concurrently. - SetDiskCacheEvictionEnv("10000000"); + SetDiskCacheEvictionEnv("1000000000"); ConcurentReadWriteCache(1, 50); } From 983bc988cddca9535042014a2dd58c36aa16ab0a Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Fri, 13 Dec 2024 13:28:00 -0800 Subject: [PATCH 09/16] Fix data race between eviction and cache read/write --- sycl/source/detail/os_util.cpp | 13 ++++- .../detail/persistent_device_code_cache.cpp | 57 +++++++++++-------- .../PersistentDeviceCodeCache.cpp | 14 ++++- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 5aaf5958ca6df..b38b46a5ed91f 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -39,7 +39,7 @@ namespace fs = std::experimental::filesystem; #include // for PATH_MAX #include #include -#include +#include #elif defined(__SYCL_RT_OS_WINDOWS) @@ -52,8 +52,10 @@ namespace fs = std::experimental::filesystem; #elif defined(__SYCL_RT_OS_DARWIN) #include +#include #include #include +#include #endif // __SYCL_RT_OS @@ -353,8 +355,13 @@ void updateFileModificationTime(const std::string &Path) { #if defined(__SYCL_RT_OS_WINDOWS) // For Windows, use SetFileTime to update file access time. - HANDLE hFile = CreateFileA(Path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + // Open file with FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING flags + // to ensure that the file time is updated on disk, asap. + HANDLE hFile = CreateFileA( + Path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, + NULL); if (hFile != INVALID_HANDLE_VALUE) { FILETIME ft; GetSystemTimeAsFileTime(&ft); diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index e25c3d08b2e84..68540acdbfbe8 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -241,7 +241,22 @@ void PersistentDeviceCodeCache::evictItemsFromCache( // Get the list of all files in the cache directory along with their last // modification time. - auto FilesWithAccessTime = getFilesWithLastModificationTime(CacheRoot, true); + std::vector> FilesWithAccessTime; + + // getFileWithLastModificationTime can throw if any new file is created or + // removed during the iteration. Retry in that case. When eviction is in + // progress,, we don't insert any new item but processes can still read the + // cache. Reading from cache can create/remove .lock file which can cause the + // exception. + while (true) { + try { + FilesWithAccessTime = getFilesWithLastModificationTime(CacheRoot, false); + break; + } catch (...) { + // If the cache directory is removed during the iteration, retry. + continue; + } + } // Sort the files in the cache directory based on their last access time. std::sort(FilesWithAccessTime.begin(), FilesWithAccessTime.end(), @@ -266,33 +281,34 @@ void PersistentDeviceCodeCache::evictItemsFromCache( const std::string BinFile = FileNameWOExt + ".bin"; const std::string SrcFile = FileNameWOExt + ".src"; - while (OSUtil::isPathPresent(BinFile) && OSUtil::isPathPresent(SrcFile)) { - - // This is used to prevent race between processes trying to read the file - // while it is being evicted. - if (LockCacheItem::isLocked(FileNameWOExt + "_reader")) { - // If some other process is reading the file, spin and wait. - continue; - } - - // Take a lock on the file to prevent other processes from reading the - // file. - LockCacheItem Lock{FileNameWOExt}; - + while (OSUtil::isPathPresent(BinFile) || OSUtil::isPathPresent(SrcFile)) { + // Remove the file and subtract its size from the cache size. auto RemoveFileAndSubtractSize = [&CurrCacheSize](const std::string &FileName) { + // If the file is not present, return. + if (!OSUtil::isPathPresent(FileName)) + return; + auto FileSize = getFileSize(FileName); if (std::remove(FileName.c_str())) { - PersistentDeviceCodeCache::trace("Failed to remove file: " + - FileName); + throw sycl::exception(make_error_code(errc::runtime), + "Failed to evict cache entry: " + FileName); } else { PersistentDeviceCodeCache::trace("File removed: " + FileName); CurrCacheSize -= FileSize; } }; - RemoveFileAndSubtractSize(SrcFile); - RemoveFileAndSubtractSize(BinFile); + // If removal fails due to a race, retry. + // Races are rare, but can happen if another process is reading the file. + // Locking down the entire cache and blocking all readers would be + // inefficient. + try { + RemoveFileAndSubtractSize(SrcFile); + RemoveFileAndSubtractSize(BinFile); + } catch (...) { + continue; + } } // If the cache size is less than the threshold, break. @@ -511,11 +527,6 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( while (OSUtil::isPathPresent(FileName + ".bin") || OSUtil::isPathPresent(FileName + ".src")) { - // Create a file, _reader.lock, to indicate that the file is - // being read. This file is used to prevent another process from evicting - // the cache entry while it is being read. - LockCacheItem Lock{FileName + "_reader"}; - if (!LockCacheItem::isLocked(FileName) && isCacheItemSrcEqual(FileName + ".src", Devices[DeviceIndex], SortedImgs, SpecConsts, BuildOptionsString)) { diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index be9c69ee31d76..d73178c279f6f 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -559,15 +559,25 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { std::string BuildOptions{"--eviction"}; // Put 3 items to the cache. + // On Windows, for NTFS, the file timestamp resolution is 100ns, + // but on Linux, for EXT4, the file timestamp resolution is few milliseconds. + // So, to make this test deterministic, we need to sleep for a while, + // say 20ms, between each putItemToDisc/getItemFromDisc call. detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + // Retrieve 0.bin from the cache. auto Res = detail::PersistentDeviceCodeCache::getItemFromDisc( {Dev}, {&Img}, {}, BuildOptions); @@ -616,7 +626,7 @@ TEST_P(PersistentDeviceCodeCache, ConcurentReadWriteCacheFileSize) { // Insanely large value (1GB) to not trigger eviction. This test just checks // for deadlocks/crashes when updating the size file concurrently. SetDiskCacheEvictionEnv("1000000000"); - ConcurentReadWriteCache(1, 50); + ConcurentReadWriteCache(1, 100); } // Unit test for adding and evicting cache, concurrently. @@ -627,7 +637,7 @@ TEST_P(PersistentDeviceCodeCache, ConcurentReadWriteCacheEviction) { ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); SetDiskCacheEvictionEnv("1000"); - ConcurentReadWriteCache(2, 40); + ConcurentReadWriteCache(2, 100); } INSTANTIATE_TEST_SUITE_P(PersistentDeviceCodeCacheImpl, From 1e6c088ee11cf640bd8987f36d152e5e1cc52f4c Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Fri, 13 Dec 2024 15:31:18 -0800 Subject: [PATCH 10/16] Use nano seconds file timestamps for Linux --- sycl/source/detail/os_util.cpp | 11 +-- .../detail/persistent_device_code_cache.cpp | 68 ++++++++++--------- .../PersistentDeviceCodeCache.cpp | 4 +- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index b38b46a5ed91f..e6c6e8acef1e5 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -39,7 +39,6 @@ namespace fs = std::experimental::filesystem; #include // for PATH_MAX #include #include -#include #elif defined(__SYCL_RT_OS_WINDOWS) @@ -55,7 +54,6 @@ namespace fs = std::experimental::filesystem; #include #include #include -#include #endif // __SYCL_RT_OS @@ -330,7 +328,7 @@ getFilesWithLastModificationTime(const std::string &Path, #if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) struct stat StatBuf; if (stat(FileName.c_str(), &StatBuf) == 0) - Files.push_back({StatBuf.st_mtime, FileName}); + Files.push_back({StatBuf.st_atim.tv_nsec, FileName}); #elif defined(__SYCL_RT_OS_WINDOWS) // Use GetFileAttributeExA to get file modification time. WIN32_FILE_ATTRIBUTE_DATA FileAttr; @@ -384,11 +382,8 @@ void updateFileModificationTime(const std::string &Path) { } #elif defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - // For Linux and Darwin, use utime to update file modification time. - struct utimbuf UtimeBuf; - UtimeBuf.actime = UtimeBuf.actime; - UtimeBuf.modtime = time(nullptr); - utime(Path.c_str(), &UtimeBuf); + // For Linux and Darwin, use utimensat to update file modification time. + utimensat(0, Path.c_str(), nullptr, 0); #endif // __SYCL_RT_OS } diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 68540acdbfbe8..384d60d3cca96 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -282,32 +282,37 @@ void PersistentDeviceCodeCache::evictItemsFromCache( const std::string SrcFile = FileNameWOExt + ".src"; while (OSUtil::isPathPresent(BinFile) || OSUtil::isPathPresent(SrcFile)) { - // Remove the file and subtract its size from the cache size. - auto RemoveFileAndSubtractSize = - [&CurrCacheSize](const std::string &FileName) { - // If the file is not present, return. - if (!OSUtil::isPathPresent(FileName)) - return; - - auto FileSize = getFileSize(FileName); - if (std::remove(FileName.c_str())) { - throw sycl::exception(make_error_code(errc::runtime), - "Failed to evict cache entry: " + FileName); - } else { - PersistentDeviceCodeCache::trace("File removed: " + FileName); - CurrCacheSize -= FileSize; - } - }; - - // If removal fails due to a race, retry. - // Races are rare, but can happen if another process is reading the file. - // Locking down the entire cache and blocking all readers would be - // inefficient. - try { - RemoveFileAndSubtractSize(SrcFile); - RemoveFileAndSubtractSize(BinFile); - } catch (...) { - continue; + + // Lock to prevent race between writer and eviction thread. + LockCacheItem Lock{FileNameWOExt}; + if (Lock.isOwned()) { + // Remove the file and subtract its size from the cache size. + auto RemoveFileAndSubtractSize = [&CurrCacheSize]( + const std::string &FileName) { + // If the file is not present, return. + if (!OSUtil::isPathPresent(FileName)) + return; + + auto FileSize = getFileSize(FileName); + if (std::remove(FileName.c_str())) { + throw sycl::exception(make_error_code(errc::runtime), + "Failed to evict cache entry: " + FileName); + } else { + PersistentDeviceCodeCache::trace("File removed: " + FileName); + CurrCacheSize -= FileSize; + } + }; + + // If removal fails due to a race, retry. + // Races are rare, but can happen if another process is reading the + // file. Locking down the entire cache and blocking all readers would be + // inefficient. + try { + RemoveFileAndSubtractSize(SrcFile); + RemoveFileAndSubtractSize(BinFile); + } catch (...) { + continue; + } } } @@ -426,7 +431,6 @@ void PersistentDeviceCodeCache::putItemToDisc( return; std::string FileName; - bool IsWriteSuccess = false; try { OSUtil::makeDir(DirName.c_str()); FileName = getUniqueFilename(DirName); @@ -437,7 +441,10 @@ void PersistentDeviceCodeCache::putItemToDisc( trace("device binary has been cached: " + FullFileName); writeSourceItem(FileName + ".src", Devices[DeviceIndex], SortedImgs, SpecConsts, BuildOptionsString); - IsWriteSuccess = true; + + // Update Total cache size after adding the new items. + TotalSize += getFileSize(FileName + ".src"); + TotalSize += getFileSize(FileName + ".bin"); } else { PersistentDeviceCodeCache::trace("cache lock not owned " + FileName); } @@ -450,11 +457,6 @@ void PersistentDeviceCodeCache::putItemToDisc( std::string("error outputting persistent cache: ") + std::strerror(errno)); } - - if (IsWriteSuccess) { - TotalSize += getFileSize(FileName + ".src"); - TotalSize += getFileSize(FileName + ".bin"); - } } // Update the cache size file and trigger cache eviction if needed. diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index d73178c279f6f..6018760cb2808 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -623,8 +623,8 @@ TEST_P(PersistentDeviceCodeCache, ConcurentReadWriteCacheFileSize) { ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(CacheRoot)); ASSERT_NO_ERROR(llvm::sys::fs::create_directories(CacheRoot)); - // Insanely large value (1GB) to not trigger eviction. This test just checks - // for deadlocks/crashes when updating the size file concurrently. + // Insanely large value (1GB) to not trigger eviction. This test just + // checks for deadlocks/crashes when updating the size file concurrently. SetDiskCacheEvictionEnv("1000000000"); ConcurentReadWriteCache(1, 100); } From 0ca7befa9c10870461eaf2838aa2aa3ba3fd1abf Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Sun, 15 Dec 2024 09:58:43 -0800 Subject: [PATCH 11/16] Instrument cache operations --- .../detail/persistent_device_code_cache.cpp | 22 ++++++++++++-- .../detail/persistent_device_code_cache.hpp | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 384d60d3cca96..6cd3c486e708b 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -16,6 +16,8 @@ #include #include +#include + #if defined(__SYCL_RT_OS_POSIX_SUPPORT) #include #else @@ -402,6 +404,10 @@ void PersistentDeviceCodeCache::putItemToDisc( const SerializedObj &SpecConsts, const std::string &BuildOptionsString, const ur_program_handle_t &NativePrg) { +#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE + InstrumentCache Instrument{"putItemToDisc: "}; +#endif + if (!areImagesCacheable(Imgs)) return; @@ -460,8 +466,12 @@ void PersistentDeviceCodeCache::putItemToDisc( } // Update the cache size file and trigger cache eviction if needed. - if (TotalSize) + if (TotalSize) { +#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE + InstrumentCache Instrument{"Eviction: "}; +#endif updateCacheFileSizeAndTriggerEviction(getRootDir(), TotalSize); + } } void PersistentDeviceCodeCache::putCompiledKernelToDisc( @@ -513,6 +523,10 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( if (!areImagesCacheable(Imgs)) return {}; +#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE + InstrumentCache Instrument{"getItemFromDisc: "}; +#endif + std::vector SortedImgs = getSortedImages(Imgs); std::vector> Binaries(Devices.size()); std::string FileNames; @@ -538,8 +552,12 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( // Explicitly update the access time of the file. This is required for // eviction. - if (isEvictionEnabled()) + if (isEvictionEnabled()) { +#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE + InstrumentCache Instrument{"Updating file access time: "}; +#endif updateFileModificationTime(FileName + ".bin"); + } FileNames += FullFileName + ";"; break; diff --git a/sycl/source/detail/persistent_device_code_cache.hpp b/sycl/source/detail/persistent_device_code_cache.hpp index 392335b9d0604..cd0714a605cd7 100644 --- a/sycl/source/detail/persistent_device_code_cache.hpp +++ b/sycl/source/detail/persistent_device_code_cache.hpp @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -20,6 +21,12 @@ #include #include +#define __SYCL_INSTRUMENT_PERSISTENT_CACHE + +#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE +#include +#endif + namespace sycl { inline namespace _V1 { namespace detail { @@ -90,6 +97,29 @@ class PersistentDeviceCodeCache { * - on cache read operation it is treated as cache miss. */ private: +#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE + // Class to instrument cache operations. + class InstrumentCache { + std::string PrintMsg; + std::chrono::high_resolution_clock::time_point StartTime; + + public: + InstrumentCache(const std::string &Name) : PrintMsg(Name) { + // Store start time. + StartTime = std::chrono::high_resolution_clock::now(); + } + ~InstrumentCache() { + // Calculate time spent and print message. + auto EndTime = std::chrono::high_resolution_clock::now(); + auto Duration = std::chrono::duration_cast( + EndTime - StartTime) + .count(); + PersistentDeviceCodeCache::trace(PrintMsg + std::to_string(Duration) + + "ns"); + } + }; +#endif + /* Write built binary to persistent cache * Format: BinarySize, Binary */ From 06d01a7de09256ccf1cbfbee222258bdb8ad0fc4 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Wed, 18 Dec 2024 15:09:51 -0800 Subject: [PATCH 12/16] Store last access time of a cache entry in a file --- sycl/include/sycl/detail/os_util.hpp | 14 +-- sycl/source/detail/os_util.cpp | 111 ++++-------------- .../detail/persistent_device_code_cache.cpp | 74 ++++++------ .../detail/persistent_device_code_cache.hpp | 31 +---- 4 files changed, 73 insertions(+), 157 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index 5fcee1fc54ffd..fee142c481d23 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -12,10 +12,10 @@ #include // for __SYCL_EXPORT -#include // for size_t +#include // for size_t +#include #include // for string #include // for stat -#include // for vector #ifdef _WIN32 #define __SYCL_RT_OS_WINDOWS @@ -100,12 +100,10 @@ size_t getDirectorySize(const std::string &Path, bool ignoreError); // Get size of file in bytes. size_t getFileSize(const std::string &Path); -// Get list of all files in the directory along with its last modification time. -std::vector> -getFilesWithLastModificationTime(const std::string &Path, bool ignoreError); - -// Function to update file modification time with current time. -void updateFileModificationTime(const std::string &Path); +// Function to recursively iterate over the directory and execute +// 'Func' on each regular file. +void fileTreeWalk(const std::string Path, + std::function Func); } // namespace detail } // namespace _V1 diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index e6c6e8acef1e5..878987323fc57 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -285,106 +285,43 @@ int OSUtil::makeDir(const char *Dir) { return 0; } -// Get size of a directory in bytes. -size_t getDirectorySize(const std::string &Path, bool IgnoreError = false) { - size_t DirSizeVar = 0; - std::error_code EC; - for (auto It = fs::recursive_directory_iterator(Path, EC); - It != fs::recursive_directory_iterator(); It.increment(EC)) { - // Errors can happen if a file was removed/added during the iteration. - if (EC && !IgnoreError) - throw sycl::exception(make_error_code(errc::runtime), - "Failed to get directory size: " + Path + "\n" + - EC.message()); - - if (fs::is_regular_file(It->path())) - DirSizeVar += getFileSize(It->path().string()); - } - return DirSizeVar; -} - // Get size of file in bytes. size_t getFileSize(const std::string &Path) { return static_cast(fs::file_size(Path)); } -// Get list of all files in the directory along with its last modification time. -std::vector> -getFilesWithLastModificationTime(const std::string &Path, - bool IgnoreError = false) { - std::vector> Files = {}; +// Function to recursively iterate over the directory and execute +// 'Func' on each regular file. +void fileTreeWalk(const std::string Path, + std::function Func) { + std::error_code EC; for (auto It = fs::recursive_directory_iterator(Path, EC); It != fs::recursive_directory_iterator(); It.increment(EC)) { + // Errors can happen if a file was removed/added during the iteration. - if (EC && !IgnoreError) - throw sycl::exception(make_error_code(errc::runtime), - "Failed to get files with access time: " + Path + - "\n" + EC.message()); - - const std::string FileName = It->path().string(); - if (fs::is_regular_file(It->path())) { -// For Linux and Darwin, use stats. -#if defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - struct stat StatBuf; - if (stat(FileName.c_str(), &StatBuf) == 0) - Files.push_back({StatBuf.st_atim.tv_nsec, FileName}); -#elif defined(__SYCL_RT_OS_WINDOWS) - // Use GetFileAttributeExA to get file modification time. - WIN32_FILE_ATTRIBUTE_DATA FileAttr; - if (GetFileAttributesExA(FileName.c_str(), GetFileExInfoStandard, - &FileAttr)) { - ULARGE_INTEGER AccessTime; - AccessTime.HighPart = FileAttr.ftLastWriteTime.dwHighDateTime; - AccessTime.LowPart = FileAttr.ftLastWriteTime.dwLowDateTime; - Files.push_back({AccessTime.QuadPart, FileName}); - } else - throw sycl::exception(make_error_code(errc::runtime), - "Failed to get file attributes for: " + FileName); -#endif // __SYCL_RT_OS - } - } + if (EC) + throw sycl::exception( + make_error_code(errc::runtime), + "Failed to do File Tree Walk. Ensure that the directory is not " + "getting updated while FileTreeWalk is in progress.: " + + Path + "\n" + EC.message()); - return Files; + if (fs::is_regular_file(It->path())) + Func(It->path().string()); + } } -// Function to update file modification time with current time. -void updateFileModificationTime(const std::string &Path) { - -#if defined(__SYCL_RT_OS_WINDOWS) - // For Windows, use SetFileTime to update file access time. - - // Open file with FILE_FLAG_WRITE_THROUGH and FILE_FLAG_NO_BUFFERING flags - // to ensure that the file time is updated on disk, asap. - HANDLE hFile = CreateFileA( - Path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, - NULL); - if (hFile != INVALID_HANDLE_VALUE) { - FILETIME ft; - GetSystemTimeAsFileTime(&ft); - if (!SetFileTime(hFile, NULL, NULL, &ft)) { - // Print full error. - char *errorText = nullptr; - FormatMessageA( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPSTR)&errorText, 0, NULL); - - throw sycl::exception(make_error_code(errc::runtime), - "Failed to update file access time: " + Path); - } - CloseHandle(hFile); - } else { - throw sycl::exception(make_error_code(errc::runtime), - "Failed to open file: " + Path); - } +// Get size of a directory in bytes. +size_t getDirectorySize(const std::string &Path, bool IgnoreError = false) { + size_t DirSizeVar = 0; -#elif defined(__SYCL_RT_OS_LINUX) || defined(__SYCL_RT_OS_DARWIN) - // For Linux and Darwin, use utimensat to update file modification time. - utimensat(0, Path.c_str(), nullptr, 0); -#endif // __SYCL_RT_OS + auto CollectFIleSize = [&DirSizeVar](const std::string Path) { + DirSizeVar += getFileSize(Path); + }; + fileTreeWalk(Path, CollectFIleSize); + + return DirSizeVar; } } // namespace detail diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 6cd3c486e708b..4ac689a131af1 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -180,6 +180,25 @@ getProgramBinaryData(const ur_program_handle_t &NativePrg, return Result; } +// Save the current time in a file. +void PersistentDeviceCodeCache::saveCurrentTimeInAFile(std::string FileName) { + // Lock the file to prevent concurrent writes. + LockCacheItem Lock{FileName}; + if (Lock.isOwned()) { + try { + std::ofstream FileStream{FileName, std::ios::trunc}; + FileStream << std::chrono::high_resolution_clock::now() + .time_since_epoch() + .count(); + FileStream.close(); + } catch (std::exception &e) { + throw sycl::exception(make_error_code(errc::runtime), + "Failed to save current time in a file: " + + FileName + "\n" + std::string(e.what())); + } + } +} + // Check if cache_size.txt file is present in the cache root directory. // If not, create it and populate it with the size of the cache directory. void PersistentDeviceCodeCache::repopulateCacheSizeFile( @@ -245,16 +264,25 @@ void PersistentDeviceCodeCache::evictItemsFromCache( // modification time. std::vector> FilesWithAccessTime; - // getFileWithLastModificationTime can throw if any new file is created or - // removed during the iteration. Retry in that case. When eviction is in - // progress,, we don't insert any new item but processes can still read the - // cache. Reading from cache can create/remove .lock file which can cause the - // exception. + auto CollectFileAccessTime = [&FilesWithAccessTime](const std::string File) { + if (File.find("_access_time.txt") != std::string::npos) { + std::ifstream FileStream{File}; + uint64_t AccessTime; + FileStream >> AccessTime; + FilesWithAccessTime.push_back({AccessTime, File}); + } + }; + + // fileTreeWalk can throw if any new file is created or removed during the + // iteration. Retry in that case. When eviction is in progress, we don't + // insert any new item but processes can still read the cache. Reading from + // cache can create/remove .lock file which can cause the exception. while (true) { try { - FilesWithAccessTime = getFilesWithLastModificationTime(CacheRoot, false); + fileTreeWalk(CacheRoot, CollectFileAccessTime); break; } catch (...) { + FilesWithAccessTime.clear(); // If the cache directory is removed during the iteration, retry. continue; } @@ -272,14 +300,8 @@ void PersistentDeviceCodeCache::evictItemsFromCache( size_t CurrCacheSize = CacheSize; for (const auto &File : FilesWithAccessTime) { - // Remove .bin/.src/.lock extension from the file name. - auto ExtLoc = File.second.find_last_of("."); - const std::string FileNameWOExt = File.second.substr(0, ExtLoc); - const std::string Extension = File.second.substr(ExtLoc); - - if (Extension != ".bin") - continue; - + int pos = File.second.find("_access_time.txt"); + const std::string FileNameWOExt = File.second.substr(0, pos); const std::string BinFile = FileNameWOExt + ".bin"; const std::string SrcFile = FileNameWOExt + ".src"; @@ -404,10 +426,6 @@ void PersistentDeviceCodeCache::putItemToDisc( const SerializedObj &SpecConsts, const std::string &BuildOptionsString, const ur_program_handle_t &NativePrg) { -#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE - InstrumentCache Instrument{"putItemToDisc: "}; -#endif - if (!areImagesCacheable(Imgs)) return; @@ -451,6 +469,8 @@ void PersistentDeviceCodeCache::putItemToDisc( // Update Total cache size after adding the new items. TotalSize += getFileSize(FileName + ".src"); TotalSize += getFileSize(FileName + ".bin"); + + saveCurrentTimeInAFile(FileName + "_access_time.txt"); } else { PersistentDeviceCodeCache::trace("cache lock not owned " + FileName); } @@ -466,12 +486,8 @@ void PersistentDeviceCodeCache::putItemToDisc( } // Update the cache size file and trigger cache eviction if needed. - if (TotalSize) { -#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE - InstrumentCache Instrument{"Eviction: "}; -#endif + if (TotalSize) updateCacheFileSizeAndTriggerEviction(getRootDir(), TotalSize); - } } void PersistentDeviceCodeCache::putCompiledKernelToDisc( @@ -523,10 +539,6 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( if (!areImagesCacheable(Imgs)) return {}; -#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE - InstrumentCache Instrument{"getItemFromDisc: "}; -#endif - std::vector SortedImgs = getSortedImages(Imgs); std::vector> Binaries(Devices.size()); std::string FileNames; @@ -552,12 +564,8 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( // Explicitly update the access time of the file. This is required for // eviction. - if (isEvictionEnabled()) { -#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE - InstrumentCache Instrument{"Updating file access time: "}; -#endif - updateFileModificationTime(FileName + ".bin"); - } + if (isEvictionEnabled()) + saveCurrentTimeInAFile(FileName + "_access_time.txt"); FileNames += FullFileName + ";"; break; diff --git a/sycl/source/detail/persistent_device_code_cache.hpp b/sycl/source/detail/persistent_device_code_cache.hpp index cd0714a605cd7..c7f9b83f9ed2c 100644 --- a/sycl/source/detail/persistent_device_code_cache.hpp +++ b/sycl/source/detail/persistent_device_code_cache.hpp @@ -21,12 +21,6 @@ #include #include -#define __SYCL_INSTRUMENT_PERSISTENT_CACHE - -#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE -#include -#endif - namespace sycl { inline namespace _V1 { namespace detail { @@ -97,29 +91,6 @@ class PersistentDeviceCodeCache { * - on cache read operation it is treated as cache miss. */ private: -#ifdef __SYCL_INSTRUMENT_PERSISTENT_CACHE - // Class to instrument cache operations. - class InstrumentCache { - std::string PrintMsg; - std::chrono::high_resolution_clock::time_point StartTime; - - public: - InstrumentCache(const std::string &Name) : PrintMsg(Name) { - // Store start time. - StartTime = std::chrono::high_resolution_clock::now(); - } - ~InstrumentCache() { - // Calculate time spent and print message. - auto EndTime = std::chrono::high_resolution_clock::now(); - auto Duration = std::chrono::duration_cast( - EndTime - StartTime) - .count(); - PersistentDeviceCodeCache::trace(PrintMsg + std::to_string(Duration) + - "ns"); - } - }; -#endif - /* Write built binary to persistent cache * Format: BinarySize, Binary */ @@ -259,6 +230,8 @@ class PersistentDeviceCodeCache { static void evictItemsFromCache(const std::string &CacheRoot, size_t CacheSize, size_t MaxCacheSize); + static void saveCurrentTimeInAFile(std::string FileName); + // Check if eviction is enabled. static bool isEvictionEnabled() { return SYCLConfig::isPersistentCacheEvictionEnabled(); From cc36361a8634ad02881cc099481d8af31849b1a2 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Wed, 18 Dec 2024 15:33:36 -0800 Subject: [PATCH 13/16] Cleanup --- sycl/source/detail/os_util.cpp | 2 -- .../detail/persistent_device_code_cache.cpp | 15 +++++++-------- .../detail/persistent_device_code_cache.hpp | 8 +++++++- .../PersistentDeviceCodeCache.cpp | 10 ---------- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 878987323fc57..d0431d7ef40c7 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -10,7 +10,6 @@ #include #include -#include #include // For GCC versions less than 8, use experimental/filesystem. @@ -51,7 +50,6 @@ namespace fs = std::experimental::filesystem; #elif defined(__SYCL_RT_OS_DARWIN) #include -#include #include #include diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 4ac689a131af1..94e9d85112a69 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -12,12 +12,11 @@ #include #include +#include #include #include #include -#include - #if defined(__SYCL_RT_OS_POSIX_SUPPORT) #include #else @@ -252,7 +251,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( // Create a file eviction_in_progress.lock to indicate that eviction is in // progress. This file is used to prevent two processes from evicting the // cache at the same time. - LockCacheItem Lock{CacheRoot + "/eviction_in_progress"}; + LockCacheItem Lock{CacheRoot + EvictionInProgressFileSuffix}; if (!Lock.isOwned()) { // If some other process is evicting the cache, return. PersistentDeviceCodeCache::trace( @@ -265,7 +264,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( std::vector> FilesWithAccessTime; auto CollectFileAccessTime = [&FilesWithAccessTime](const std::string File) { - if (File.find("_access_time.txt") != std::string::npos) { + if (File.find(CacheEntryAccessTimeSuffix) != std::string::npos) { std::ifstream FileStream{File}; uint64_t AccessTime; FileStream >> AccessTime; @@ -300,7 +299,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( size_t CurrCacheSize = CacheSize; for (const auto &File : FilesWithAccessTime) { - int pos = File.second.find("_access_time.txt"); + int pos = File.second.find(CacheEntryAccessTimeSuffix); const std::string FileNameWOExt = File.second.substr(0, pos); const std::string BinFile = FileNameWOExt + ".bin"; const std::string SrcFile = FileNameWOExt + ".src"; @@ -434,7 +433,7 @@ void PersistentDeviceCodeCache::putItemToDisc( // Do not insert any new item if eviction is in progress. // Since evictions are rare, we can afford to spin lock here. const std::string EvictionInProgressFile = - getRootDir() + "/eviction_in_progress.lock"; + getRootDir() + EvictionInProgressFileSuffix; // Stall until the other process finishes eviction. while (OSUtil::isPathPresent(EvictionInProgressFile)) continue; @@ -470,7 +469,7 @@ void PersistentDeviceCodeCache::putItemToDisc( TotalSize += getFileSize(FileName + ".src"); TotalSize += getFileSize(FileName + ".bin"); - saveCurrentTimeInAFile(FileName + "_access_time.txt"); + saveCurrentTimeInAFile(FileName + CacheEntryAccessTimeSuffix); } else { PersistentDeviceCodeCache::trace("cache lock not owned " + FileName); } @@ -565,7 +564,7 @@ std::vector> PersistentDeviceCodeCache::getItemFromDisc( // Explicitly update the access time of the file. This is required for // eviction. if (isEvictionEnabled()) - saveCurrentTimeInAFile(FileName + "_access_time.txt"); + saveCurrentTimeInAFile(FileName + CacheEntryAccessTimeSuffix); FileNames += FullFileName + ";"; break; diff --git a/sycl/source/detail/persistent_device_code_cache.hpp b/sycl/source/detail/persistent_device_code_cache.hpp index c7f9b83f9ed2c..a976781ed9a82 100644 --- a/sycl/source/detail/persistent_device_code_cache.hpp +++ b/sycl/source/detail/persistent_device_code_cache.hpp @@ -8,7 +8,6 @@ #pragma once -#include #include #include #include @@ -236,6 +235,13 @@ class PersistentDeviceCodeCache { static bool isEvictionEnabled() { return SYCLConfig::isPersistentCacheEvictionEnabled(); } + + // Suffix for access time file. Every cache entry will have one. + static inline std::string CacheEntryAccessTimeSuffix = "_access_time.txt"; + // Suffix for eviction in progress file. It is created when eviction is + // triggered and removed when eviction is done. + static inline std::string EvictionInProgressFileSuffix = + "_eviction_in_progress"; }; } // namespace detail } // namespace _V1 diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index 6018760cb2808..4faaf546608c0 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -559,25 +559,15 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { std::string BuildOptions{"--eviction"}; // Put 3 items to the cache. - // On Windows, for NTFS, the file timestamp resolution is 100ns, - // but on Linux, for EXT4, the file timestamp resolution is few milliseconds. - // So, to make this test deterministic, we need to sleep for a while, - // say 20ms, between each putItemToDisc/getItemFromDisc call. detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - // Retrieve 0.bin from the cache. auto Res = detail::PersistentDeviceCodeCache::getItemFromDisc( {Dev}, {&Img}, {}, BuildOptions); From 202c71cd240ce2f3a5b8d400c4c87ba65a175d62 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Wed, 18 Dec 2024 15:37:38 -0800 Subject: [PATCH 14/16] Remove unused parameter --- sycl/include/sycl/detail/os_util.hpp | 2 +- sycl/source/detail/os_util.cpp | 2 +- sycl/source/detail/persistent_device_code_cache.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index fee142c481d23..a0c3a8483373e 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -95,7 +95,7 @@ class __SYCL_EXPORT OSUtil { // exporting them as ABI. They are only used in persistent cache // implementation and should not be exposed to the end users. // Get size of directory in bytes. -size_t getDirectorySize(const std::string &Path, bool ignoreError); +size_t getDirectorySize(const std::string &Path); // Get size of file in bytes. size_t getFileSize(const std::string &Path); diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index d0431d7ef40c7..8c4c0bff1293d 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -311,7 +311,7 @@ void fileTreeWalk(const std::string Path, } // Get size of a directory in bytes. -size_t getDirectorySize(const std::string &Path, bool IgnoreError = false) { +size_t getDirectorySize(const std::string &Path) { size_t DirSizeVar = 0; auto CollectFIleSize = [&DirSizeVar](const std::string Path) { diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 94e9d85112a69..2376f3090c252 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -233,7 +233,7 @@ void PersistentDeviceCodeCache::repopulateCacheSizeFile( // Calculate the size of the cache directory. // During directory size calculation, do not add anything // in the cache. Otherwise, we'll get a std::fs_error. - size_t CacheSize = getDirectorySize(CacheRoot, true); + size_t CacheSize = getDirectorySize(CacheRoot); std::ofstream FileStream{CacheSizeFile}; FileStream << CacheSize; From 2375a186fff962b8ca45a177664629c64f6fba51 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Thu, 19 Dec 2024 12:31:03 -0800 Subject: [PATCH 15/16] Evict half the cache when triggered --- .../detail/persistent_device_code_cache.cpp | 5 ++- .../PersistentDeviceCodeCache.cpp | 32 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/sycl/source/detail/persistent_device_code_cache.cpp b/sycl/source/detail/persistent_device_code_cache.cpp index 2376f3090c252..ae18643ef1ca9 100644 --- a/sycl/source/detail/persistent_device_code_cache.cpp +++ b/sycl/source/detail/persistent_device_code_cache.cpp @@ -248,6 +248,9 @@ void PersistentDeviceCodeCache::evictItemsFromCache( const std::string &CacheRoot, size_t CacheSize, size_t MaxCacheSize) { PersistentDeviceCodeCache::trace("Cache eviction triggered."); + // EVict half of the cache. + constexpr float HowMuchCacheToEvict = 0.5; + // Create a file eviction_in_progress.lock to indicate that eviction is in // progress. This file is used to prevent two processes from evicting the // cache at the same time. @@ -340,7 +343,7 @@ void PersistentDeviceCodeCache::evictItemsFromCache( } // If the cache size is less than the threshold, break. - if (CurrCacheSize <= MaxCacheSize) + if (CurrCacheSize <= (size_t)(HowMuchCacheToEvict * MaxCacheSize)) break; } diff --git a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp index 4faaf546608c0..814fcb318d807 100644 --- a/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp +++ b/sycl/unittests/kernel-and-program/PersistentDeviceCodeCache.cpp @@ -562,6 +562,10 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); + std::string ItemDir = detail::PersistentDeviceCodeCache::getCacheItemPath( + Dev, {&Img}, {}, BuildOptions); + size_t SizeOfOneEntry = (size_t)(detail::getDirectorySize(ItemDir)); + detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); @@ -573,35 +577,33 @@ TEST_P(PersistentDeviceCodeCache, BasicEviction) { {Dev}, {&Img}, {}, BuildOptions); // Get the number of binary files in the cached item folder. - std::string ItemDir = detail::PersistentDeviceCodeCache::getCacheItemPath( - Dev, {&Img}, {}, BuildOptions); auto BinFiles = getBinaryFileNames(ItemDir); - EXPECT_EQ(BinFiles.size(), static_cast(3)) << "Missing binary files. Eviction should not have happened."; - // Get Cache size and size of each entry. Set eviction threshold so that - // just one item is evicted. - size_t SizeOfOneEntry = - (size_t)(detail::getDirectorySize(CacheRoot, false)) + 10; - // Set SYCL_CACHE_MAX_SIZE. - SetDiskCacheEvictionEnv(std::to_string(SizeOfOneEntry).c_str()); + SetDiskCacheEvictionEnv(std::to_string(3 * SizeOfOneEntry).c_str()); - // Put 4th item to the cache. This should trigger eviction. Only the first - // item should be evicted. + // Put 4th item to the cache. This should trigger eviction. Three of the + // items should be evicted as we evict till the size of cache is less than + // the half of cache size. detail::PersistentDeviceCodeCache::putItemToDisc({Dev}, {&Img}, {}, BuildOptions, NativeProg); - // We should have three binary files: 0.bin, 2.bin, 3.bin. + // We should have two binary files: 0.bin, 3.bin. BinFiles = getBinaryFileNames(ItemDir); - EXPECT_EQ(BinFiles.size(), static_cast(3)) + EXPECT_EQ(BinFiles.size(), static_cast(1)) << "Eviction failed. Wrong number of binary files in the cache."; - // Check that 1.bin was evicted. - for (const auto &File : BinFiles) + // Check that 1.bin, 2.bin, and 0.bin was evicted. + for (const auto &File : BinFiles) { EXPECT_NE(File, "1.bin") << "Eviction failed. 1.bin should have been evicted."; + EXPECT_NE(File, "2.bin") + << "Eviction failed. 2.bin should have been evicted."; + EXPECT_NE(File, "0.bin") + << "Eviction failed. 0.bin should have been evicted."; + } ASSERT_NO_ERROR(llvm::sys::fs::remove_directories(ItemDir)); } From 0122fce3ef2b3f8747affebcfe8369f7abbfa500 Mon Sep 17 00:00:00 2001 From: "Agarwal, Udit" Date: Fri, 20 Dec 2024 09:01:42 -0800 Subject: [PATCH 16/16] Update docs --- sycl/doc/design/KernelProgramCache.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sycl/doc/design/KernelProgramCache.md b/sycl/doc/design/KernelProgramCache.md index fd242e7d9749d..487e427980f16 100644 --- a/sycl/doc/design/KernelProgramCache.md +++ b/sycl/doc/design/KernelProgramCache.md @@ -415,15 +415,16 @@ When adding a new program to cache, we check if the size of the program cache ex #### Persistent cache eviction -Persistent cache eviction is going to be applied based on file last access -(read/write) date (access time). On SYCL application shutdown phase cache -eviction process is initiated which walks through cache directories as follows: - -- if the file is locked, go to the next file; -- otherwise check file access time: - - if file access time is above threshold, delete the file and remove parent - directory while they are unlocked and empty; - - otherwise do nothing. +Persistent cache eviction can be enabled using the SYCL_CACHE_MAX_SIZE environment variable and is based on the LRU strategy. + +- A new file, called `cache_size.txt`, is created at the root of the persistent cache directory. This file contains the total size of the cache in bytes. When a new item is added to the cache, the size of the item is added to the total size in the `cache_size.txt` file. When the total size exceeds the threshold, the eviction process is initiated. + +- Whenever a cache entry is added or accessed, the corresponding cache item directory is updated with the current time. This is done by creating a new file, called `_access_time.txt`, in the cache item directory. This file contains the current time in nanoseconds since the epoch. When the eviction process is initiated, we use this file to determine the last access time of the cache item. + +- When a new item is added to the cache, we check if the total size exceeds the threshold. If so, we iterate through the cache item directories and delete the least recently accessed items until the total size is below half the cache size. + +Note that once the eviction is triggered, the cache size is reduced to half the cache size to avoid frequent eviction. + ## Cache limitations