Skip to content

Commit 52015d8

Browse files
[SYCL] Implement eviction in persistent cache (#16289)
This PR implements eviction for persistent cache. Eviction is disabled by default and can be controlled by the user via `SYCL_CACHE_MAX_SIZE` variable. Here's how eviction works: 1. File size: A file called, `cache_size.txt`, is stored at the root of persistent cache. Every time a process adds a new entry to cache, it will also update the `cache_size.txt` file. This file is used to track the size of persistent cache. For backwards compatibility, if SYCL RT does not find `cache_size.txt` in the cache root, it will create once. All access to `cache_size.txt` are done using `LockCacheItem`, to prevent data races. 2. When adding a new entry to cache, SYCL RT will check the `cache_size.txt` file and if the cache size exceeds the threshold, eviction is triggered. 3. When a cache entry is created/accessed, SYCL RT create a file in that cache entry to store the access time. This file is later read during eviction. 4. During eviction, SYCL RT will determine the last access time of each cache entry and items are evicted based on the LRU policy.
1 parent cc4dee4 commit 52015d8

File tree

8 files changed

+604
-19
lines changed

8 files changed

+604
-19
lines changed

sycl/doc/design/KernelProgramCache.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -415,15 +415,16 @@ When adding a new program to cache, we check if the size of the program cache ex
415415

416416
#### Persistent cache eviction
417417

418-
Persistent cache eviction is going to be applied based on file last access
419-
(read/write) date (access time). On SYCL application shutdown phase cache
420-
eviction process is initiated which walks through cache directories as follows:
421-
422-
- if the file is locked, go to the next file;
423-
- otherwise check file access time:
424-
- if file access time is above threshold, delete the file and remove parent
425-
directory while they are unlocked and empty;
426-
- otherwise do nothing.
418+
Persistent cache eviction can be enabled using the SYCL_CACHE_MAX_SIZE environment variable and is based on the LRU strategy.
419+
420+
- 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.
421+
422+
- 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.
423+
424+
- 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.
425+
426+
Note that once the eviction is triggered, the cache size is reduced to half the cache size to avoid frequent eviction.
427+
427428

428429
## Cache limitations
429430

sycl/include/sycl/detail/os_util.hpp

+16-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212

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

15-
#include <cstdlib> // for size_t
15+
#include <cstdlib> // for size_t
16+
#include <functional>
1617
#include <string> // for string
1718
#include <sys/stat.h> // for stat
1819

@@ -90,6 +91,20 @@ class __SYCL_EXPORT OSUtil {
9091
}
9192
};
9293

94+
// These functions are not a part of OSUtils class to prevent
95+
// exporting them as ABI. They are only used in persistent cache
96+
// implementation and should not be exposed to the end users.
97+
// Get size of directory in bytes.
98+
size_t getDirectorySize(const std::string &Path);
99+
100+
// Get size of file in bytes.
101+
size_t getFileSize(const std::string &Path);
102+
103+
// Function to recursively iterate over the directory and execute
104+
// 'Func' on each regular file.
105+
void fileTreeWalk(const std::string Path,
106+
std::function<void(const std::string)> Func);
107+
93108
} // namespace detail
94109
} // namespace _V1
95110
} // namespace sycl

sycl/source/detail/config.hpp

+50
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,56 @@ template <> class SYCLConfig<SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD> {
806806
}
807807
};
808808

809+
// SYCL_CACHE_MAX_SIZE accepts an integer that specifies
810+
// the maximum size of the on-disk Program cache.
811+
// Cache eviction is performed when the cache size exceeds the threshold.
812+
// The thresholds are specified in bytes.
813+
// The default value is "0" which means that eviction is disabled.
814+
template <> class SYCLConfig<SYCL_CACHE_MAX_SIZE> {
815+
using BaseT = SYCLConfigBase<SYCL_CACHE_MAX_SIZE>;
816+
817+
public:
818+
static long long get() { return getCachedValue(); }
819+
static void reset() { (void)getCachedValue(true); }
820+
821+
static long long getProgramCacheSize() { return getCachedValue(); }
822+
823+
static bool isPersistentCacheEvictionEnabled() {
824+
return getProgramCacheSize() > 0;
825+
}
826+
827+
private:
828+
static long long getCachedValue(bool ResetCache = false) {
829+
const auto Parser = []() {
830+
const char *ValStr = BaseT::getRawValue();
831+
832+
// Disable eviction by default.
833+
if (!ValStr)
834+
return (long long)0;
835+
836+
long long CacheSize = 0;
837+
try {
838+
CacheSize = std::stoll(ValStr);
839+
if (CacheSize < 0)
840+
throw INVALID_CONFIG_EXCEPTION(BaseT, "Value must be non-negative");
841+
} catch (...) {
842+
std::string Msg =
843+
std::string{"Invalid input to SYCL_CACHE_MAX_SIZE. Please try "
844+
"a positive integer."};
845+
throw exception(make_error_code(errc::runtime), Msg);
846+
}
847+
848+
return CacheSize;
849+
};
850+
851+
static auto EvictionThresholds = Parser();
852+
if (ResetCache)
853+
EvictionThresholds = Parser();
854+
855+
return EvictionThresholds;
856+
}
857+
};
858+
809859
#undef INVALID_CONFIG_EXCEPTION
810860

811861
} // namespace detail

sycl/source/detail/os_util.cpp

+48-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@
77
//===----------------------------------------------------------------------===//
88

99
#include <sycl/detail/os_util.hpp>
10+
#include <sycl/exception.hpp>
1011

1112
#include <cassert>
1213
#include <limits>
1314

14-
#if __GNUC__ && __GNUC__ < 8
15-
// Don't include <filesystem> for GCC versions less than 8
15+
// For GCC versions less than 8, use experimental/filesystem.
16+
#if defined(__has_include) && __has_include(<filesystem>)
17+
#include <filesystem>
18+
namespace fs = std::filesystem;
19+
#elif defined(__has_include) && __has_include(<experimental/filesystem>)
20+
#include <experimental/filesystem>
21+
namespace fs = std::experimental::filesystem;
1622
#else
17-
#include <filesystem> // C++ 17 std::create_directories
23+
#error "OSUtils requires C++ filesystem support"
1824
#endif
1925

2026
#if defined(__SYCL_RT_OS_LINUX)
@@ -277,6 +283,45 @@ int OSUtil::makeDir(const char *Dir) {
277283
return 0;
278284
}
279285

286+
// Get size of file in bytes.
287+
size_t getFileSize(const std::string &Path) {
288+
return static_cast<size_t>(fs::file_size(Path));
289+
}
290+
291+
// Function to recursively iterate over the directory and execute
292+
// 'Func' on each regular file.
293+
void fileTreeWalk(const std::string Path,
294+
std::function<void(const std::string)> Func) {
295+
296+
std::error_code EC;
297+
for (auto It = fs::recursive_directory_iterator(Path, EC);
298+
It != fs::recursive_directory_iterator(); It.increment(EC)) {
299+
300+
// Errors can happen if a file was removed/added during the iteration.
301+
if (EC)
302+
throw sycl::exception(
303+
make_error_code(errc::runtime),
304+
"Failed to do File Tree Walk. Ensure that the directory is not "
305+
"getting updated while FileTreeWalk is in progress.: " +
306+
Path + "\n" + EC.message());
307+
308+
if (fs::is_regular_file(It->path()))
309+
Func(It->path().string());
310+
}
311+
}
312+
313+
// Get size of a directory in bytes.
314+
size_t getDirectorySize(const std::string &Path) {
315+
size_t DirSizeVar = 0;
316+
317+
auto CollectFIleSize = [&DirSizeVar](const std::string Path) {
318+
DirSizeVar += getFileSize(Path);
319+
};
320+
fileTreeWalk(Path, CollectFIleSize);
321+
322+
return DirSizeVar;
323+
}
324+
280325
} // namespace detail
281326
} // namespace _V1
282327
} // namespace sycl

0 commit comments

Comments
 (0)