From 94d1678c6737f0d22ae76432d2224c950fc3aa34 Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Fri, 29 Oct 2021 01:12:25 +0000 Subject: [PATCH] Implement ConcurrentCache This patch implements the template class ConcurrentCache which will be used to implement all the thread-safe object caches in Dawn (e.g. PipelineLayout, ComputePipeline and RenderPipeline). BUG=dawn:529 TEST=dawn_unittests Change-Id: Ia6f75191110a93285bdb23fdc1d275444d1ee866 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/67760 Reviewed-by: Corentin Wallez Reviewed-by: Austin Eng Commit-Queue: Jiawei Shao --- src/common/BUILD.gn | 1 + src/common/CMakeLists.txt | 1 + src/common/ConcurrentCache.h | 54 +++++++++ src/tests/BUILD.gn | 1 + src/tests/unittests/ConcurrentCacheTests.cpp | 114 +++++++++++++++++++ 5 files changed, 171 insertions(+) create mode 100644 src/common/ConcurrentCache.h create mode 100644 src/tests/unittests/ConcurrentCacheTests.cpp diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn index b4ae07731d..92da725881 100644 --- a/src/common/BUILD.gn +++ b/src/common/BUILD.gn @@ -171,6 +171,7 @@ if (is_win || is_linux || is_chromeos || is_mac || is_fuchsia || is_android) { "Assert.h", "BitSetIterator.h", "Compiler.h", + "ConcurrentCache.h", "Constants.h", "CoreFoundationRef.h", "DynamicLib.cpp", diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index fe90b29a9a..d839d84106 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(dawn_common PRIVATE "Assert.h" "BitSetIterator.h" "Compiler.h" + "ConcurrentCache.h" "Constants.h" "CoreFoundationRef.h" "DynamicLib.cpp" diff --git a/src/common/ConcurrentCache.h b/src/common/ConcurrentCache.h new file mode 100644 index 0000000000..0e93dddf12 --- /dev/null +++ b/src/common/ConcurrentCache.h @@ -0,0 +1,54 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COMMON_CONCURRENT_CACHE_H_ +#define COMMON_CONCURRENT_CACHE_H_ + +#include "common/NonCopyable.h" + +#include +#include +#include + +template +class ConcurrentCache : public NonMovable { + public: + ConcurrentCache() = default; + + T* Find(T* object) { + std::lock_guard lock(mMutex); + auto iter = mCache.find(object); + if (iter == mCache.end()) { + return nullptr; + } + return *iter; + } + + std::pair Insert(T* object) { + std::lock_guard lock(mMutex); + auto insertion = mCache.insert(object); + return std::make_pair(*(insertion.first), insertion.second); + } + + size_t Erase(T* object) { + std::lock_guard lock(mMutex); + return mCache.erase(object); + } + + private: + std::mutex mMutex; + std::unordered_set mCache; +}; + +#endif diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index 77b4099ae6..c7f4b7a96e 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -191,6 +191,7 @@ test("dawn_unittests") { "unittests/BuddyMemoryAllocatorTests.cpp", "unittests/ChainUtilsTests.cpp", "unittests/CommandAllocatorTests.cpp", + "unittests/ConcurrentCacheTests.cpp", "unittests/EnumClassBitmasksTests.cpp", "unittests/EnumMaskIteratorTests.cpp", "unittests/ErrorTests.cpp", diff --git a/src/tests/unittests/ConcurrentCacheTests.cpp b/src/tests/unittests/ConcurrentCacheTests.cpp new file mode 100644 index 0000000000..079355ab60 --- /dev/null +++ b/src/tests/unittests/ConcurrentCacheTests.cpp @@ -0,0 +1,114 @@ +// Copyright 2021 The Dawn Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "common/ConcurrentCache.h" +#include "dawn_native/AsyncTask.h" +#include "dawn_platform/DawnPlatform.h" +#include "utils/SystemUtils.h" + +namespace { + class SimpleCachedObject { + public: + explicit SimpleCachedObject(size_t value) : mValue(value) { + } + + size_t GetValue() const { + return mValue; + } + + struct EqualityFunc { + bool operator()(const SimpleCachedObject* a, const SimpleCachedObject* b) const { + return a->mValue == b->mValue; + } + }; + + struct HashFunc { + size_t operator()(const SimpleCachedObject* obj) const { + return obj->mValue; + } + }; + + private: + size_t mValue; + }; + +} // anonymous namespace + +class ConcurrentCacheTest : public testing::Test { + public: + ConcurrentCacheTest() : mPool(mPlatform.CreateWorkerTaskPool()), mTaskManager(mPool.get()) { + } + + protected: + dawn_platform::Platform mPlatform; + std::unique_ptr mPool; + dawn_native::AsyncTaskManager mTaskManager; + ConcurrentCache mCache; +}; + +// Test inserting two objects that are equal to each other into the concurrent cache works as +// expected. +TEST_F(ConcurrentCacheTest, InsertAtSameTime) { + SimpleCachedObject cachedObject(1); + SimpleCachedObject anotherCachedObject(1); + + std::pair insertOutput = {}; + std::pair anotherInsertOutput = {}; + + ConcurrentCache* cachePtr = &mCache; + dawn_native::AsyncTask asyncTask1([&insertOutput, cachePtr, &cachedObject] { + insertOutput = cachePtr->Insert(&cachedObject); + }); + dawn_native::AsyncTask asyncTask2([&anotherInsertOutput, cachePtr, &anotherCachedObject] { + anotherInsertOutput = cachePtr->Insert(&anotherCachedObject); + }); + mTaskManager.PostTask(std::move(asyncTask1)); + mTaskManager.PostTask(std::move(asyncTask2)); + + mTaskManager.WaitAllPendingTasks(); + + ASSERT_TRUE(insertOutput.first == &cachedObject || insertOutput.first == &anotherCachedObject); + ASSERT_EQ(insertOutput.first, anotherInsertOutput.first); + ASSERT_EQ(insertOutput.second, !anotherInsertOutput.second); +} + +// Testing erasing an object after inserting into the cache works as expected. +TEST_F(ConcurrentCacheTest, EraseAfterInsertion) { + SimpleCachedObject cachedObject(1); + + std::pair insertOutput = {}; + ConcurrentCache* cachePtr = &mCache; + dawn_native::AsyncTask insertTask([&insertOutput, cachePtr, &cachedObject] { + insertOutput = cachePtr->Insert(&cachedObject); + }); + + size_t erasedObjectCount = 0; + dawn_native::AsyncTask eraseTask([&erasedObjectCount, cachePtr, &cachedObject] { + while (cachePtr->Find(&cachedObject) == nullptr) { + utils::USleep(100); + } + erasedObjectCount = cachePtr->Erase(&cachedObject); + }); + + mTaskManager.PostTask(std::move(insertTask)); + mTaskManager.PostTask(std::move(eraseTask)); + + mTaskManager.WaitAllPendingTasks(); + + ASSERT_EQ(&cachedObject, insertOutput.first); + ASSERT_TRUE(insertOutput.second); + ASSERT_EQ(1u, erasedObjectCount); +}