/* * 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 FFRT_EXECUTE_UNIT_HPP #define FFRT_EXECUTE_UNIT_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include "cpp/mutex.h" #include "eu/thread_group.h" #include "eu/cpu_worker.h" #include "sync/sync.h" #include "internal_inc/osal.h" #include "util/cb_func.h" #ifdef FFRT_WORKERS_DYNAMIC_SCALING #include "eu/blockaware.h" #endif namespace { constexpr uint64_t ONE_STAGE_INTERVAL = 10; constexpr uint64_t TWO_STAGE_INTERVAL = 100; constexpr uint64_t THREE_STAGE_INTERVAL = 1000; constexpr uint64_t ONE_STAGE_WORKER_NUM = 128; constexpr uint64_t TWO_STAGE_WORKER_NUM = 256; constexpr int DEEP_SLEEP_NUM_DOUBLE = 2; } namespace ffrt { enum class TaskNotifyType { TASK_PICKED = 0, TASK_ADDED, TASK_ESCAPED, TASK_ADDED_RTQ, }; struct WorkerStatusInfo { unsigned int startedCnt = 0; unsigned int exitedCnt = 0; std::deque startedTids; std::deque exitedTids; }; struct CPUWorkerGroup { // rtg parameters std::unique_ptr tg; uint64_t tgRefCount = 0; mutable std::shared_mutex tgMutex; // worker manage parameters size_t hardLimit{0}; size_t maxConcurrency{0}; size_t workerStackSize{0}; bool setWorkerMaxNum{false}; std::unordered_map> threads; std::mutex* mutex; // global sched mutex(per qos) shared with EU and Scheduler std::condition_variable cv; // group status parameters alignas(cacheline_size) fast_mutex lock; alignas(cacheline_size) int executingNum{0}; alignas(cacheline_size) int sleepingNum{0}; alignas(cacheline_size) bool irqEnable{false}; /* used for performance mode */ alignas(cacheline_size) bool fastWakeEnable = false; // directly wakeup first worker by futex alignas(cacheline_size) int pendingWakeCnt = 0; // number of workers waking but not waked-up yet alignas(cacheline_size) int pendingTaskCnt = 0; // number of tasks submitted to RTB but not picked-up yet // used for worker share std::vector> workerShareConfig; int deepSleepingWorkerNum{0}; bool retryBeforeDeepSleep{true}; std::chrono::steady_clock::time_point escapeReportTime{}; inline void WorkerCreate() { executingNum++; } inline void RollBackCreate() { std::lock_guard lk(lock); executingNum--; } inline void IntoDeepSleep() { std::lock_guard lk(lock); deepSleepingWorkerNum++; } inline void OutOfDeepSleep(bool irqWake = false) { std::lock_guard lk(lock); if (irqWake) { irqEnable = false; } sleepingNum--; deepSleepingWorkerNum--; executingNum++; } inline void OutOfSleep(bool irqWake = false) { std::lock_guard lk(lock); if (irqWake) { irqEnable = false; } if (pendingWakeCnt > 0) { pendingWakeCnt--; } sleepingNum--; executingNum++; } inline void WorkerDestroy() { std::lock_guard lk(lock); sleepingNum--; } inline bool TryDestroy() { std::lock_guard lk(lock); sleepingNum--; return sleepingNum > 0; } inline void RollbackDestroy(bool irqWake = false) { std::lock_guard lk(lock); if (irqWake) { irqEnable = false; } executingNum++; } inline void SetTearDown() { std::shared_lock lck(tgMutex); for (const auto& pair : threads) { pair.second->SetExited(); } } }; struct EscapeConfig { bool enableEscape_ = false; uint64_t oneStageIntervalMs_ = ONE_STAGE_INTERVAL; uint64_t twoStageIntervalMs_ = TWO_STAGE_INTERVAL; uint64_t threeStageIntervalMs_ = THREE_STAGE_INTERVAL; uint64_t oneStageWorkerNum_ = ONE_STAGE_WORKER_NUM; uint64_t twoStageWorkerNum_ = TWO_STAGE_WORKER_NUM; }; class ExecuteUnit { public: static void RegistInsCb(SingleInsCB::Instance &&cb); ThreadGroup* BindTG(QoS& qos); void UnbindTG(QoS& qos); void BindWG(QoS& qos); // event notify template void NotifyTask(const QoS &qos, bool isPollWait = false, bool isRisingEdge = false) { if constexpr (TYPE == TaskNotifyType::TASK_ADDED) { PokeAdd(qos); } else if constexpr (TYPE == TaskNotifyType::TASK_PICKED) { PokePick(qos); } else if constexpr (TYPE == TaskNotifyType::TASK_ESCAPED) { PokeEscape(qos, isPollWait); } else if constexpr (TYPE == TaskNotifyType::TASK_ADDED_RTQ) { PokeAddRtq(qos, isRisingEdge); } } // dfx op virtual void WorkerInit() = 0; virtual CPUWorkerGroup &GetWorkerGroup(int qos) { return workerGroup[qos]; } virtual inline int SetWorkerMaxNum(const QoS &qos, uint32_t num) { CPUWorkerGroup &group = workerGroup[qos]; std::lock_guard lk(group.lock); if (group.setWorkerMaxNum) { FFRT_SYSEVENT_LOGE("qos[%d] worker num can only been setup once", qos()); return -1; } group.hardLimit = static_cast(num); group.setWorkerMaxNum = true; return 0; } int SetWorkerStackSize(const QoS &qos, size_t stack_size); // worker escape int SetEscapeEnable(uint64_t oneStageIntervalMs, uint64_t twoStageIntervalMs, uint64_t threeStageIntervalMs, uint64_t oneStageWorkerNum, uint64_t twoStageWorkerNum); inline void SetEscapeDisable() { escapeConfig.enableEscape_ = false; // after the escape function is disabled, parameters are restored to default values escapeConfig.oneStageIntervalMs_ = ONE_STAGE_INTERVAL; escapeConfig.twoStageIntervalMs_ = TWO_STAGE_INTERVAL; escapeConfig.threeStageIntervalMs_ = THREE_STAGE_INTERVAL; escapeConfig.oneStageWorkerNum_ = ONE_STAGE_WORKER_NUM; escapeConfig.twoStageWorkerNum_ = TWO_STAGE_WORKER_NUM; } inline bool IsEscapeEnable() { return escapeConfig.enableEscape_; } void SubmitEscape(int qos, uint64_t totalWorkerNum); inline uint64_t GetWorkerNum() { return workerNum.load(); } inline bool isEscapeStageOne(uint16_t totalWorkerNum) { if (totalWorkerNum < escapeConfig.oneStageWorkerNum_) { return true; } return false; } inline void SetSchedMode(const QoS qos, const sched_mode_type mode) { schedMode[qos].store(mode); } inline sched_mode_type GetSchedMode(const QoS qos) { return schedMode[qos].load(); } inline void SetWorkerShare(const std::map>> workerShareConfig) { for (const auto& item : workerShareConfig) { workerGroup[item.first].workerShareConfig = item.second; } } inline void SetTaskBacklog(const std::set userTaskBacklogConfig) { for (const QoS& qos : userTaskBacklogConfig) { taskBacklogConfig[qos] = true; } } virtual void DisableWorkerMonitor(const QoS& qos, int tid); void RestoreThreadConfig(); virtual void NotifyWorkers(const QoS &qos, int number); // used for worker sharing bool WorkerShare(CPUWorker* worker, bool(*taskFunction)(int, CPUWorker*)); // worker dynamic scaling #ifdef FFRT_WORKERS_DYNAMIC_SCALING void MonitorMain(); BlockawareWakeupCond *WakeupCond(void); void ExecuteSetSilentMode(const QoS &qos, unsigned int silentMode); #endif void WorkerStart(int qos); void WorkerExit(int qos); virtual WorkerAction WorkerIdleAction(CPUWorker *thread) = 0; virtual void WorkerPrepare(CPUWorker *thread) = 0; void WorkerRetired(CPUWorker *thread); WorkerStatusInfo GetWorkerStatusInfoAndReset(int qos); #ifdef FFRT_WORKERS_DYNAMIC_SCALING bool IsBlockAwareInit(void); #endif protected: virtual void WakeupWorkers(const QoS &qos) = 0; // worker manipulate op bool IncWorker(const QoS &qos); // worker rtg config void WorkerJoinTg(const QoS &qos, pid_t pid); void WorkerLeaveTg(const QoS &qos, pid_t pid); // worker group info inline bool IsExceedDeepSleepThreshold() { int totalWorker = 0; int deepSleepingWorkerNum = 0; for (unsigned int i = 0; i < static_cast(QoS::Max()); i++) { CPUWorkerGroup &group = workerGroup[i]; std::lock_guard lk(group.lock); deepSleepingWorkerNum += group.deepSleepingWorkerNum; totalWorker += group.executingNum + group.sleepingNum; } return deepSleepingWorkerNum * DEEP_SLEEP_NUM_DOUBLE > totalWorker; } // worker group state virtual void IntoSleep(const QoS &qos) = 0; ExecuteUnit(); virtual ~ExecuteUnit(); size_t GetRunningNum(const QoS &qos, const CPUWorkerGroup &group); void ReportEscapeEvent(int qos, size_t totalNum); CPUWorkerGroup workerGroup[QoS::MaxNum()]; std::atomic_uint64_t workerNum = 0; std::atomic_bool tearDown{false}; std::atomic_bool tearDownFinish{false}; #ifdef FFRT_WORKERS_DYNAMIC_SCALING bool blockAwareInit{false}; bool stopMonitor{false}; unsigned long keyPtr{0}; int qosMonitorMaxNum{std::min(QoS::Max(), BLOCKAWARE_DOMAIN_ID_MAX + 1)}; BlockawareWakeupCond wakeupCond; BlockawareDomainInfoArea domainInfoMonitor; #endif // eu sched task bacllog array bool taskBacklogConfig[QoS::MaxNum()] = {}; private: friend class FFRTFacade; static ExecuteUnit &Instance(); // use FFRTFacade::GetExecuteUnit to get EU Instance CPUWorker *CreateCPUWorker(const QoS &qos); virtual void PokeAdd(const QoS &qos) = 0; virtual void PokePick(const QoS &qos) = 0; virtual void PokeEscape(const QoS &qos, bool isPollWait) = 0; virtual void PokeAddRtq(const QoS &qos, bool isRisingEdge) = 0; // eu sched mode array static std::array, QoS::MaxNum()> schedMode; // worker escape EscapeConfig escapeConfig; std::atomic submittedDelayedTask_[QoS::MaxNum()] = {0}; WaitUntilEntry *we_[QoS::MaxNum()] = {nullptr}; virtual void ExecuteEscape(int qos) = 0; inline uint64_t CalEscapeInterval(uint16_t totalWorkerNum) { if (totalWorkerNum < escapeConfig.oneStageWorkerNum_) { return escapeConfig.oneStageIntervalMs_; } else if (totalWorkerNum >= escapeConfig.oneStageWorkerNum_ && totalWorkerNum < escapeConfig.twoStageWorkerNum_) { return escapeConfig.twoStageIntervalMs_; } else { return escapeConfig.threeStageIntervalMs_; } } }; } // namespace ffrt #endif