From f1d5c50bb04ab801fcf943f04acd4a69417f8366 Mon Sep 17 00:00:00 2001 From: molotkovmikhail Date: Fri, 29 Sep 2023 09:41:00 +0000 Subject: [PATCH] Implemented 2 new selectors and added test for them. They are used in TaskScheduler with new enum. Signed-off-by: molotkovmikhail --- libpandabase/BUILD.gn | 10 ++ libpandabase/CMakeLists.txt | 6 + libpandabase/taskmanager/task_queue.h | 25 ++++ libpandabase/taskmanager/task_scheduler.cpp | 77 ++++++---- libpandabase/taskmanager/task_scheduler.h | 36 ++++- .../task_selector/frequent_task_selector.cpp | 44 ++++++ .../task_selector/frequent_task_selector.h | 51 +++++++ .../task_selector/random_task_selector.cpp | 59 ++++++++ .../task_selector/random_task_selector.h | 54 +++++++ .../task_selector/rare_task_selector.cpp | 43 ++++++ .../task_selector/rare_task_selector.h | 52 +++++++ .../task_selector/task_selector.cpp | 35 +++++ .../taskmanager/task_selector/task_selector.h | 79 ++++++++++ .../task_selector_with_memory.cpp | 116 +++++++++++++++ .../task_selector/task_selector_with_memory.h | 113 ++++++++++++++ .../tests/taskmanager/task_scheduler_test.cpp | 59 +++++--- .../tests/taskmanager/task_selector_test.cpp | 140 ++++++++++++++++++ plugins/ets/runtime/ets_vm.cpp | 4 +- plugins/ets/runtime_options.yaml | 11 ++ 19 files changed, 956 insertions(+), 58 deletions(-) create mode 100644 libpandabase/taskmanager/task_selector/frequent_task_selector.cpp create mode 100644 libpandabase/taskmanager/task_selector/frequent_task_selector.h create mode 100644 libpandabase/taskmanager/task_selector/random_task_selector.cpp create mode 100644 libpandabase/taskmanager/task_selector/random_task_selector.h create mode 100644 libpandabase/taskmanager/task_selector/rare_task_selector.cpp create mode 100644 libpandabase/taskmanager/task_selector/rare_task_selector.h create mode 100644 libpandabase/taskmanager/task_selector/task_selector.cpp create mode 100644 libpandabase/taskmanager/task_selector/task_selector.h create mode 100644 libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp create mode 100644 libpandabase/taskmanager/task_selector/task_selector_with_memory.h create mode 100644 libpandabase/tests/taskmanager/task_selector_test.cpp diff --git a/libpandabase/BUILD.gn b/libpandabase/BUILD.gn index 26604c215..df3ac8a59 100644 --- a/libpandabase/BUILD.gn +++ b/libpandabase/BUILD.gn @@ -86,6 +86,11 @@ if (is_mingw) { "$ark_root/libpandabase/taskmanager/task.cpp", "$ark_root/libpandabase/taskmanager/task_queue.cpp", "$ark_root/libpandabase/taskmanager/task_scheduler.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/frequent_task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/random_task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/rare_task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp", "$ark_root/libpandabase/taskmanager/worker_thread.cpp", "$ark_root/libpandabase/utils/dfx.cpp", "$ark_root/libpandabase/utils/json_builder.cpp", @@ -119,6 +124,11 @@ if (is_mingw) { "$ark_root/libpandabase/taskmanager/task.cpp", "$ark_root/libpandabase/taskmanager/task_queue.cpp", "$ark_root/libpandabase/taskmanager/task_scheduler.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/frequent_task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/random_task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/rare_task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/task_selector.cpp", + "$ark_root/libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp", "$ark_root/libpandabase/taskmanager/worker_thread.cpp", # product build diff --git a/libpandabase/CMakeLists.txt b/libpandabase/CMakeLists.txt index 8e31cdc51..34ec48066 100644 --- a/libpandabase/CMakeLists.txt +++ b/libpandabase/CMakeLists.txt @@ -47,6 +47,11 @@ set(SOURCES ${PANDA_ROOT}/libpandabase/taskmanager/task_scheduler.cpp ${PANDA_ROOT}/libpandabase/taskmanager/task_queue.cpp ${PANDA_ROOT}/libpandabase/taskmanager/worker_thread.cpp + ${PANDA_ROOT}/libpandabase/taskmanager/task_selector/rare_task_selector.cpp + ${PANDA_ROOT}/libpandabase/taskmanager/task_selector/frequent_task_selector.cpp + ${PANDA_ROOT}/libpandabase/taskmanager/task_selector/task_selector.cpp + ${PANDA_ROOT}/libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp + ${PANDA_ROOT}/libpandabase/taskmanager/task_selector/random_task_selector.cpp ${ARKBASE_LTO_SOURCES} ) @@ -421,6 +426,7 @@ panda_add_gtest( SOURCES tests/taskmanager/task_test.cpp tests/taskmanager/task_scheduler_test.cpp + tests/taskmanager/task_selector_test.cpp LIBRARIES arkbase_static SANITIZERS diff --git a/libpandabase/taskmanager/task_queue.h b/libpandabase/taskmanager/task_queue.h index 5b0895f6a..253bea823 100644 --- a/libpandabase/taskmanager/task_queue.h +++ b/libpandabase/taskmanager/task_queue.h @@ -21,6 +21,8 @@ #include #include #include +#include +#include namespace panda::taskmanager { @@ -34,6 +36,14 @@ public: static_assert(sizeof(VMType) == sizeof(TaskQueueId) / 2); } + constexpr TaskQueueId(const TaskQueueId &) = default; + constexpr TaskQueueId &operator=(const TaskQueueId &) = default; + + constexpr TaskQueueId(TaskQueueId &&) = default; + constexpr TaskQueueId &operator=(TaskQueueId &&) = default; + + ~TaskQueueId() = default; + friend constexpr bool operator==(const TaskQueueId &lv, const TaskQueueId &rv) { return lv.val_ == rv.val_; @@ -48,6 +58,19 @@ public: { return lv.val_ < rv.val_; } + friend std::ostream &operator<<(std::ostream &os, const TaskQueueId &id) + { + os << id.val_; + return os; + } + class Hash { + public: + constexpr Hash() = default; + constexpr size_t operator()(const TaskQueueId &id) const + { + return id.val_; + } + }; private: uint16_t val_; @@ -171,6 +194,8 @@ private: bool finish_ {false}; }; +using TaskQueueMap = std::map; + } // namespace panda::taskmanager #endif // PANDA_LIBPANDABASE_TASKMANAGER_TASK_QUEUE_H diff --git a/libpandabase/taskmanager/task_scheduler.cpp b/libpandabase/taskmanager/task_scheduler.cpp index 9c6ed72e2..85ca806e1 100644 --- a/libpandabase/taskmanager/task_scheduler.cpp +++ b/libpandabase/taskmanager/task_scheduler.cpp @@ -20,14 +20,22 @@ namespace panda::taskmanager { TaskScheduler *TaskScheduler::instance_ = nullptr; -TaskScheduler::TaskScheduler(size_t workers_count) : workers_count_(workers_count) {} +TaskScheduler::TaskScheduler(size_t workers_count, TaskSelectorType selector_type) + : workers_count_(workers_count), + random_task_selector_(task_queues_), + rare_task_selector_(task_queues_, DEFAULT_RARE_REDUCTION_FACTOR, PRIORITY_VALUE), + frequent_task_selector_(task_queues_, DEFAULT_FREQUENT_REDUCTION_FACTOR, PRIORITY_VALUE) +{ + os::memory::LockHolder lock_holder(task_manager_lock_); + current_selector_type_ = selector_type; +} /* static */ -TaskScheduler *TaskScheduler::Create(size_t threads_count) +TaskScheduler *TaskScheduler::Create(size_t threads_count, TaskSelectorType selector_type) { ASSERT(instance_ == nullptr); ASSERT(threads_count > 0); - instance_ = new TaskScheduler(threads_count); + instance_ = new TaskScheduler(threads_count, selector_type); return instance_; } @@ -62,7 +70,11 @@ TaskQueueId TaskScheduler::RegisterQueue(TaskQueue *queue) void TaskScheduler::Initialize() { ASSERT(!start_); + os::memory::LockHolder task_manager_lock_holder(task_manager_lock_); start_ = true; + random_task_selector_.Start(); + rare_task_selector_.Start(); + frequent_task_selector_.Start(); LOG(DEBUG, RUNTIME) << "TaskScheduler: creates " << workers_count_ << " threads"; for (size_t i = 0; i < workers_count_; i++) { workers_.push_back(new WorkerThread( @@ -108,35 +120,30 @@ bool TaskScheduler::FillWithTasks(WorkerThread *worker, size_t tasks_count) Task TaskScheduler::GetNextTask() { ASSERT(!AreQueuesEmpty()); - LOG(DEBUG, RUNTIME) << "TaskScheduler: GetNextTask()"; - - auto kinetic_priorities = GetKineticPriorities(); - size_t kinetic_max = 0; - std::tie(kinetic_max, std::ignore) = *kinetic_priorities.rbegin(); // Get key of the last element in map - - // NOLINTNEXTLINE(cert-msc50-cpp) - size_t choice = std::rand() % kinetic_max; // Get random number in range [0, kinetic_max) - TaskQueue *queue = nullptr; - std::tie(std::ignore, queue) = *kinetic_priorities.upper_bound(choice); // Get queue of chosen element - - return queue->PopTask().value(); + LOG(DEBUG, COMMON) << "TaskScheduler: GetNextTask()"; + TaskSelectorInterface *selector = nullptr; + switch (current_selector_type_) { + case TaskSelectorType::RANDOM_SELECTOR: + selector = &random_task_selector_; + break; + case TaskSelectorType::RARE_SELECTOR: + selector = &rare_task_selector_; + break; + case TaskSelectorType::FREQUENT_SELECTOR: + selector = &frequent_task_selector_; + break; + default: + UNREACHABLE(); + } + return GetNextTaskWithSelector(selector); } -std::map TaskScheduler::GetKineticPriorities() const +Task TaskScheduler::GetNextTaskWithSelector(TaskSelectorInterface *selector) { - ASSERT(!task_queues_.empty()); // no TaskQueues - size_t kinetic_sum = 0; - std::map kinetic_priorities; - TaskQueue *queue = nullptr; - for (auto &traits_queue_pair : task_queues_) { - std::tie(std::ignore, queue) = traits_queue_pair; - if (queue->IsEmpty()) { - continue; - } - kinetic_sum += queue->GetPriority(); - kinetic_priorities[kinetic_sum] = queue; - } - return kinetic_priorities; + auto queue_id = selector->GetSelectedQueue(); + TaskQueue *task_queues_iterator = task_queues_[queue_id]; + auto task = task_queues_iterator->PopTask().value(); + return task; } void TaskScheduler::PutTaskInWorker(WorkerThread *worker, Task &&task) @@ -236,6 +243,18 @@ void TaskScheduler::IncrementFinishedTaskCounter(const TaskPropertiesCounterMap } } +void TaskScheduler::SetSelectorType(TaskSelectorType selector) +{ + os::memory::LockHolder lock_holder(task_manager_lock_); + current_selector_type_ = selector; +} + +TaskSelectorType TaskScheduler::GetUsedSelectorType() +{ + os::memory::LockHolder lock_holder(task_manager_lock_); + return current_selector_type_; +} + TaskScheduler::~TaskScheduler() { // We can delete TaskScheduler if it wasn't started or it was finished diff --git a/libpandabase/taskmanager/task_scheduler.h b/libpandabase/taskmanager/task_scheduler.h index 722a322b2..73d9a2c15 100644 --- a/libpandabase/taskmanager/task_scheduler.h +++ b/libpandabase/taskmanager/task_scheduler.h @@ -17,9 +17,12 @@ #define PANDA_LIBPANDABASE_TASKMANAGER_TASK_MANAGER_H #include "libpandabase/taskmanager/worker_thread.h" -#include "libpandabase/taskmanager/task_queue.h" +#include "libpandabase/taskmanager/task_selector/rare_task_selector.h" +#include "libpandabase/taskmanager/task_selector/frequent_task_selector.h" +#include "libpandabase/taskmanager/task_selector/random_task_selector.h" #include #include +#include namespace panda::taskmanager { @@ -30,6 +33,10 @@ namespace panda::taskmanager { * - JIT queue */ class TaskScheduler { + static constexpr double DEFAULT_RARE_REDUCTION_FACTOR = 0.15; + static constexpr double DEFAULT_FREQUENT_REDUCTION_FACTOR = 0.15; + static constexpr size_t PRIORITY_VALUE = 100; + public: NO_COPY_SEMANTIC(TaskScheduler); NO_MOVE_SEMANTIC(TaskScheduler); @@ -38,7 +45,8 @@ public: * @brief Creates an instance of TaskScheduler. * @param threads_count - number of worker that will be created be Task Manager */ - PANDA_PUBLIC_API static TaskScheduler *Create(size_t threads_count); + PANDA_PUBLIC_API static TaskScheduler *Create(size_t threads_count, + TaskSelectorType selector_type = TaskSelectorType::RARE_SELECTOR); /** * @brief Returns the pointer to TaskScheduler. If you use it before the Create or after Destroy methods, it will @@ -99,17 +107,27 @@ public: */ PANDA_PUBLIC_API void WaitForFinishAllTasksWithProperties(TaskProperties properties); + /* + * @brief Method changes selector. + * @param selector - TaskSelectorType that you want to use next + */ + PANDA_PUBLIC_API void SetSelectorType(TaskSelectorType selector); + + /// @brief Method returns current selector type + PANDA_PUBLIC_API TaskSelectorType GetUsedSelectorType(); + /// @brief This method indicates that workers can no longer wait for new tasks and be completed. PANDA_PUBLIC_API void Finalize(); PANDA_PUBLIC_API ~TaskScheduler(); private: - explicit TaskScheduler(size_t workers_count); + explicit TaskScheduler(size_t workers_count, TaskSelectorType selector_type); - /// @brief Method pops one task from internal queues based on priorities. + /// @brief Method pops one task from internal queues with selected strategy. [[nodiscard]] Task GetNextTask() REQUIRES(task_manager_lock_); + [[nodiscard]] Task GetNextTaskWithSelector(TaskSelectorInterface *selector) REQUIRES(task_manager_lock_); /** * @brief Method puts one @arg task to @arg worker. * @param worker - pointer on worker that should be fill will tasks @@ -121,7 +139,6 @@ private: * @brief This method @returns map from kinetic sum of non-empty queues to queues pointer * in the same order as they place in task_queues_. Use this method to choose next thread */ - std::map GetKineticPriorities() const REQUIRES(task_manager_lock_); /// @brief Checks if task queues are empty bool AreQueuesEmpty() const REQUIRES(task_manager_lock_); @@ -146,6 +163,8 @@ private: size_t workers_count_; + TaskSelectorType current_selector_type_ GUARDED_BY(task_manager_lock_); + /// Pointers to Worker Threads. std::vector workers_; @@ -158,7 +177,7 @@ private: * Since we can change the map only before creating the workers, we do not need to synchronize access after * Initialize method */ - std::map task_queues_; + TaskQueueMap task_queues_; /** * task_manager_lock_ is used in case of access to shared resources operated by the task manager: @@ -193,6 +212,11 @@ private: * - it was gotten by main thread; */ TaskPropertiesCounterMap finished_tasks_count_ GUARDED_BY(task_manager_lock_); + + /// Selectors + RandomTaskSelector random_task_selector_ GUARDED_BY(task_manager_lock_); + RareTaskSelector rare_task_selector_ GUARDED_BY(task_manager_lock_); + FrequentTaskSelector frequent_task_selector_ GUARDED_BY(task_manager_lock_); }; } // namespace panda::taskmanager diff --git a/libpandabase/taskmanager/task_selector/frequent_task_selector.cpp b/libpandabase/taskmanager/task_selector/frequent_task_selector.cpp new file mode 100644 index 000000000..e1b07ed47 --- /dev/null +++ b/libpandabase/taskmanager/task_selector/frequent_task_selector.cpp @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2023 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. + */ + +#include "taskmanager/task_selector/frequent_task_selector.h" + +namespace panda::taskmanager { + +FrequentTaskSelector::FrequentTaskSelector(const TaskQueueMap &task_queues, double reduction_factor, + size_t priority_value) + : TaskSelectorWithMemory(task_queues, TaskSelectorType::RARE_SELECTOR, priority_value), + reduction_factor_(reduction_factor) +{ +} + +void FrequentTaskSelector::UpdateTaskCounter(TaskQueueIdCounterMap &counter_map) +{ + TaskQueueId id = INVALID_TASKQUEUE_ID; + for (const auto &id_queue_pair : GetTaskQueues()) { + std::tie(id, std::ignore) = id_queue_pair; + selected_task_counter_map_[id] *= reduction_factor_; + counter_map[id] += selected_task_counter_map_[id]; + } +} + +void FrequentTaskSelector::ActionIfSkippingQueue([[maybe_unused]] TaskQueueId id) {} + +void FrequentTaskSelector::ActionIfSelectQueue(TaskQueueId id) +{ + selected_task_counter_map_[id]++; +} + +} // namespace panda::taskmanager \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/frequent_task_selector.h b/libpandabase/taskmanager/task_selector/frequent_task_selector.h new file mode 100644 index 000000000..100a9c28c --- /dev/null +++ b/libpandabase/taskmanager/task_selector/frequent_task_selector.h @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2023 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 PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_FREQUENT_TASK_SELECTOR_H +#define PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_FREQUENT_TASK_SELECTOR_H + +#include "libpandabase/taskmanager/task_selector/task_selector_with_memory.h" + +namespace panda::taskmanager { + +/* + * This selector implements the selection policy for TaskSelectorWithMemory. This selector counts the number of issued + * tasks and, when updating the queue, increases the counter value by the value of its own counter multiplied by a + * certain number from 0 to 1, which is set in advance. + */ +class FrequentTaskSelector : public TaskSelectorWithMemory { +public: + FrequentTaskSelector(const TaskQueueMap &task_queues, double reduction_factor, size_t priority_value); + ~FrequentTaskSelector() override = default; + + NO_COPY_SEMANTIC(FrequentTaskSelector); + NO_MOVE_SEMANTIC(FrequentTaskSelector); + +private: + void UpdateTaskCounter(TaskQueueIdCounterMap &counter_map) override; + + void ActionIfSkippingQueue(TaskQueueId id) override; + + void ActionIfSelectQueue(TaskQueueId id) override; + + double reduction_factor_; + + /// Counter of every TaskQueueId that was selected. + TaskQueueIdCounterMap selected_task_counter_map_; +}; + +} // namespace panda::taskmanager + +#endif // PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_FREQUENT_TASK_SELECTOR_H diff --git a/libpandabase/taskmanager/task_selector/random_task_selector.cpp b/libpandabase/taskmanager/task_selector/random_task_selector.cpp new file mode 100644 index 000000000..b15b5ad8a --- /dev/null +++ b/libpandabase/taskmanager/task_selector/random_task_selector.cpp @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2023 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. + */ + +#include "libpandabase/taskmanager/task_selector/random_task_selector.h" + +namespace panda::taskmanager { + +RandomTaskSelector::RandomTaskSelector(const TaskQueueMap &task_queues) + : TaskSelectorInterface(task_queues, TaskSelectorType::RANDOM_SELECTOR) +{ +} + +TaskQueueId RandomTaskSelector::GetSelectedQueue() +{ + UpdateKineticPriorities(); + size_t kinetic_max = 0; + std::tie(kinetic_max, std::ignore) = *kinetic_priorities_.rbegin(); // Get key of the last element in map + + // NOLINTNEXTLINE(cert-msc50-cpp) + size_t choice = std::rand() % kinetic_max; // Get random number in range [0, kinetic_max) + TaskQueueId id = INVALID_TASKQUEUE_ID; + std::tie(std::ignore, id) = *kinetic_priorities_.upper_bound(choice); // Get queue of chosen element + + return id; +} + +void RandomTaskSelector::Start() {} + +void RandomTaskSelector::UpdateKineticPriorities() +{ + const auto &task_queues = GetTaskQueues(); + ASSERT(!task_queues.empty()); // no TaskQueues + size_t kinetic_sum = 0; + TaskQueueId id = INVALID_TASKQUEUE_ID; + TaskQueue *queue = nullptr; + kinetic_priorities_.clear(); + for (const auto &id_queue_pair : task_queues) { + std::tie(id, queue) = id_queue_pair; + if (queue->IsEmpty()) { + continue; + } + kinetic_sum += task_queues.at(id)->GetPriority(); + kinetic_priorities_.insert({kinetic_sum, id}); + } +} + +} // namespace panda::taskmanager diff --git a/libpandabase/taskmanager/task_selector/random_task_selector.h b/libpandabase/taskmanager/task_selector/random_task_selector.h new file mode 100644 index 000000000..35c3f7589 --- /dev/null +++ b/libpandabase/taskmanager/task_selector/random_task_selector.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2023 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 PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_RANDOM_TASK_SELECTOR_H +#define PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_RANDOM_TASK_SELECTOR_H + +#include "libpandabase/taskmanager/task_selector/task_selector.h" + +namespace panda::taskmanager { + +/* + * Random Task Selector is a selector that selects a queue randomly. Priority, for this selector, is only a relative + * increase in the probability of choosing this queue. At each request, the selector generates a kinetic sum by + * priority, and then selects using a random number which point should be selected next + */ +class RandomTaskSelector : public TaskSelectorInterface { +public: + explicit RandomTaskSelector(const TaskQueueMap &task_queues); + ~RandomTaskSelector() override = default; + + NO_COPY_SEMANTIC(RandomTaskSelector); + NO_MOVE_SEMANTIC(RandomTaskSelector); + + /// @brief Method uses ot start selecting tasks. After execution task_queues instance should not be modified. + void Start() override; + + /// @brief Method should return id of selected queue. Queue should be not empty. + [[nodiscard]] TaskQueueId GetSelectedQueue() override; + +private: + /** + * @brief This method updates map from kinetic sum of non-empty queues to queues pointer + * in the same order as they place in task_queues_. + */ + void UpdateKineticPriorities(); + + std::map kinetic_priorities_; +}; + +} // namespace panda::taskmanager + +#endif // PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_RANDOM_TASK_SELECTOR_H \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/rare_task_selector.cpp b/libpandabase/taskmanager/task_selector/rare_task_selector.cpp new file mode 100644 index 000000000..1d4509111 --- /dev/null +++ b/libpandabase/taskmanager/task_selector/rare_task_selector.cpp @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2023 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. + */ + +#include "libpandabase/taskmanager/task_selector/rare_task_selector.h" + +namespace panda::taskmanager { + +RareTaskSelector::RareTaskSelector(const TaskQueueMap &task_queues, double reduction_factor, size_t priority_value) + : TaskSelectorWithMemory(task_queues, TaskSelectorType::RARE_SELECTOR, priority_value), + reduction_factor_(reduction_factor) +{ +} + +void RareTaskSelector::UpdateTaskCounter(TaskQueueIdCounterMap &counter_map) +{ + TaskQueueId id = INVALID_TASKQUEUE_ID; + for (const auto &id_queue_pair : GetTaskQueues()) { + std::tie(id, std::ignore) = id_queue_pair; + skipped_task_counter_map_[id] *= reduction_factor_; + counter_map[id] = counter_map[id] * reduction_factor_ + skipped_task_counter_map_[id]; + } +} + +void RareTaskSelector::ActionIfSkippingQueue(TaskQueueId id) +{ + skipped_task_counter_map_[id]++; +} + +void RareTaskSelector::ActionIfSelectQueue([[maybe_unused]] TaskQueueId id) {} + +} // namespace panda::taskmanager \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/rare_task_selector.h b/libpandabase/taskmanager/task_selector/rare_task_selector.h new file mode 100644 index 000000000..d41b857ec --- /dev/null +++ b/libpandabase/taskmanager/task_selector/rare_task_selector.h @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2023 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 PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_RARE_TASK_SELECTOR_H +#define PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_RARE_TASK_SELECTOR_H + +#include "libpandabase/taskmanager/task_selector/task_selector_with_memory.h" + +namespace panda::taskmanager { + +/* + * This selector implements the selection policy for TaskSelectorWithMemory. This selector counts the number of missed + * tasks and, when updating the queue, increases the counter value by the value of its own counter multiplied by a + * certain number from 0 to 1, which is set in advance. At the same time, when updating the values of the counters, the + * same is multiplied by this number. + */ +class RareTaskSelector : public TaskSelectorWithMemory { +public: + RareTaskSelector(const TaskQueueMap &task_queues, double reduction_factor, size_t priority_value); + ~RareTaskSelector() override = default; + + NO_COPY_SEMANTIC(RareTaskSelector); + NO_MOVE_SEMANTIC(RareTaskSelector); + +private: + void UpdateTaskCounter(TaskQueueIdCounterMap &counter_map) override; + + void ActionIfSkippingQueue(TaskQueueId id) override; + + void ActionIfSelectQueue(TaskQueueId id) override; + + double reduction_factor_; + + /// Counter of every TaskQueueId that was skipped. + TaskQueueIdCounterMap skipped_task_counter_map_; +}; + +} // namespace panda::taskmanager + +#endif // PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_RARE_TASK_SELECTOR_H \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/task_selector.cpp b/libpandabase/taskmanager/task_selector/task_selector.cpp new file mode 100644 index 000000000..ddee189c9 --- /dev/null +++ b/libpandabase/taskmanager/task_selector/task_selector.cpp @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2023 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. + */ + +#include "libpandabase/taskmanager/task_selector/task_selector.h" + +namespace panda::taskmanager { + +TaskSelectorInterface::TaskSelectorInterface(const TaskQueueMap &task_queues, const TaskSelectorType &type) + : task_queues_(task_queues), type_(type) +{ +} + +TaskSelectorType TaskSelectorInterface::GetType() const +{ + return type_; +} + +const TaskQueueMap &TaskSelectorInterface::GetTaskQueues() const +{ + return task_queues_; +} + +}; // namespace panda::taskmanager \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/task_selector.h b/libpandabase/taskmanager/task_selector/task_selector.h new file mode 100644 index 000000000..dff8fa9cd --- /dev/null +++ b/libpandabase/taskmanager/task_selector/task_selector.h @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2023 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 PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_TASK_SELECTOR_H +#define PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_TASK_SELECTOR_H + +#include "libpandabase/taskmanager/task_queue.h" +#include + +namespace panda::taskmanager { + +/** + * Now we have 2 types of selector: + * - RANDOM_SELECTOR: selector uses only current state and choose random queue based on priorities of queue. + * - RARE_SELECTOR: selector with memory, it wants to return specified count of task based on priorities and count of + * skipped tasks ids. + * - FREQUENT_SELECTOR: selector with memory, it wants to return specified count of task based on priorities and count + * of selected tasks ids. + */ +enum class TaskSelectorType : uint8_t { RANDOM_SELECTOR, RARE_SELECTOR, FREQUENT_SELECTOR }; + +constexpr TaskSelectorType StringToTaskSelectorType(std::string_view selector) +{ + if (selector == "random-selector") { + return TaskSelectorType::RANDOM_SELECTOR; + } + if (selector == "frequent-selector") { + return TaskSelectorType::FREQUENT_SELECTOR; + } + if (selector == "rare-selector") { + return TaskSelectorType::FREQUENT_SELECTOR; + } + UNREACHABLE(); +} +class TaskSelectorInterface { +public: + /** + * @param task_queues - reference to map from TaskQueueId to pointer on Queue. Instance on task_queues can be + * modified after constructor but can't be after Start method. + * @param type - TaskSelectorType of selector that will be used. + */ + TaskSelectorInterface(const TaskQueueMap &task_queues, const TaskSelectorType &type); + virtual ~TaskSelectorInterface() = default; + + NO_COPY_SEMANTIC(TaskSelectorInterface); + NO_MOVE_SEMANTIC(TaskSelectorInterface); + + TaskSelectorType GetType() const; + + /// @brief Method is used to start selecting tasks. After execution task_queues instance should not be modified. + virtual void Start() = 0; + + /// @brief Method should return id of selected queue. Queue should be not empty. + [[nodiscard]] virtual TaskQueueId GetSelectedQueue() = 0; + +protected: + using TaskQueueIdCounterMap = std::unordered_map; + const TaskQueueMap &GetTaskQueues() const; + +private: + const TaskQueueMap &task_queues_; + TaskSelectorType type_; +}; + +} // namespace panda::taskmanager + +#endif // PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_TASK_SELECTOR_H \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp b/libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp new file mode 100644 index 000000000..1bf634209 --- /dev/null +++ b/libpandabase/taskmanager/task_selector/task_selector_with_memory.cpp @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2023 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. + */ + +#include "libpandabase/taskmanager/task_selector/task_selector_with_memory.h" + +namespace panda::taskmanager { + +TaskSelectorWithMemory::TaskSelectorWithMemory(const TaskQueueMap &task_queues, TaskSelectorType type, + size_t priority_value) + : TaskSelectorInterface(task_queues, type), priority_value_(priority_value) +{ +} + +bool TaskSelectorWithMemory::IsPossibleToFindId() const +{ + const auto &task_queues = GetTaskQueues(); + for (const auto id : all_task_queues_) { + if (main_task_counter_map_.at(id) != 0 && !task_queues.at(id)->IsEmpty()) { + return true; + } + } + return false; +} + +bool TaskSelectorWithMemory::ExistNonEmptyQueue() const +{ + TaskQueue *queue = nullptr; + for (const auto &id_queue_pair : GetTaskQueues()) { + std::tie(std::ignore, queue) = id_queue_pair; + if (!queue->IsEmpty()) { + return true; + } + } + return false; +} + +void TaskSelectorWithMemory::Start() +{ + start_ = true; + const auto &task_queues = GetTaskQueues(); + TaskQueueId id = INVALID_TASKQUEUE_ID; + for (const auto &id_queue_pair : task_queues) { + std::tie(id, std::ignore) = id_queue_pair; + all_task_queues_.insert(id); + } + UpdateTaskCounterWithPriority(); + // set current_queue_id_ as last element in all_task_queues_ + current_queue_id_ = *all_task_queues_.rbegin(); +} + +TaskQueueId TaskSelectorWithMemory::GetSelectedQueue() +{ + ASSERT(start_); + ASSERT(ExistNonEmptyQueue()); + const auto &task_queues = GetTaskQueues(); + while (true) { + // Is queue empty() + current_queue_id_ = GetNextQueueId(); + // if queue is empty, we should skip it + if (task_queues.at(current_queue_id_)->IsEmpty()) { + // if counter of this id in zero we should decrement it. + if (main_task_counter_map_[current_queue_id_] != 0) { + ActionIfSkippingQueue(current_queue_id_); + main_task_counter_map_[current_queue_id_]--; + } + continue; + } + // Counter of current_queue_id_ is 0, we should skip it + if (main_task_counter_map_[current_queue_id_] == 0) { + if (IsPossibleToFindId()) { + // It's possible to find id of non-empty queue + continue; + } + // current_queue_id_ is the only id which queue is not empty + // We need to update main_task_counter_map_ + UpdateTaskCounter(main_task_counter_map_); + UpdateTaskCounterWithPriority(); + } + // We have selected id and returns it. + ActionIfSelectQueue(current_queue_id_); + main_task_counter_map_[current_queue_id_]--; + return current_queue_id_; + } +} + +void TaskSelectorWithMemory::UpdateTaskCounterWithPriority() +{ + TaskQueueId id = INVALID_TASKQUEUE_ID; + for (const auto &id_queue_pair : GetTaskQueues()) { + std::tie(id, std::ignore) = id_queue_pair; + main_task_counter_map_[id] += GetValidatePriority(id); + } +} + +TaskQueueId TaskSelectorWithMemory::GetNextQueueId() const +{ + auto next_id_iter = std::next(all_task_queues_.find(current_queue_id_)); + if (next_id_iter == all_task_queues_.end()) { + return *all_task_queues_.begin(); + } + return *next_id_iter; +} + +} // namespace panda::taskmanager \ No newline at end of file diff --git a/libpandabase/taskmanager/task_selector/task_selector_with_memory.h b/libpandabase/taskmanager/task_selector/task_selector_with_memory.h new file mode 100644 index 000000000..270187b5b --- /dev/null +++ b/libpandabase/taskmanager/task_selector/task_selector_with_memory.h @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2023 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 PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_TASK_SELECTOR_WITH_MEMORY_H +#define PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_TASK_SELECTOR_WITH_MEMORY_H + +#include "libpandabase/taskmanager/task_selector/task_selector.h" +#include + +namespace panda::taskmanager { + +/* + * Selector with memory uses TaskQueueIdCounterMap to save count of every TaskQueueId that we want to select. This + * selector works as follows: After the Start method has been used, the selector generates a list of all ids that can be + * used. When using the GetSelectedQueue method, the next id is taken and checked whether the queue with this id is + * empty. If it is empty, then if the counter for this id is not zero, then it will decrease by one and + * ActionIfSkippingQueue meters are produced, then the cycle repeats. If the queue is not empty, it is checked whether + * the counter for this id is equal to zero. If so, the selector checks whether it is possible to find a queue whose id + * can be returned (the Is PossibleToFindId method). If it is possible to find such an id, then the cycle starts from + * the beginning, if not, then 2 counter updates occur (UpdateTaskCounter - based on the selector policy, + * UpdateTaskCounterWithPriority - based on priority). If the counter is not empty, the counter is reduced and + * ActionIfSelectQueue is executed, and the selected id is returned. ActionIfShippingQueue, ActionIfSelectQueue, + * UpdateTaskCounterWithPriority are virtual methods that define the selection policy + */ +class TaskSelectorWithMemory : public TaskSelectorInterface { +public: + TaskSelectorWithMemory(const TaskQueueMap &task_queues, TaskSelectorType type, size_t priority_value); + ~TaskSelectorWithMemory() override = default; + + NO_COPY_SEMANTIC(TaskSelectorWithMemory); + NO_MOVE_SEMANTIC(TaskSelectorWithMemory); + + /// @brief Method uses ot start selecting tasks. After execution task_queues instance should not be modified. + void Start() override; + + /// @brief Method should return id of selected queue. Queue should be not empty. + [[nodiscard]] TaskQueueId GetSelectedQueue() override; + +protected: + /** + * @brief Updates counter_map when one task_queue is not empty but its counter is 0. It should based on choosen + * policy. + * @param counter_map - ref on TaskQueueIdCounterMap that should be updated + */ + virtual void UpdateTaskCounter(TaskQueueIdCounterMap &counter_map) = 0; + + /** + * @brief Method calls when selector want to skip queue id + * @param id - TaskQueueId of queue that will be skipped + */ + virtual void ActionIfSkippingQueue(TaskQueueId id) = 0; + + /** + * @brief Method calls when selector want to return queue id + * @param id - TaskQueueId of queue that will be returned + */ + virtual void ActionIfSelectQueue(TaskQueueId id) = 0; + + TaskQueueIdCounterMap &GetTaskCounterMap() + { + return main_task_counter_map_; + } + +private: + /** + * @brief return true if at least one id from not_empty_queues have not zero counter + * @param not_empty_queues - set of TaskQueueId of queue that are not empty at the moment + */ + bool IsPossibleToFindId() const; + + size_t GetValidatePriority(TaskQueueId id) + { + auto task_queue = GetTaskQueues(); + return task_queue[id]->GetPriority() * priority_value_; + } + + /// @brief Method updates main_task_counter_map_ with priority values of queues. + void UpdateTaskCounterWithPriority(); + + /// @brief Method returns next queue id based on current_queue_id_ and all_task_queues_. + TaskQueueId GetNextQueueId() const; + + bool ExistNonEmptyQueue() const; + + /// Value that represent who many task we will add in queue for one point of priority. + size_t priority_value_; + + /// Represent count of every TaskQueueId that can be selected or skipped. + TaskQueueIdCounterMap main_task_counter_map_; + + std::set all_task_queues_; + + /// Last id that was popped + TaskQueueId current_queue_id_ = INVALID_TASKQUEUE_ID; + + bool start_ {false}; +}; + +} // namespace panda::taskmanager + +#endif // PANDA_LIBPANDABASE_TASKMANAGER_TASK_SELECTOR_TASK_SELECTOR_WITH_MEMORY_H \ No newline at end of file diff --git a/libpandabase/tests/taskmanager/task_scheduler_test.cpp b/libpandabase/tests/taskmanager/task_scheduler_test.cpp index 16a28e705..239437b1c 100644 --- a/libpandabase/tests/taskmanager/task_scheduler_test.cpp +++ b/libpandabase/tests/taskmanager/task_scheduler_test.cpp @@ -15,6 +15,7 @@ #include "libpandabase/taskmanager/task_scheduler.h" #include "libpandabase/taskmanager/task.h" +#include "taskmanager/task_selector/task_selector.h" #include namespace panda::taskmanager { @@ -22,7 +23,7 @@ namespace panda::taskmanager { constexpr size_t DEFAULT_SEED = 123456; constexpr size_t TIMEOUT = 1; -class TaskSchedulerTest : public testing::Test { +class TaskSchedulerTest : public testing::TestWithParam { public: static constexpr TaskProperties GC_STATIC_VM_BACKGROUND_PROPERTIES {TaskType::GC, VMType::STATIC_VM, TaskExecutionMode::BACKGROUND}; @@ -167,12 +168,13 @@ TEST_F(TaskSchedulerTest, TaskQueueRegistration) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, TaskQueuesFillingFromOwner) +TEST_P(TaskSchedulerTest, TaskQueuesFillingFromOwner) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 5; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::DEFAULT_PRIORITY; TaskQueue gc_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -205,12 +207,13 @@ TEST_F(TaskSchedulerTest, TaskQueuesFillingFromOwner) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, TaskQueuesFillingFromTaskScheduler) +TEST_P(TaskSchedulerTest, TaskQueuesFillingFromTaskScheduler) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 5; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::DEFAULT_PRIORITY; TaskQueue gc_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -243,12 +246,13 @@ TEST_F(TaskSchedulerTest, TaskQueuesFillingFromTaskScheduler) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, ForegroundQueueTest) +TEST_P(TaskSchedulerTest, ForegroundQueueTest) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 1; // IMPORTANT: only one worker to see effect of using foreground execution mode - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::DEFAULT_PRIORITY; TaskQueue gc_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -283,12 +287,13 @@ TEST_F(TaskSchedulerTest, ForegroundQueueTest) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, TaskCreateTask) +TEST_P(TaskSchedulerTest, TaskCreateTask) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 5; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::DEFAULT_PRIORITY; TaskQueue gc_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -322,12 +327,13 @@ TEST_F(TaskSchedulerTest, TaskCreateTask) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, MultithreadingUsage) +TEST_P(TaskSchedulerTest, MultithreadingUsage) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 15; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create 4 thread. Each thread create, register and fill queues constexpr size_t PRODUCER_THREADS_COUNT = 4; SetQueueCount(PRODUCER_THREADS_COUNT); @@ -364,17 +370,18 @@ TEST_F(TaskSchedulerTest, MultithreadingUsage) delete gc_dynamic_thread; } -TEST_F(TaskSchedulerTest, TaskSchedulerGetTask) +TEST_P(TaskSchedulerTest, TaskSchedulerGetTask) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 1; // Worker will not be used in this test - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); constexpr uint8_t QUEUE_PRIORITY = TaskQueue::MAX_PRIORITY; auto queue = TaskQueue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); tm->RegisterQueue(&queue); std::queue global_queue; - constexpr size_t COUNT_OF_TASKS = 100; + constexpr size_t COUNT_OF_TASKS = 300; for (size_t i = 0; i < COUNT_OF_TASKS; i++) { tm->AddTask( Task::Create(GC_STATIC_VM_BACKGROUND_PROPERTIES, [&global_queue]() { global_queue.push(TaskType::GC); })); @@ -392,12 +399,13 @@ TEST_F(TaskSchedulerTest, TaskSchedulerGetTask) tm->Destroy(); } -TEST_F(TaskSchedulerTest, TasksWithMutex) +TEST_P(TaskSchedulerTest, TasksWithMutex) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 10; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::DEFAULT_PRIORITY; TaskQueue gc_task_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -407,7 +415,7 @@ TEST_F(TaskSchedulerTest, TasksWithMutex) // Initialize tm workers tm->Initialize(); // Fill queues with tasks that increment counter with its type. - constexpr size_t COUNT_OF_TASK = 1000; + constexpr size_t COUNT_OF_TASK = 300; std::array counters = {0, 0}; os::memory::Mutex main_mutex; for (size_t i = 0; i < COUNT_OF_TASK; i++) { @@ -429,12 +437,13 @@ TEST_F(TaskSchedulerTest, TasksWithMutex) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, TaskCreateTaskRecursively) +TEST_P(TaskSchedulerTest, TaskCreateTaskRecursively) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 5; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::MAX_PRIORITY; TaskQueue gc_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -445,7 +454,7 @@ TEST_F(TaskSchedulerTest, TaskCreateTaskRecursively) std::atomic_size_t counter = 0; constexpr size_t COUNT_OF_TASK = 10; constexpr size_t COUNT_OF_REPLICAS = 10; - constexpr size_t MAX_RECURSION_DEPTH = 5; + constexpr size_t MAX_RECURSION_DEPTH = 4; std::function runner; runner = [&counter, &runner](size_t recursion_depth) { if (recursion_depth < MAX_RECURSION_DEPTH) { @@ -467,12 +476,13 @@ TEST_F(TaskSchedulerTest, TaskCreateTaskRecursively) TaskScheduler::Destroy(); } -TEST_F(TaskSchedulerTest, TaskSchedulerTaskGetTask) +TEST_P(TaskSchedulerTest, TaskSchedulerTaskGetTask) { srand(GetSeed()); // Create TaskScheduler + auto task_selector_type = GetParam(); constexpr size_t THREADS_COUNT = 5; - auto *tm = TaskScheduler::Create(THREADS_COUNT); + auto *tm = TaskScheduler::Create(THREADS_COUNT, task_selector_type); // Create and register 2 queues constexpr uint8_t QUEUE_PRIORITY = TaskQueue::MAX_PRIORITY; TaskQueue gc_queue(TaskType::GC, VMType::STATIC_VM, QUEUE_PRIORITY); @@ -560,4 +570,9 @@ TEST_F(TaskSchedulerTest, TaskSchedulerWaitForFinishAllTaskFromQueue) TaskScheduler::Destroy(); } +constexpr std::array SELECTOR_TYPES = { + TaskSelectorType::RANDOM_SELECTOR, TaskSelectorType::RARE_SELECTOR, TaskSelectorType::FREQUENT_SELECTOR}; + +INSTANTIATE_TEST_SUITE_P(TaskSelectorTypeSet, TaskSchedulerTest, testing::ValuesIn(SELECTOR_TYPES)); + } // namespace panda::taskmanager \ No newline at end of file diff --git a/libpandabase/tests/taskmanager/task_selector_test.cpp b/libpandabase/tests/taskmanager/task_selector_test.cpp new file mode 100644 index 000000000..ca5c6c4f4 --- /dev/null +++ b/libpandabase/tests/taskmanager/task_selector_test.cpp @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2023 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. + */ + +#include "libpandabase/taskmanager/task_selector/rare_task_selector.h" +#include "libpandabase/taskmanager/task_selector/frequent_task_selector.h" +#include "taskmanager/task_queue.h" +#include + +namespace panda::taskmanager { + +class TaskSelectorWithMemoryTest : public testing::Test { +public: + using TaskQueueIdCounterMap = std::unordered_map; + TaskSelectorWithMemoryTest() = default; + ~TaskSelectorWithMemoryTest() override = default; + + NO_COPY_SEMANTIC(TaskSelectorWithMemoryTest); + NO_MOVE_SEMANTIC(TaskSelectorWithMemoryTest); + + static constexpr TaskProperties GC_STATIC_PROP {TaskType::GC, VMType::STATIC_VM, TaskExecutionMode::BACKGROUND}; + static constexpr TaskProperties JIT_STATIC_PROP {TaskType::JIT, VMType::STATIC_VM, TaskExecutionMode::BACKGROUND}; + static constexpr TaskProperties GC_DYNAMIC_PROP {TaskType::GC, VMType::DYNAMIC_VM, TaskExecutionMode::BACKGROUND}; + + void InsertTaskQueue(TaskQueue *queue) + { + TaskQueueId id(queue->GetTaskType(), queue->GetVMType()); + task_queues_[id] = queue; + not_empty_queues_.insert(id); + } + + const TaskQueueMap &GetTaskQueues() const + { + return task_queues_; + } + + const std::set &GetNotEmptyQueues() const + { + return not_empty_queues_; + } + +private: + TaskQueueMap task_queues_; + std::set not_empty_queues_; +}; + +TEST_F(TaskSelectorWithMemoryTest, RareTaskSelectorAlgorithmCorrectWorkWithFullQueues) +{ + constexpr double FACTOR = 0.5; + constexpr size_t PRIORITY_VALUE = 1; + RareTaskSelector selector(GetTaskQueues(), FACTOR, PRIORITY_VALUE); + // preparation of queues + constexpr size_t TASK_QUEUE_PRIORITY = TaskQueue::MAX_PRIORITY; + TaskQueue gc_static_queue(TaskType::GC, VMType::STATIC_VM, TASK_QUEUE_PRIORITY); + TaskQueue jit_static_queue(TaskType::JIT, VMType::STATIC_VM, TASK_QUEUE_PRIORITY); + TaskQueue gc_dynamic_queue(TaskType::GC, VMType::DYNAMIC_VM, TASK_QUEUE_PRIORITY); + InsertTaskQueue(&gc_static_queue); + InsertTaskQueue(&gc_dynamic_queue); + InsertTaskQueue(&jit_static_queue); + selector.Start(); + // insert tasks in queues + // GC_DYNAMIC_PROP will be first in task_queues, so if we will try get id 2 * TASK_QUEUE_PRIORITY times it will be + // skipped TASK_QUEUE_PRIORITY times + gc_static_queue.AddTask(Task::Create(GC_STATIC_PROP, []() {})); + jit_static_queue.AddTask(Task::Create(JIT_STATIC_PROP, []() {})); + // test selector + TaskQueueIdCounterMap counter_map; + for (size_t i = 0; i < 2 * TASK_QUEUE_PRIORITY; i++) { + counter_map[selector.GetSelectedQueue()]++; + } + EXPECT_EQ(counter_map.at({TaskType::GC, VMType::STATIC_VM}), TASK_QUEUE_PRIORITY); + EXPECT_EQ(counter_map.find({TaskType::GC, VMType::DYNAMIC_VM}), counter_map.end()); + EXPECT_EQ(counter_map.at({TaskType::JIT, VMType::STATIC_VM}), TASK_QUEUE_PRIORITY); + counter_map.clear(); + gc_dynamic_queue.AddTask(Task::Create(GC_DYNAMIC_PROP, []() {})); + for (size_t i = 0; i < (3 + FACTOR) * TASK_QUEUE_PRIORITY; i++) { + counter_map[selector.GetSelectedQueue()]++; + } + EXPECT_EQ(counter_map.at({TaskType::GC, VMType::STATIC_VM}), TASK_QUEUE_PRIORITY); + EXPECT_EQ(counter_map.at({TaskType::GC, VMType::DYNAMIC_VM}), (1 + FACTOR) * TASK_QUEUE_PRIORITY); + EXPECT_EQ(counter_map.at({TaskType::JIT, VMType::STATIC_VM}), TASK_QUEUE_PRIORITY); + // pop all tasks + (void)gc_static_queue.PopTask(); + (void)gc_dynamic_queue.PopTask(); + (void)jit_static_queue.PopTask(); +} + +TEST_F(TaskSelectorWithMemoryTest, FrequentTaskSelectorAlgorithmCorrectWorkWithFullQueues) +{ + constexpr double FACTOR = 0.5; + constexpr size_t PRIORITY_VALUE = 1; + FrequentTaskSelector selector(GetTaskQueues(), FACTOR, PRIORITY_VALUE); + // preparation of queues + constexpr size_t TASK_QUEUE_PRIORITY = TaskQueue::MAX_PRIORITY; + TaskQueue gc_static_queue(TaskType::GC, VMType::STATIC_VM, TASK_QUEUE_PRIORITY); + TaskQueue jit_static_queue(TaskType::JIT, VMType::STATIC_VM, TASK_QUEUE_PRIORITY); + TaskQueue gc_dynamic_queue(TaskType::GC, VMType::DYNAMIC_VM, TASK_QUEUE_PRIORITY); + InsertTaskQueue(&gc_static_queue); + InsertTaskQueue(&gc_dynamic_queue); + InsertTaskQueue(&jit_static_queue); + selector.Start(); + // insert tasks in queues + // JIT_STATIC_PROP will be last in task_queues, so if we will try get id TASK_QUEUE_PRIORITY times every other id + // will be skipped TASK_QUEUE_PRIORITY times + jit_static_queue.AddTask(Task::Create(JIT_STATIC_PROP, []() {})); + // test selector + TaskQueueIdCounterMap counter_map; + for (size_t i = 0; i < TASK_QUEUE_PRIORITY; i++) { + counter_map[selector.GetSelectedQueue()]++; + } + EXPECT_EQ(counter_map.find({TaskType::GC, VMType::STATIC_VM}), counter_map.end()); + EXPECT_EQ(counter_map.find({TaskType::GC, VMType::DYNAMIC_VM}), counter_map.end()); + EXPECT_EQ(counter_map.at({TaskType::JIT, VMType::STATIC_VM}), TASK_QUEUE_PRIORITY); + counter_map.clear(); + gc_static_queue.AddTask(Task::Create(GC_STATIC_PROP, []() {})); + gc_dynamic_queue.AddTask(Task::Create(GC_DYNAMIC_PROP, []() {})); + for (size_t i = 0; i < (3 + FACTOR) * TASK_QUEUE_PRIORITY; i++) { + counter_map[selector.GetSelectedQueue()]++; + } + EXPECT_EQ(counter_map.at({TaskType::GC, VMType::STATIC_VM}), TASK_QUEUE_PRIORITY); + EXPECT_EQ(counter_map.at({TaskType::GC, VMType::DYNAMIC_VM}), TASK_QUEUE_PRIORITY); + EXPECT_EQ(counter_map.at({TaskType::JIT, VMType::STATIC_VM}), (1 + FACTOR) * TASK_QUEUE_PRIORITY); + // pop all tasks + (void)gc_static_queue.PopTask(); + (void)gc_dynamic_queue.PopTask(); + (void)jit_static_queue.PopTask(); +} + +} // namespace panda::taskmanager \ No newline at end of file diff --git a/plugins/ets/runtime/ets_vm.cpp b/plugins/ets/runtime/ets_vm.cpp index b962f105c..23af5cd47 100644 --- a/plugins/ets/runtime/ets_vm.cpp +++ b/plugins/ets/runtime/ets_vm.cpp @@ -82,7 +82,9 @@ bool PandaEtsVM::CreateTaskManagerIfNeeded(const RuntimeOptions &options) { auto lang_str = plugins::LangToRuntimeType(panda_file::SourceLang::ETS); if (options.GetWorkersType(lang_str) == "taskmanager" && Runtime::GetTaskScheduler() == nullptr) { - auto *task_scheduler = taskmanager::TaskScheduler::Create(options.GetTaskmanagerWorkersCount(lang_str)); + auto *task_scheduler = taskmanager::TaskScheduler::Create( + options.GetTaskmanagerWorkersCount(lang_str), + taskmanager::StringToTaskSelectorType(options.GetTaskmanagerSelectorType(lang_str))); if (task_scheduler == nullptr) { return false; } diff --git a/plugins/ets/runtime_options.yaml b/plugins/ets/runtime_options.yaml index 45758dbed..57eb6afe2 100644 --- a/plugins/ets/runtime_options.yaml +++ b/plugins/ets/runtime_options.yaml @@ -216,3 +216,14 @@ options: type: uint32_t default: 4 description: Number of worker threads for the taskmanager. Option is used only for --worker-type=taskmanager + +- name: taskmanager-selector-type + lang: + - ets + type: std::string + default: rare-selector + possible_values: + - random-selector + - rare-selector + - frequent-selector + description: Set type of selector for task scheduler. TaskSelector it's a structure that select queue for getting task. Option is used only for --worker-type=taskmanager -- Gitee