refactor: windows cl_cache eviction mechanism

Refactored eviction mechanism works as follows:
- eviction is needed only if
total size of cache binaries + size of the new binary exceed cache limit
- single evition call removes files with a summed size of 1/3 of the cache limit
- if new binary can not fit in the cache size limit
even after eviction, it will not be saved
- cache limit applies only to
files in cache directory with .cl_cache/.l0_cache extension.
Only these files are counted and only these files are removed

Related-To: NEO-8092
Signed-off-by: Fabian Zwolinski <fabian.zwolinski@intel.com>
This commit is contained in:
Fabian Zwolinski
2023-10-03 09:39:59 +00:00
committed by Compute-Runtime-Automation
parent 8fa0b90f35
commit 1f1af5bb36
5 changed files with 175 additions and 45 deletions

View File

@@ -48,7 +48,7 @@ class CompilerCache {
MOCKABLE_VIRTUAL std::unique_ptr<char[]> loadCachedBinary(const std::string &kernelFileHash, size_t &cachedBinarySize);
protected:
MOCKABLE_VIRTUAL bool evictCache();
MOCKABLE_VIRTUAL bool evictCache(uint64_t &bytesEvicted);
MOCKABLE_VIRTUAL bool renameTempFileBinaryToProperName(const std::string &oldName, const std::string &kernelFileHash);
MOCKABLE_VIRTUAL bool createUniqueTempFileAndWriteData(char *tmpFilePathTemplate, const char *pBinary, size_t binarySize);
MOCKABLE_VIRTUAL void lockConfigFileAndReadSize(const std::string &configFilePath, UnifiedHandle &fd, size_t &directorySize);

View File

@@ -47,7 +47,7 @@ bool compareByLastAccessTime(const ElementsStruct &a, ElementsStruct &b) {
return a.statEl.st_atime < b.statEl.st_atime;
}
bool CompilerCache::evictCache() {
bool CompilerCache::evictCache(uint64_t &bytesEvicted) {
struct dirent **files = 0;
int filesCount = NEO::SysCalls::scandir(config.cacheDir.c_str(), &files, filterFunction, NULL);
@@ -231,7 +231,8 @@ bool CompilerCache::cacheBinary(const std::string &kernelFileHash, const char *p
size_t maxSize = config.cacheSize;
if (maxSize < directorySize + binarySize) {
if (!evictCache()) {
uint64_t bytesEvicted{0u};
if (!evictCache(bytesEvicted)) {
unlockFileAndClose(std::get<int>(fd));
return false;
}

View File

@@ -30,7 +30,7 @@ std::vector<ElementsStruct> getFiles(const std::string &path) {
std::vector<ElementsStruct> files;
std::string newPath;
WIN32_FIND_DATAA ffd;
WIN32_FIND_DATAA ffd{0};
HANDLE hFind = INVALID_HANDLE_VALUE;
if (path.c_str()[path.size() - 1] == '\\') {
@@ -49,8 +49,7 @@ std::vector<ElementsStruct> getFiles(const std::string &path) {
do {
auto fileName = joinPath(path, ffd.cFileName);
if (fileName.find(".cl_cache") != fileName.npos ||
fileName.find(".l0_cache") != fileName.npos ||
fileName.find(".ocloc_cache") != fileName.npos) {
fileName.find(".l0_cache") != fileName.npos) {
uint64_t fileSize = (ffd.nFileSizeHigh * (MAXDWORD + 1)) + ffd.nFileSizeLow;
files.push_back({fileName, ffd.ftLastAccessTime, fileSize});
}
@@ -74,16 +73,19 @@ void unlockFileAndClose(UnifiedHandle handle) {
NEO::SysCalls::closeHandle(std::get<void *>(handle));
}
bool CompilerCache::evictCache() {
auto files = getFiles(config.cacheDir);
auto evictionLimit = config.cacheSize / 3;
uint64_t evictionSizeCount = 0;
bool CompilerCache::evictCache(uint64_t &bytesEvicted) {
bytesEvicted = 0;
const auto cacheFiles = getFiles(config.cacheDir);
const auto evictionLimit = config.cacheSize / 3;
for (const auto &file : files) {
NEO::SysCalls::deleteFileA(file.path.c_str());
evictionSizeCount += file.fileSize;
for (const auto &file : cacheFiles) {
auto res = NEO::SysCalls::deleteFileA(file.path.c_str());
if (!res) {
continue;
}
if (evictionSizeCount > evictionLimit) {
bytesEvicted += file.fileSize;
if (bytesEvicted > evictionLimit) {
return true;
}
}
@@ -143,9 +145,9 @@ void CompilerCache::lockConfigFileAndReadSize(const std::string &configFilePath,
}
if (countDirectorySize) {
auto files = getFiles(config.cacheDir);
const auto cacheFiles = getFiles(config.cacheDir);
for (const auto &file : files) {
for (const auto &file : cacheFiles) {
directorySize += static_cast<size_t>(file.fileSize);
}
} else {
@@ -233,8 +235,24 @@ class HandleGuard {
void *handle = nullptr;
};
void writeDirSizeToConfigFile(void *hConfigFile, size_t directorySize) {
DWORD sizeWritten = 0;
OVERLAPPED overlapped = {0};
auto result = NEO::SysCalls::writeFile(hConfigFile,
&directorySize,
(DWORD)sizeof(directorySize),
&sizeWritten,
&overlapped);
if (!result) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Writing to config file failed! error code: %lu\n", NEO::SysCalls::getProcessId(), SysCalls::getLastError());
} else if (sizeWritten != (DWORD)sizeof(directorySize)) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Writing to config file failed! Incorrect number of bytes written: %lu vs %lu\n", NEO::SysCalls::getProcessId(), sizeWritten, (DWORD)sizeof(directorySize));
}
}
bool CompilerCache::cacheBinary(const std::string &kernelFileHash, const char *pBinary, size_t binarySize) {
if (pBinary == nullptr || binarySize == 0) {
if (pBinary == nullptr || binarySize == 0 || binarySize > config.cacheSize) {
return false;
}
@@ -263,9 +281,18 @@ bool CompilerCache::cacheBinary(const std::string &kernelFileHash, const char *p
return true;
}
size_t maxSize = config.cacheSize;
const size_t maxSize = config.cacheSize;
if (maxSize < (directorySize + binarySize)) {
if (!evictCache()) {
uint64_t bytesEvicted{0u};
const auto evictSuccess = evictCache(bytesEvicted);
const auto availableSpace = maxSize - directorySize + bytesEvicted;
directorySize = std::max(static_cast<size_t>(0), directorySize - static_cast<size_t>(bytesEvicted));
if (!evictSuccess || binarySize > availableSpace) {
if (bytesEvicted > 0) {
writeDirSizeToConfigFile(std::get<void *>(hConfigFile), directorySize);
}
return false;
}
}
@@ -283,20 +310,7 @@ bool CompilerCache::cacheBinary(const std::string &kernelFileHash, const char *p
}
directorySize += binarySize;
DWORD sizeWritten = 0;
OVERLAPPED overlapped = {0};
auto result = NEO::SysCalls::writeFile(std::get<void *>(hConfigFile),
&directorySize,
(DWORD)sizeof(directorySize),
&sizeWritten,
&overlapped);
if (!result) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Writing to config file failed! error code: %lu\n", NEO::SysCalls::getProcessId(), SysCalls::getLastError());
} else if (sizeWritten != (DWORD)sizeof(directorySize)) {
NEO::printDebugString(NEO::DebugManager.flags.PrintDebugMessages.get(), stderr, "PID %d [Cache failure]: Writing to config file failed! Incorrect number of bytes written: %lu vs %lu\n", NEO::SysCalls::getProcessId(), sizeWritten, (DWORD)sizeof(directorySize));
}
writeDirSizeToConfigFile(std::get<void *>(hConfigFile), directorySize);
return true;
}

View File

@@ -120,7 +120,8 @@ TEST(CompilerCacheTests, GivenCompilerCacheWithOneMegabyteWhenEvictCacheIsCalled
CompilerCacheMockLinux cache({true, ".cl_cache", "/home/cl_cache/", MemoryConstants::megaByte - 2u});
EXPECT_TRUE(cache.evictCache());
uint64_t bytesEvicted{0u};
EXPECT_TRUE(cache.evictCache(bytesEvicted));
EXPECT_EQ(unlinkLocalFiles.size(), 2u);
@@ -132,7 +133,8 @@ TEST(CompilerCacheTests, GivenCompilerCacheWithWhenScandirFailThenEvictCacheFail
CompilerCacheMockLinux cache({true, ".cl_cache", "/home/cl_cache/", MemoryConstants::megaByte});
VariableBackup<decltype(NEO::SysCalls::sysCallsScandir)> scandirBackup(&NEO::SysCalls::sysCallsScandir, [](const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)) -> int { return -1; });
EXPECT_FALSE(cache.evictCache());
uint64_t bytesEvicted{0u};
EXPECT_FALSE(cache.evictCache(bytesEvicted));
}
namespace CreateUniqueTempFilePass {
@@ -482,7 +484,7 @@ class CompilerCacheLinuxReturnFalseOnCacheBinaryIfEvictFailed : public CompilerC
return;
}
bool evictCache() override {
bool evictCache(uint64_t &bytesEvicted) override {
return false;
}
};
@@ -509,7 +511,7 @@ class CompilerCacheLinuxReturnFalseOnCacheBinaryIfCreateUniqueFileFailed : publi
return;
}
bool evictCache() override {
bool evictCache(uint64_t &bytesEvicted) override {
return true;
}
@@ -540,7 +542,7 @@ class CompilerCacheLinuxReturnFalseOnCacheBinaryIfRenameFileFailed : public Comp
return;
}
bool evictCache() override {
bool evictCache(uint64_t &bytesEvicted) override {
return true;
}
@@ -575,7 +577,7 @@ class CompilerCacheLinuxReturnTrueOnCacheBinary : public CompilerCache {
return;
}
bool evictCache() override {
bool evictCache(uint64_t &bytesEvicted) override {
return true;
}

View File

@@ -57,17 +57,19 @@ class CompilerCacheMockWindows : public CompilerCache {
bool renameTempFileBinaryToProperNameResult = true;
std::string renameTempFileBinaryToProperNameCacheFilePath = "";
bool evictCache() override {
bool evictCache(uint64_t &bytesEvicted) override {
evictCacheCalled++;
if (callBaseEvictCache) {
return CompilerCache::evictCache();
return CompilerCache::evictCache(bytesEvicted);
}
bytesEvicted = evictCacheBytesEvicted;
return evictCacheResult;
}
bool callBaseEvictCache = true;
size_t evictCacheCalled = 0u;
bool evictCacheResult = true;
uint64_t evictCacheBytesEvicted = 0u;
void lockConfigFileAndReadSize(const std::string &configFilePath, UnifiedHandle &handle, size_t &directorySize) override {
lockConfigFileAndReadSizeCalled++;
@@ -75,11 +77,13 @@ class CompilerCacheMockWindows : public CompilerCache {
return CompilerCache::lockConfigFileAndReadSize(configFilePath, handle, directorySize);
}
std::get<void *>(handle) = lockConfigFileAndReadSizeHandle;
directorySize = lockConfigFileAndReadSizeDirSize;
}
bool callBaseLockConfigFileAndReadSize = true;
size_t lockConfigFileAndReadSizeCalled = 0u;
void *lockConfigFileAndReadSizeHandle = INVALID_HANDLE_VALUE;
size_t lockConfigFileAndReadSizeDirSize = 0u;
};
TEST(CompilerCacheHelper, GivenHomeEnvWhenOtherProcessCreatesNeoCompilerCacheFolderThenProperDirectoryIsReturned) {
@@ -236,7 +240,9 @@ TEST_F(CompilerCacheWindowsTest, GivenCompilerCacheWithOneMegabyteWhenEvictCache
const size_t cacheSize = MemoryConstants::megaByte - 2u;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
auto &deletedFiles = SysCalls::deleteFiles;
auto result = cache.evictCache();
uint64_t bytesEvicted{0u};
auto result = cache.evictCache(bytesEvicted);
EXPECT_TRUE(result);
EXPECT_EQ(2u, SysCalls::deleteFileACalled);
@@ -252,8 +258,10 @@ TEST_F(CompilerCacheWindowsTest, givenEvictCacheWhenFileSearchFailedThenDebugMes
const size_t cacheSize = MemoryConstants::megaByte - 2u;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
uint64_t bytesEvicted{0u};
::testing::internal::CaptureStderr();
cache.evictCache();
cache.evictCache(bytesEvicted);
auto capturedStderr = ::testing::internal::GetCapturedStderr();
std::string expectedStderrSubstr("[Cache failure]: File search failed! error code:");
@@ -618,6 +626,14 @@ TEST_F(CompilerCacheWindowsTest, givenEmptyBinaryAndOrBinarySizeWhenCacheBinaryT
EXPECT_FALSE(result);
}
TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenBinarySizeIsOverCacheLimitThenEarlyReturnFalse) {
const size_t cacheSize = MemoryConstants::megaByte;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
auto result = cache.cacheBinary("7e3291364d8df42", "123456", cacheSize * 2);
EXPECT_FALSE(result);
}
TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenAllStepsSuccessThenReturnTrue) {
SysCalls::getFileAttributesResult = INVALID_FILE_ATTRIBUTES;
@@ -777,14 +793,51 @@ TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenLockConfigFileAndReadSizeFa
EXPECT_FALSE(result);
}
TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenEvictCacheFailsThenReturnFalse) {
TEST_F(CompilerCacheWindowsTest, givenCacheDirectoryFilledToTheLimitWhenNewBinaryFitsAfterEvictionThenWriteCacheAndUpdateConfigAndReturnTrue) {
SysCalls::getFileAttributesResult = INVALID_FILE_ATTRIBUTES;
const size_t cacheSize = 3u;
const size_t cacheSize = 10;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
cache.callBaseLockConfigFileAndReadSize = false;
cache.lockConfigFileAndReadSizeHandle = reinterpret_cast<HANDLE>(0x1234);
cache.lockConfigFileAndReadSizeDirSize = 6;
cache.callBaseEvictCache = false;
cache.evictCacheResult = true;
cache.evictCacheBytesEvicted = cacheSize / 3;
cache.callBaseCreateUniqueTempFileAndWriteData = false;
cache.createUniqueTempFileAndWriteDataResult = true;
cache.callBaseRenameTempFileBinaryToProperName = false;
cache.renameTempFileBinaryToProperNameResult = true;
const std::string kernelFileHash = "7e3291364d8df42";
const char *binary = "123456";
const size_t binarySize = strlen(binary);
SysCalls::writeFileNumberOfBytesWritten = static_cast<DWORD>(sizeof(size_t));
auto result = cache.cacheBinary(kernelFileHash, binary, binarySize);
EXPECT_TRUE(result);
EXPECT_EQ(1u, cache.lockConfigFileAndReadSizeCalled);
EXPECT_EQ(1u, SysCalls::getFileAttributesCalled);
EXPECT_EQ(1u, cache.createUniqueTempFileAndWriteDataCalled);
EXPECT_EQ(1u, cache.renameTempFileBinaryToProperNameCalled);
EXPECT_EQ(1u, SysCalls::writeFileCalled);
EXPECT_EQ(0, strcmp(cache.createUniqueTempFileAndWriteDataTmpFilePath.c_str(), "somePath\\cl_cache\\cl_cache.XXXXXX"));
EXPECT_EQ(0, strcmp(cache.renameTempFileBinaryToProperNameCacheFilePath.c_str(), "somePath\\cl_cache\\7e3291364d8df42.cl_cache"));
}
TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenEvictCacheFailsThenReturnFalse) {
SysCalls::getFileAttributesResult = INVALID_FILE_ATTRIBUTES;
const size_t cacheSize = 10u;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
cache.callBaseLockConfigFileAndReadSize = false;
cache.lockConfigFileAndReadSizeHandle = reinterpret_cast<HANDLE>(0x1234);
cache.lockConfigFileAndReadSizeDirSize = 5;
cache.callBaseEvictCache = false;
cache.evictCacheResult = false;
@@ -804,6 +857,66 @@ TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenEvictCacheFailsThenReturnFa
EXPECT_EQ(0u, SysCalls::writeFileCalled);
}
TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenBinaryDoesntFitAfterEvictionThenWriteToConfigAndReturnFalse) {
SysCalls::getFileAttributesResult = INVALID_FILE_ATTRIBUTES;
const size_t cacheSize = 10u;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
cache.callBaseLockConfigFileAndReadSize = false;
cache.lockConfigFileAndReadSizeHandle = reinterpret_cast<HANDLE>(0x1234);
cache.lockConfigFileAndReadSizeDirSize = 9;
cache.callBaseEvictCache = false;
cache.evictCacheResult = true;
cache.evictCacheBytesEvicted = cacheSize / 3;
const std::string kernelFileHash = "7e3291364d8df42";
const char *binary = "123456";
const size_t binarySize = strlen(binary);
auto result = cache.cacheBinary(kernelFileHash, binary, binarySize);
EXPECT_FALSE(result);
EXPECT_EQ(1u, SysCalls::unlockFileExCalled);
EXPECT_EQ(1u, SysCalls::closeHandleCalled);
EXPECT_EQ(1u, cache.lockConfigFileAndReadSizeCalled);
EXPECT_EQ(1u, SysCalls::getFileAttributesCalled);
EXPECT_EQ(1u, SysCalls::writeFileCalled);
EXPECT_EQ(0u, cache.createUniqueTempFileAndWriteDataCalled);
EXPECT_EQ(0u, cache.renameTempFileBinaryToProperNameCalled);
}
TEST_F(CompilerCacheWindowsTest, givenCacheDirectoryFilledToTheLimitWhenNoBytesHaveBeenEvictedAndNewBinaryDoesntFitAfterEvictionThenDontWriteToConfigAndReturnFalse) {
SysCalls::getFileAttributesResult = INVALID_FILE_ATTRIBUTES;
const size_t cacheSize = 10u;
CompilerCacheMockWindows cache({true, ".cl_cache", "somePath\\cl_cache", cacheSize});
cache.callBaseLockConfigFileAndReadSize = false;
cache.lockConfigFileAndReadSizeHandle = reinterpret_cast<HANDLE>(0x1234);
cache.lockConfigFileAndReadSizeDirSize = 9;
cache.callBaseEvictCache = false;
cache.evictCacheResult = true;
cache.evictCacheBytesEvicted = 0;
const std::string kernelFileHash = "7e3291364d8df42";
const char *binary = "123456";
const size_t binarySize = strlen(binary);
auto result = cache.cacheBinary(kernelFileHash, binary, binarySize);
EXPECT_FALSE(result);
EXPECT_EQ(1u, SysCalls::unlockFileExCalled);
EXPECT_EQ(1u, SysCalls::closeHandleCalled);
EXPECT_EQ(1u, cache.lockConfigFileAndReadSizeCalled);
EXPECT_EQ(1u, SysCalls::getFileAttributesCalled);
EXPECT_EQ(0u, SysCalls::writeFileCalled);
EXPECT_EQ(0u, cache.createUniqueTempFileAndWriteDataCalled);
EXPECT_EQ(0u, cache.renameTempFileBinaryToProperNameCalled);
}
TEST_F(CompilerCacheWindowsTest, givenCacheBinaryWhenCreateUniqueTempFileAndWriteDataFailsThenReturnFalse) {
SysCalls::getFileAttributesResult = INVALID_FILE_ATTRIBUTES;