From 8dc5b36517375d83fe083497c6b92c3ac26f39ce Mon Sep 17 00:00:00 2001 From: blue sky Date: Tue, 29 Mar 2022 17:31:56 +0800 Subject: [PATCH 1/3] add lru bucket Signed-off-by: blue sky --- frameworks/common/lru_bucket.h | 224 +++++++++++++++ frameworks/common/test/lru_bucket_test.cpp | 318 +++++++++++++++++++++ 2 files changed, 542 insertions(+) create mode 100644 frameworks/common/lru_bucket.h create mode 100644 frameworks/common/test/lru_bucket_test.cpp diff --git a/frameworks/common/lru_bucket.h b/frameworks/common/lru_bucket.h new file mode 100644 index 000000000..7f1173ffc --- /dev/null +++ b/frameworks/common/lru_bucket.h @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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 OHOS_DISTRIBUTED_DATA_FRAMEWORKS_COMMON_LRU_BUCKET_H +#define OHOS_DISTRIBUTED_DATA_FRAMEWORKS_COMMON_LRU_BUCKET_H + +#include +#include +#include + +namespace OHOS { +template +class LRUBucket { +public: + LRUBucket(size_t capacity) + : size_(0), capacity_(capacity) {}; + + LRUBucket(LRUBucket &&bucket) noexcept = delete; + LRUBucket(const LRUBucket &bucket) = delete; + LRUBucket &operator=(LRUBucket &&bucket) noexcept = delete; + LRUBucket &operator=(const LRUBucket &bucket) = delete; + + ~LRUBucket() + { + std::lock_guard lock(mutex_); + while (size_ > 0) { + PopBack(); + } + } + + size_t Size() const + { + return size_; + } + + size_t Capacity() const + { + return capacity_; + } + + bool ResetCapacity(size_t capacity) + { + std::lock_guard lock(mutex_); + capacity_ = capacity; + while (capacity_ < size_) { + PopBack(); + } + return capacity_ == capacity; + } + + /** + * The time complexity is O(log(index size)) + **/ + bool Get(const _Key &key, _Tp &value) + { + std::lock_guard lock(mutex_); + auto it = indexes_.find(key); + if (it != indexes_.end()) { + // move node from the list; + Remove(it->second); + // insert node to the head + Insert(&head_, it->second); + value = it->second->value_; + return true; + } + return false; + } + + /** + * The time complexity is O(log(index size)) + **/ + bool Set(const _Key &key, const _Tp &value) + { + std::lock_guard lock(mutex_); + if (capacity_ == 0) { + return false; + } + + auto it = indexes_.find(key); + if (it != indexes_.end()) { + Update(it->second, value); + Remove(it->second); + Insert(&head_, it->second); + return true; + } + + while (capacity_ <= size_) { + PopBack(); + } + + auto *node = new(std::nothrow) Node(value); + if (node == nullptr) { + return false; + } + + Insert(&head_, node); + auto pair = indexes_.emplace(key, node); + node->iterator_ = pair.first; + return true; + } + + /** + * Just update the values, not change the lru + **/ + bool Update(const _Key &key, const _Tp &value) + { + std::lock_guard lock(mutex_); + auto it = indexes_.find(key); + if (it != indexes_.end()) { + Update(it->second, value); + return true; + } + return false; + } + + /** + * The time complexity is O(min(indexes, values)) + * Just update the values, not change the lru chain + */ + bool Update(const std::map<_Key, _Tp> &values) + { + std::lock_guard lock(mutex_); + auto idx = indexes_.begin(); + auto val = values.begin(); + bool updated = false; + auto comp = indexes_.key_comp(); + while (idx != indexes_.end() && val != values.end()) { + if (comp(idx->first, val->first)) { + ++idx; + continue; + } + if (comp(val->first, idx->first)) { + ++val; + continue; + } + updated = true; + Update(idx->second, val->second); + ++idx; + ++val; + } + return updated; + } + + /** + * The time complexity is O(log(index size)) + * */ + bool Delete(const _Key &key) + { + std::lock_guard lock(mutex_); + auto it = indexes_.find(key); + if (it != indexes_.end()) { + Remove(it->second); + Delete(it->second); + return true; + } + return false; + } + +private: + struct Node final { + using iterator = typename std::map<_Key, Node *>::iterator; + Node(const _Tp &value) : value_(value) {} + Node() : value_() {} + ~Node() = default; + _Tp value_; + iterator iterator_; + Node *prev_ = this; + Node *next_ = this; + }; + + void PopBack() + { + auto *node = head_.prev_; + Remove(node); + Delete(node); + } + + void Update(Node *node, const _Tp &value) + { + node->value_ = value; + } + + void Remove(Node *node) + { + node->prev_->next_ = node->next_; + node->next_->prev_ = node->prev_; + size_--; + } + + void Insert(Node *prev, Node *node) + { + prev->next_->prev_ = node; + node->next_ = prev->next_; + prev->next_ = node; + node->prev_ = prev; + size_++; + } + + void Delete(Node *node) + { + indexes_.erase(node->iterator_); + delete node; + } + + mutable std::recursive_mutex mutex_; + std::map<_Key, Node *> indexes_; + Node head_; + size_t size_; + size_t capacity_; +}; +} // namespace OHOS +#endif // OHOS_DISTRIBUTED_DATA_FRAMEWORKS_COMMON_LRU_BUCKET_H diff --git a/frameworks/common/test/lru_bucket_test.cpp b/frameworks/common/test/lru_bucket_test.cpp new file mode 100644 index 000000000..05be54b2f --- /dev/null +++ b/frameworks/common/test/lru_bucket_test.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * 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. + */ + +#define LOG_TAG "LRUBucketTest" + +#include "lru_bucket.h" +#include "gtest/gtest.h" + +using namespace testing::ext; +template using LRUBucket = OHOS::LRUBucket<_Key, _Tp>; + +class LRUBucketTest : public testing::Test { +public: + struct TestValue { + std::string id; + std::string name; + std::string testCase; + }; + + static void SetUpTestCase(void) {} + + static void TearDownTestCase(void) {} + +protected: + void SetUp() + { + bucket_.ResetCapacity(0); + bucket_.ResetCapacity(10); + for (int i = 0; i < 10; ++i) { + std::string key = std::string("test_") + std::to_string(i); + TestValue value = {key, key, "case"}; + bucket_.Set(key, value); + } + } + + void TearDown() {} + + LRUBucket bucket_{10}; +}; + +/** +* @tc.name: insert +* @tc.desc: Set the value to the lru bucket, whose capacity is more than one. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, insert, TestSize.Level0) +{ + bucket_.Set("test_10", {"test_10", "test_10", "case"}); + TestValue value; + ASSERT_TRUE(!bucket_.Get("test_0", value)); + ASSERT_TRUE(bucket_.Get("test_6", value)); + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_6", value)); +} + +/** +* @tc.name: cap_one_insert +* @tc.desc: Set the value to the lru bucket, whose capacity is one. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, cap_one_insert, TestSize.Level0) +{ + bucket_.ResetCapacity(1); + for (int i = 0; i < 11; ++i) { + std::string key = std::string("test_") + std::to_string(i); + TestValue value = {key, key, "find"}; + bucket_.Set(key, value); + } + TestValue value; + ASSERT_TRUE(!bucket_.Get("test_0", value)); + ASSERT_TRUE(bucket_.Get("test_10", value)); +} + +/** +* @tc.name: cap_zero_insert +* @tc.desc: Set the value to the lru bucket, whose capacity is zero. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, cap_zero_insert, TestSize.Level0) +{ + bucket_.ResetCapacity(0); + for (int i = 0; i < 11; ++i) { + std::string key = std::string("test_") + std::to_string(i); + TestValue value = {key, key, "find"}; + bucket_.Set(key, value); + } + TestValue value; + ASSERT_TRUE(!bucket_.Get("test_10", value)); +} + +/** +* @tc.name: find_head +* @tc.desc: find the head element from the lru bucket. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, find_head, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_9", value)); +} + +/** +* @tc.name: find_tail +* @tc.desc: find the tail element, then the element will move to head. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, find_tail, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.Get("test_0", value)); + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_0", value)); +} + +/** +* @tc.name: find_mid +* @tc.desc: find the mid element, then the element will move to head. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, find_mid, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.Get("test_5", value)); + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_5", value)); +} + +/** +* @tc.name: find_and_insert +* @tc.desc: find the tail element, then the element will move to head. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, find_and_insert, TestSize.Level0) +{ + TestValue value; + if (!bucket_.Get("MyTest", value)) { + bucket_.Set("MyTest", {"MyTest", "MyTest", "case"}); + } + ASSERT_TRUE(bucket_.Get("MyTest", value)); + + if (!bucket_.Get("test_0", value)) { + bucket_.Set("test_0", {"test_0", "test_0", "case"}); + } + ASSERT_TRUE(bucket_.Get("test_0", value)); + ASSERT_TRUE(bucket_.Get("test_5", value)); + ASSERT_TRUE(bucket_.Get("test_4", value)); + ASSERT_TRUE(!bucket_.Get("test_1", value)); + ASSERT_TRUE(bucket_.Get("test_2", value)); +} + +/** +* @tc.name: del_head +* @tc.desc: delete the head element, then the next element will move to head. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, del_head, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.Delete("test_9")); + ASSERT_TRUE(!bucket_.Get("test_9", value)); + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_8", value)); +} + +/** +* @tc.name: del_head +* @tc.desc: delete the tail element, then the lru chain keep valid. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, del_tail, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.Delete("test_0")); + ASSERT_TRUE(!bucket_.Get("test_0", value)); + ASSERT_TRUE(bucket_.Get("test_4", value)); + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_4", value)); +} + +/** +* @tc.name: del_mid +* @tc.desc: delete the mid element, then the lru chain keep valid. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, del_mid, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.Delete("test_5")); + ASSERT_TRUE(!bucket_.Get("test_5", value)); + ASSERT_TRUE(bucket_.Get("test_4", value)); + ASSERT_TRUE(bucket_.Get("test_6", value)); + ASSERT_TRUE(bucket_.ResetCapacity(2)); + ASSERT_TRUE(bucket_.Capacity() == 2); + ASSERT_TRUE(bucket_.Size() <= 2); + ASSERT_TRUE(bucket_.Get("test_4", value)); + ASSERT_TRUE(bucket_.Get("test_6", value)); +} + +/** +* @tc.name: del_mid +* @tc.desc: the lru bucket has only one element, then delete it. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, cap_one_del, TestSize.Level0) +{ + TestValue value; + bucket_.ResetCapacity(1); + ASSERT_TRUE(bucket_.Delete("test_9")); + ASSERT_TRUE(!bucket_.Get("test_9", value)); +} + +/** +* @tc.name: del_mid +* @tc.desc: the lru bucket has no element. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, cap_zero_del, TestSize.Level0) +{ + TestValue value; + bucket_.ResetCapacity(0); + ASSERT_TRUE(!bucket_.Delete("test_9")); + ASSERT_TRUE(!bucket_.Get("test_9", value)); +} + +/** +* @tc.name: update_one +* @tc.desc: update the value and the lru chain won't change. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, update_one, TestSize.Level0) +{ + TestValue value; + ASSERT_TRUE(bucket_.Update("test_4", {"test_4", "test_4", "update"})); + ASSERT_TRUE(bucket_.Get("test_4", value)); + ASSERT_TRUE(value.testCase == "update"); + ASSERT_TRUE(bucket_.Update("test_9", {"test_9", "test_9", "update"})); + ASSERT_TRUE(bucket_.ResetCapacity(1)); + ASSERT_TRUE(bucket_.Capacity() == 1); + ASSERT_TRUE(bucket_.Size() <= 1); + ASSERT_TRUE(bucket_.Get("test_4", value)); + ASSERT_TRUE(!bucket_.Get("test_9", value)); +} + +/** +* @tc.name: update_several +* @tc.desc: update several values and the lru chain won't change. +* @tc.type: FUNC +* @tc.require: +* @tc.author: Sven Wang +*/ +HWTEST_F(LRUBucketTest, update_several, TestSize.Level0) +{ + TestValue value; + std::map values = {{"test_2", {"test_2", "test_2", "update"}}, + {"test_3", {"test_3", "test_3", "update"}}, + {"test_6", {"test_6", "test_6", "update"}},}; + ASSERT_TRUE(bucket_.Update(values)); + ASSERT_TRUE(bucket_.ResetCapacity(3)); + ASSERT_TRUE(bucket_.Capacity() == 3); + ASSERT_TRUE(bucket_.Size() <= 3); + ASSERT_TRUE(!bucket_.Get("test_2", value)); + ASSERT_TRUE(!bucket_.Get("test_3", value)); + ASSERT_TRUE(!bucket_.Get("test_6", value)); + ASSERT_TRUE(bucket_.Get("test_9", value)); + ASSERT_TRUE(bucket_.Get("test_8", value)); + ASSERT_TRUE(bucket_.Get("test_7", value)); +} \ No newline at end of file -- Gitee From b16d49136f3f4aa88b2148b1f48f09f47558524f Mon Sep 17 00:00:00 2001 From: blue sky Date: Tue, 29 Mar 2022 19:14:23 +0800 Subject: [PATCH 2/3] fixed code style Signed-off-by: blue sky --- frameworks/common/lru_bucket.h | 2 +- frameworks/common/test/lru_bucket_test.cpp | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frameworks/common/lru_bucket.h b/frameworks/common/lru_bucket.h index 7f1173ffc..1fac6f159 100644 --- a/frameworks/common/lru_bucket.h +++ b/frameworks/common/lru_bucket.h @@ -25,7 +25,7 @@ template class LRUBucket { public: LRUBucket(size_t capacity) - : size_(0), capacity_(capacity) {}; + : size_(0), capacity_(capacity) {} LRUBucket(LRUBucket &&bucket) noexcept = delete; LRUBucket(const LRUBucket &bucket) = delete; diff --git a/frameworks/common/test/lru_bucket_test.cpp b/frameworks/common/test/lru_bucket_test.cpp index 05be54b2f..b54fadbe5 100644 --- a/frameworks/common/test/lru_bucket_test.cpp +++ b/frameworks/common/test/lru_bucket_test.cpp @@ -28,6 +28,7 @@ public: std::string name; std::string testCase; }; + static constexpr size_t TEST_CAPACITY = 10; static void SetUpTestCase(void) {} @@ -37,8 +38,8 @@ protected: void SetUp() { bucket_.ResetCapacity(0); - bucket_.ResetCapacity(10); - for (int i = 0; i < 10; ++i) { + bucket_.ResetCapacity(TEST_CAPACITY); + for (int i = 0; i < TEST_CAPACITY; ++i) { std::string key = std::string("test_") + std::to_string(i); TestValue value = {key, key, "case"}; bucket_.Set(key, value); @@ -79,7 +80,7 @@ HWTEST_F(LRUBucketTest, insert, TestSize.Level0) HWTEST_F(LRUBucketTest, cap_one_insert, TestSize.Level0) { bucket_.ResetCapacity(1); - for (int i = 0; i < 11; ++i) { + for (int i = 0; i <= TEST_CAPACITY; ++i) { std::string key = std::string("test_") + std::to_string(i); TestValue value = {key, key, "find"}; bucket_.Set(key, value); @@ -99,7 +100,7 @@ HWTEST_F(LRUBucketTest, cap_one_insert, TestSize.Level0) HWTEST_F(LRUBucketTest, cap_zero_insert, TestSize.Level0) { bucket_.ResetCapacity(0); - for (int i = 0; i < 11; ++i) { + for (int i = 0; i <= TEST_CAPACITY; ++i) { std::string key = std::string("test_") + std::to_string(i); TestValue value = {key, key, "find"}; bucket_.Set(key, value); @@ -304,7 +305,7 @@ HWTEST_F(LRUBucketTest, update_several, TestSize.Level0) TestValue value; std::map values = {{"test_2", {"test_2", "test_2", "update"}}, {"test_3", {"test_3", "test_3", "update"}}, - {"test_6", {"test_6", "test_6", "update"}},}; + {"test_6", {"test_6", "test_6", "update"}}}; ASSERT_TRUE(bucket_.Update(values)); ASSERT_TRUE(bucket_.ResetCapacity(3)); ASSERT_TRUE(bucket_.Capacity() == 3); -- Gitee From 7769cb597b373d8124f9cf4f716d9e458eeda6aa Mon Sep 17 00:00:00 2001 From: blue sky Date: Tue, 29 Mar 2022 20:07:13 +0800 Subject: [PATCH 3/3] update Signed-off-by: blue sky --- frameworks/common/test/lru_bucket_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frameworks/common/test/lru_bucket_test.cpp b/frameworks/common/test/lru_bucket_test.cpp index b54fadbe5..3c294da7b 100644 --- a/frameworks/common/test/lru_bucket_test.cpp +++ b/frameworks/common/test/lru_bucket_test.cpp @@ -48,7 +48,7 @@ protected: void TearDown() {} - LRUBucket bucket_{10}; + LRUBucket bucket_{TEST_CAPACITY}; }; /** -- Gitee