Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SYCL] Implement eviction in persistent cache #16289

Merged
merged 17 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions sycl/doc/design/KernelProgramCache.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<entry name>_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

Expand Down
17 changes: 16 additions & 1 deletion sycl/include/sycl/detail/os_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

#include <sycl/detail/export.hpp> // for __SYCL_EXPORT

#include <cstdlib> // for size_t
#include <cstdlib> // for size_t
#include <functional>
#include <string> // for string
#include <sys/stat.h> // for stat

Expand Down Expand Up @@ -90,6 +91,20 @@ class __SYCL_EXPORT OSUtil {
}
};

// 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);

// Function to recursively iterate over the directory and execute
// 'Func' on each regular file.
void fileTreeWalk(const std::string Path,
std::function<void(const std::string)> Func);

} // namespace detail
} // namespace _V1
} // namespace sycl
50 changes: 50 additions & 0 deletions sycl/source/detail/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,56 @@ template <> class SYCLConfig<SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD> {
}
};

// 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<SYCL_CACHE_MAX_SIZE> {
using BaseT = SYCLConfigBase<SYCL_CACHE_MAX_SIZE>;

public:
static long long get() { return getCachedValue(); }
static void reset() { (void)getCachedValue(true); }

static long long getProgramCacheSize() { return getCachedValue(); }

static bool isPersistentCacheEvictionEnabled() {
return getProgramCacheSize() > 0;
}

private:
static long long getCachedValue(bool ResetCache = false) {
const auto Parser = []() {
const char *ValStr = BaseT::getRawValue();

// Disable eviction by default.
if (!ValStr)
return (long long)0;

long long CacheSize = 0;
try {
CacheSize = std::stoll(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
Expand Down
51 changes: 48 additions & 3 deletions sycl/source/detail/os_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
//===----------------------------------------------------------------------===//

#include <sycl/detail/os_util.hpp>
#include <sycl/exception.hpp>

#include <cassert>
#include <limits>

#if __GNUC__ && __GNUC__ < 8
// Don't include <filesystem> for GCC versions less than 8
// For GCC versions less than 8, use experimental/filesystem.
#if defined(__has_include) && __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif defined(__has_include) && __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#include <filesystem> // C++ 17 std::create_directories
#error "OSUtils requires C++ filesystem support"
#endif

#if defined(__SYCL_RT_OS_LINUX)
Expand Down Expand Up @@ -277,6 +283,45 @@ int OSUtil::makeDir(const char *Dir) {
return 0;
}

// Get size of file in bytes.
size_t getFileSize(const std::string &Path) {
return static_cast<size_t>(fs::file_size(Path));
}

// Function to recursively iterate over the directory and execute
// 'Func' on each regular file.
void fileTreeWalk(const std::string Path,
std::function<void(const std::string)> 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)
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());

if (fs::is_regular_file(It->path()))
Func(It->path().string());
}
}

// Get size of a directory in bytes.
size_t getDirectorySize(const std::string &Path) {
size_t DirSizeVar = 0;

auto CollectFIleSize = [&DirSizeVar](const std::string Path) {
DirSizeVar += getFileSize(Path);
};
fileTreeWalk(Path, CollectFIleSize);

return DirSizeVar;
}

} // namespace detail
} // namespace _V1
} // namespace sycl
Loading
Loading