From 6564d6e81dc26226c515a23f41a48f6183ddf682 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niccol=C3=B2=20Betto?= <niccolo.betto@skywarder.eu>
Date: Wed, 28 Dec 2022 16:28:24 +0100
Subject: [PATCH] [TaskScheduler] Pre-allocate the agenda's backing vector

Avoids runtime re-allocations by reserving the
maximum capacity at construction time.
---
 src/shared/scheduler/TaskScheduler.cpp | 67 +++++++++++++++-----------
 src/shared/scheduler/TaskScheduler.h   | 33 +++++++++----
 2 files changed, 62 insertions(+), 38 deletions(-)

diff --git a/src/shared/scheduler/TaskScheduler.cpp b/src/shared/scheduler/TaskScheduler.cpp
index 035233b37..3e52a5342 100644
--- a/src/shared/scheduler/TaskScheduler.cpp
+++ b/src/shared/scheduler/TaskScheduler.cpp
@@ -31,6 +31,7 @@ using namespace miosix;
 
 namespace Boardcore
 {
+
 namespace Constants
 {
 static constexpr unsigned int TICKS_PER_MS =
@@ -39,8 +40,16 @@ static constexpr unsigned int MS_PER_TICK =
     1000 / miosix::TICK_FREQ;  // Number of milliseconds in a tick
 }  // namespace Constants
 
+TaskScheduler::EventQueue TaskScheduler::makeAgenda()
+{
+    std::vector<Event> agendaStorage{};
+    agendaStorage.reserve(MAX_TASKS);
+    return EventQueue{std::greater<Event>{}, std::move(agendaStorage)};
+}
+
 TaskScheduler::TaskScheduler(miosix::Priority priority)
-    : ActiveObject(STACK_MIN_FOR_SKYWARD, priority), tasks()
+    : ActiveObject(STACK_MIN_FOR_SKYWARD, priority), tasks(),
+      agenda(makeAgenda())
 {
     // Preallocate the vector to avoid dynamic allocation later on
     tasks.reserve(MAX_TASKS);
@@ -65,17 +74,21 @@ size_t TaskScheduler::addTask(function_t function, uint32_t period,
         return 0;
     }
 
-    // Insert a new task with the given parameters
-    tasks.emplace_back(function, period, policy);
-    size_t id = tasks.size() - 1;
-
     if (policy == Policy::ONE_SHOT)
     {
         startTick += period;
     }
 
-    // Add the task first event in the agenda, performs in-place construction
-    agenda.emplace(id, startTick);
+    // Insert a new task with the given parameters
+    tasks.emplace_back(function, period, policy, startTick);
+    size_t id = tasks.size() - 1;
+
+    // Only add the task to the agenda if the scheduler is running
+    // Otherwise, the agenda will be populated when the scheduler is started
+    if (isRunning())
+    {
+        agenda.emplace(id, startTick);
+    }
     condvar.broadcast();  // Signals the run thread
 
     mutex.unlock();
@@ -131,8 +144,8 @@ bool TaskScheduler::start()
         return false;
     }
 
-    // Normalize the tasks start time if they precede the current tick
-    normalizeTasks();
+    // Populate the agenda with the tasks we have so far
+    populateAgenda();
 
     return ActiveObject::start();
 }
@@ -163,27 +176,24 @@ vector<TaskStatsResult> TaskScheduler::getTaskStats()
     return result;
 }
 
-void TaskScheduler::normalizeTasks()
+void TaskScheduler::populateAgenda()
 {
     int64_t currentTick = getTick();
 
-    EventQueue newAgenda;
-    while (!agenda.empty())
+    for (size_t id = 1; id < tasks.size(); id++)
     {
-        Event event = agenda.top();
-        agenda.pop();
+        Task& task = tasks[id];
 
-        if (event.nextTick < currentTick)
+        int64_t nextTick = task.startTick;
+        // Normalize the tasks start time if they precede the current tick
+        if (nextTick < currentTick)
         {
-            Task& task = tasks[event.taskId];
-            event.nextTick +=
-                ((currentTick - event.nextTick) / task.period + 1) *
-                task.period;
+            nextTick +=
+                ((currentTick - nextTick) / task.period + 1) * task.period;
         }
 
-        newAgenda.push(event);
+        agenda.emplace(id, nextTick);
     }
-    agenda = std::move(newAgenda);
 }
 
 void TaskScheduler::run()
@@ -304,16 +314,17 @@ void TaskScheduler::enqueue(Event event, int64_t startTick)
 }
 
 TaskScheduler::Task::Task()
-    : function(nullptr), period(0), enabled(false), policy(Policy::SKIP),
-      lastCall(-1), activationStats(), periodStats(), workloadStats(),
-      missedEvents(0), failedEvents(0)
+    : function(nullptr), period(0), startTick(0), enabled(false),
+      policy(Policy::SKIP), lastCall(-1), activationStats(), periodStats(),
+      workloadStats(), missedEvents(0), failedEvents(0)
 {
 }
 
-TaskScheduler::Task::Task(function_t function, uint32_t period, Policy policy)
-    : function(function), period(period), enabled(true), policy(policy),
-      lastCall(-1), activationStats(), periodStats(), workloadStats(),
-      missedEvents(0), failedEvents(0)
+TaskScheduler::Task::Task(function_t function, uint32_t period, Policy policy,
+                          int64_t startTick)
+    : function(function), period(period), startTick(startTick), enabled(true),
+      policy(policy), lastCall(-1), activationStats(), periodStats(),
+      workloadStats(), missedEvents(0), failedEvents(0)
 {
 }
 
diff --git a/src/shared/scheduler/TaskScheduler.h b/src/shared/scheduler/TaskScheduler.h
index b7e83e496..fb10dcf87 100644
--- a/src/shared/scheduler/TaskScheduler.h
+++ b/src/shared/scheduler/TaskScheduler.h
@@ -87,7 +87,7 @@ public:
      * respected and the task will run consecutively for some time (See issue
      * #91).
      */
-    enum class Policy
+    enum class Policy : uint8_t
     {
         ONE_SHOT,  ///< Run the task one single timer.
         SKIP,      // Skips lost executions and stays aligned with the original
@@ -133,17 +133,13 @@ public:
     std::vector<TaskStatsResult> getTaskStats();
 
 private:
-    /**
-     * @brief Check the start time of the tasks in the agenda and moves them in
-     * the future respecting the period in respect to the original start time.
-     */
-    void normalizeTasks();
-
     struct Task
     {
         function_t function;
-        uint32_t period;  // [ms]
-        bool enabled;     ///< Whether the task should be executed.
+        uint32_t period;    // [ms]
+        int64_t startTick;  ///< First activation time, useful for synchronizing
+                            ///< tasks.
+        bool enabled;       ///< Whether the task should be executed.
         Policy policy;
         int64_t lastCall;  ///< Last activation tick for statistics computation.
         Stats activationStats;  ///< Stats about activation tick error.
@@ -163,8 +159,10 @@ private:
          * @param function The std::function to be called
          * @param period The Period in [ms]
          * @param policy The task policy in case of a miss
+         * @param startTick The first activation time
          */
-        explicit Task(function_t function, uint32_t period, Policy policy);
+        explicit Task(function_t function, uint32_t period, Policy policy,
+                      int64_t startTick);
 
         // Delete copy constructor and copy assignment operator to avoid copying
         // and force moving
@@ -209,6 +207,21 @@ private:
     using EventQueue =
         std::priority_queue<Event, std::vector<Event>, std::greater<Event>>;
 
+    /**
+     * @brief Instantiates a new EventQueue backed by a vector with a
+     * capacity of `MAX_TASKS` to avoid reallocations when inserting new events.
+     */
+    static EventQueue makeAgenda();
+
+    /**
+     * @brief Populates the agenda prior to starting the scheduler. Checks the
+     * start time of the tasks in the agenda and moves them in the future
+     * respecting the period in respect to the original start time.
+     * @note This function must be called before starting the scheduler or the
+     * agenda will be empty.
+     */
+    void populateAgenda();
+
     void run() override;
 
     /**
-- 
GitLab