Skip to content

Commit 14963e5

Browse files
committed
Replace E2E test with unit tests. Fix races.
1 parent 6876b72 commit 14963e5

File tree

4 files changed

+243
-77
lines changed

4 files changed

+243
-77
lines changed

sycl/source/detail/kernel_program_cache.hpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,15 @@ class KernelProgramCache {
218218
MProgramEvictionList.splice(MProgramEvictionList.end(),
219219
MProgramEvictionList, It->second);
220220
traceProgram("Program moved to the end of eviction list.", CacheKey);
221-
} else
222-
// This should never happen.
223-
throw sycl::exception(sycl::make_error_code(sycl::errc::runtime),
224-
"Program not found in the eviction list.");
221+
}
222+
// else: This can happen if concurrently the program is removed from
223+
// eviction list by another thread.
225224
}
226225

227226
bool empty() { return MProgramEvictionList.empty(); }
228227

228+
size_t size() { return MProgramEvictionList.size(); }
229+
229230
void popFront() {
230231
if (!MProgramEvictionList.empty()) {
231232
MProgramToEvictionListMap.erase(MProgramEvictionList.front());
@@ -294,6 +295,10 @@ class KernelProgramCache {
294295
return {MKernelsPerProgramCache, MKernelsPerProgramCacheMutex};
295296
}
296297

298+
Locked<EvictionListT> acquireEvictionList() {
299+
return {MEvictionList, MProgramEvictionListMutex};
300+
}
301+
297302
std::pair<ProgramBuildResultPtr, bool>
298303
getOrInsertProgram(const ProgramCacheKeyT &CacheKey) {
299304
auto LockedCache = acquireCachedPrograms();
@@ -368,13 +373,11 @@ class KernelProgramCache {
368373
ur_program_handle_t Program = std::get<3>(CacheVal);
369374
// Save kernel in fast cache only if the corresponding program is also
370375
// in the cache.
371-
{
372-
auto LockedCache = acquireCachedPrograms();
373-
auto &ProgCache = LockedCache.get();
374-
if (ProgCache.ProgramSizeMap.find(Program) ==
375-
ProgCache.ProgramSizeMap.end())
376-
return;
377-
}
376+
auto LockedCache = acquireCachedPrograms();
377+
auto &ProgCache = LockedCache.get();
378+
if (ProgCache.ProgramSizeMap.find(Program) ==
379+
ProgCache.ProgramSizeMap.end())
380+
return;
378381

379382
// Save reference between the program and the fast cache key.
380383
MProgramToKernelFastCacheKeyMap[Program].emplace_back(CacheKey);
@@ -483,9 +486,8 @@ class KernelProgramCache {
483486
const ur_program_handle_t &Program,
484487
const bool IsBuilt) {
485488

486-
static size_t ProgramCacheEvictionThreshold = static_cast<size_t>(
487-
SYCLConfig<
488-
SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD>::getProgramCacheSize());
489+
size_t ProgramCacheEvictionThreshold =
490+
SYCLConfig<SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD>::getProgramCacheSize();
489491

490492
// No need to populate the eviction list if eviction is disabled.
491493
if (ProgramCacheEvictionThreshold == 0)
@@ -561,7 +563,7 @@ class KernelProgramCache {
561563
MProgramToKernelFastCacheKeyMap.clear();
562564

563565
// Clear the eviction lists and its mutexes.
564-
std::lock_guard<std::mutex> L4(MProgramEvictionListMutex);
566+
std::lock_guard<std::mutex> EvictionListLock(MProgramEvictionListMutex);
565567
MEvictionList.clear();
566568
}
567569

sycl/test-e2e/KernelAndProgram/program_cache_eviction.cpp

Lines changed: 0 additions & 62 deletions
This file was deleted.

sycl/unittests/kernel-and-program/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ add_sycl_unittest(KernelAndProgramTests OBJECT
77
PersistentDeviceCodeCache.cpp
88
KernelBuildOptions.cpp
99
OutOfResources.cpp
10+
InMemCacheEviction.cpp
1011
)
1112
target_compile_definitions(KernelAndProgramTests PRIVATE -D__SYCL_INTERNAL_API)
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
//==----- InMemCacheEviction.cpp --- In-memory cache eviction tests -------==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// This file contains tests covering eviction in in-memory program cache.
9+
10+
#define SYCL2020_DISABLE_DEPRECATION_WARNINGS
11+
12+
#include "../thread_safety/ThreadUtils.h"
13+
#include "detail/context_impl.hpp"
14+
#include "detail/kernel_program_cache.hpp"
15+
#include <detail/config.hpp>
16+
#include <helpers/MockDeviceImage.hpp>
17+
#include <helpers/MockKernelInfo.hpp>
18+
#include <helpers/UrMock.hpp>
19+
20+
#include <gtest/gtest.h>
21+
22+
#include <iostream>
23+
24+
using namespace sycl;
25+
26+
class Kernel1;
27+
class Kernel2;
28+
class Kernel3;
29+
30+
MOCK_INTEGRATION_HEADER(Kernel1)
31+
MOCK_INTEGRATION_HEADER(Kernel2)
32+
MOCK_INTEGRATION_HEADER(Kernel3)
33+
34+
static sycl::unittest::MockDeviceImage Img[] = {
35+
sycl::unittest::generateDefaultImage({"Kernel1"}),
36+
sycl::unittest::generateDefaultImage({"Kernel2"}),
37+
sycl::unittest::generateDefaultImage({"Kernel3"})};
38+
39+
static sycl::unittest::MockDeviceImageArray<3> ImgArray{Img};
40+
41+
// Number of times urProgramCreateWithIL is called. This is used to check
42+
// if the program is created or fetched from the cache.
43+
static int NumProgramBuild = 0;
44+
45+
constexpr int ProgramSize = 10000;
46+
47+
static ur_result_t redefinedProgramCreateWithIL(void *) {
48+
++NumProgramBuild;
49+
return UR_RESULT_SUCCESS;
50+
}
51+
52+
static ur_result_t redefinedProgramGetInfoAfter(void *pParams) {
53+
auto params = *static_cast<ur_program_get_info_params_t *>(pParams);
54+
if (*params.ppropName == UR_PROGRAM_INFO_NUM_DEVICES) {
55+
auto value = reinterpret_cast<unsigned int *>(*params.ppPropValue);
56+
*value = 1;
57+
}
58+
59+
if (*params.ppropName == UR_PROGRAM_INFO_BINARY_SIZES) {
60+
auto value = reinterpret_cast<size_t *>(*params.ppPropValue);
61+
value[0] = ProgramSize;
62+
}
63+
64+
if (*params.ppropName == UR_PROGRAM_INFO_BINARIES) {
65+
auto value = reinterpret_cast<unsigned char **>(*params.ppPropValue);
66+
value[0] = 0;
67+
}
68+
69+
return UR_RESULT_SUCCESS;
70+
}
71+
72+
// Function to set SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD.
73+
static void setCacheEvictionEnv(const char *value) {
74+
#ifdef _WIN32
75+
_putenv_s("SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD", value);
76+
#else
77+
if (value)
78+
setenv("SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD", value, 1);
79+
else
80+
(void)unsetenv("SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD");
81+
#endif
82+
83+
sycl::detail::readConfig(true);
84+
sycl::detail::SYCLConfig<
85+
sycl::detail::SYCL_IN_MEM_CACHE_EVICTION_THRESHOLD>::reset();
86+
}
87+
88+
// Function to check number of entries in the cache and eviction list.
89+
static inline void
90+
CheckNumberOfEntriesInCacheAndEvictionList(detail::context_impl &CtxImpl,
91+
size_t ExpectedNumEntries) {
92+
auto &KPCache = CtxImpl.getKernelProgramCache();
93+
EXPECT_EQ(KPCache.acquireCachedPrograms().get().size(), ExpectedNumEntries)
94+
<< "Unexpected number of entries in the cache";
95+
auto EvcList = KPCache.acquireEvictionList();
96+
EXPECT_EQ(EvcList.get().size(), ExpectedNumEntries)
97+
<< "Unexpected number of entries in the eviction list";
98+
}
99+
100+
class InMemCacheEvictionTests : public ::testing::Test {
101+
protected:
102+
void TearDown() override { setCacheEvictionEnv(""); }
103+
};
104+
105+
TEST(InMemCacheEvictionTests, TestBasicEvictionAndLRU) {
106+
NumProgramBuild = 0;
107+
sycl::unittest::UrMock<> Mock;
108+
mock::getCallbacks().set_before_callback("urProgramCreateWithIL",
109+
&redefinedProgramCreateWithIL);
110+
mock::getCallbacks().set_after_callback("urProgramGetInfo",
111+
&redefinedProgramGetInfoAfter);
112+
113+
sycl::platform Plt{sycl::platform()};
114+
sycl::context Ctx{Plt};
115+
auto CtxImpl = detail::getSyclObjImpl(Ctx);
116+
queue q(Ctx, default_selector_v);
117+
118+
// One program is of 10000 bytes, so 20005 eviction threshold can
119+
// accommodate two program.
120+
setCacheEvictionEnv("20005");
121+
122+
// Cache is empty, so one urProgramCreateWithIL call.
123+
q.single_task<class Kernel1>([] {});
124+
EXPECT_EQ(NumProgramBuild, 1);
125+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 1);
126+
127+
q.single_task<class Kernel2>([] {});
128+
EXPECT_EQ(NumProgramBuild, 2);
129+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 2);
130+
131+
// Move first program to end of eviction list.
132+
q.single_task<class Kernel1>([] {});
133+
EXPECT_EQ(NumProgramBuild, 2);
134+
135+
// Calling Kernel3, Kernel2, and Kernel1 in a cyclic manner to
136+
// verify LRU's working.
137+
138+
// Kernel2's program should have been evicted.
139+
q.single_task<class Kernel3>([] {});
140+
EXPECT_EQ(NumProgramBuild, 3);
141+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 2);
142+
143+
// Calling Kernel2 again should trigger urProgramCreateWithIL and
144+
// should evict Kernel1's program.
145+
q.single_task<class Kernel2>([] {});
146+
EXPECT_EQ(NumProgramBuild, 3);
147+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 2);
148+
149+
// Calling Kernel1 again should trigger urProgramCreateWithIL and
150+
// should evict Kernel3's program.
151+
q.single_task<class Kernel1>([] {});
152+
EXPECT_EQ(NumProgramBuild, 4);
153+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 2);
154+
}
155+
156+
// Test to verify eviction using concurrent kernel invocation.
157+
TEST(InMemCacheEvictionTests, TestConcurrentEvictionDifferentQueue) {
158+
NumProgramBuild = 0;
159+
sycl::unittest::UrMock<> Mock;
160+
mock::getCallbacks().set_before_callback("urProgramCreateWithIL",
161+
&redefinedProgramCreateWithIL);
162+
mock::getCallbacks().set_after_callback("urProgramGetInfo",
163+
&redefinedProgramGetInfoAfter);
164+
165+
sycl::platform Plt{sycl::platform()};
166+
context Ctx{Plt};
167+
auto CtxImpl = detail::getSyclObjImpl(Ctx);
168+
169+
// One program is of 10000 bytes, so 20005 eviction threshold can
170+
// accommodate two program.
171+
setCacheEvictionEnv("20005");
172+
173+
constexpr size_t ThreadCount = 100;
174+
Barrier barrier(ThreadCount);
175+
{
176+
auto ConcurrentInvokeKernels = [&](std::size_t threadId) {
177+
queue q(Ctx, default_selector_v);
178+
barrier.wait();
179+
q.single_task<class Kernel1>([] {});
180+
q.single_task<class Kernel2>([] {});
181+
q.single_task<class Kernel3>([] {});
182+
q.wait_and_throw();
183+
};
184+
185+
ThreadPool MPool(ThreadCount, ConcurrentInvokeKernels);
186+
}
187+
188+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 2);
189+
}
190+
191+
// Test to verify eviction using concurrent kernel invocation when
192+
// cache size is very less so as to trigger immediate eviction.
193+
TEST(InMemCacheEvictionTests, TestConcurrentEvictionSmallCache) {
194+
NumProgramBuild = 0;
195+
sycl::unittest::UrMock<> Mock;
196+
mock::getCallbacks().set_before_callback("urProgramCreateWithIL",
197+
&redefinedProgramCreateWithIL);
198+
mock::getCallbacks().set_after_callback("urProgramGetInfo",
199+
&redefinedProgramGetInfoAfter);
200+
201+
context Ctx{platform()};
202+
auto CtxImpl = detail::getSyclObjImpl(Ctx);
203+
204+
// One program is of 10000 bytes, so 100 eviction threshold will
205+
// trigger immediate eviction.
206+
setCacheEvictionEnv("100");
207+
208+
// Fetch the same kernel concurrently from multiple threads.
209+
// This should cause some threads to insert a program and other
210+
// threads to evict the same program.
211+
constexpr size_t ThreadCount = 300;
212+
Barrier barrier(ThreadCount);
213+
{
214+
auto ConcurrentInvokeKernels = [&](std::size_t threadId) {
215+
queue q(Ctx, default_selector_v);
216+
barrier.wait();
217+
q.single_task<class Kernel1>([] {});
218+
q.wait_and_throw();
219+
};
220+
221+
ThreadPool MPool(ThreadCount, ConcurrentInvokeKernels);
222+
}
223+
224+
CheckNumberOfEntriesInCacheAndEvictionList(*CtxImpl, 0);
225+
}

0 commit comments

Comments
 (0)