Skip to content
Snippets Groups Projects
Commit d3bc8b55 authored by Niccolò Betto's avatar Niccolò Betto
Browse files

[TaskScheduler] Store tasks in a preallocated `std::vector`

The tasks vector is resized to MAX_TASKS (256 at the time of writing
this) immediately after construction to avoid reallocations.
parent b3c06531
No related branches found
No related tags found
No related merge requests found
...@@ -40,47 +40,33 @@ static constexpr unsigned int MS_PER_TICK = ...@@ -40,47 +40,33 @@ static constexpr unsigned int MS_PER_TICK =
} // namespace Constants } // namespace Constants
TaskScheduler::TaskScheduler(miosix::Priority priority) TaskScheduler::TaskScheduler(miosix::Priority priority)
: ActiveObject(STACK_MIN_FOR_SKYWARD, priority) : ActiveObject(STACK_MIN_FOR_SKYWARD, priority), tasks()
{ {
// Create dynamically the tasks vector because of too much space // Preallocate the vector to avoid dynamic allocation later on
tasks = new std::array<Task, TASKS_SIZE>(); tasks.reserve(MAX_TASKS);
// Initialize the vector elements // Reserve the zeroth element so that the task ID is never zero, for
for (size_t i = 1; i < TASKS_SIZE; i++) // API compatibility: returned value from addTask() is zero on error.
{ tasks.emplace_back();
(*tasks)[i] = Task();
}
} }
TaskScheduler::~TaskScheduler() { delete tasks; }
size_t TaskScheduler::addTask(function_t function, uint32_t period, size_t TaskScheduler::addTask(function_t function, uint32_t period,
Policy policy, int64_t startTick) Policy policy, int64_t startTick)
{ {
Lock<FastMutex> lock(mutex); Lock<FastMutex> lock(mutex);
size_t id = 1;
// Find a suitable id for the new task
for (; id < TASKS_SIZE; id++)
{
if ((*tasks)[id].empty())
{
break;
}
}
// Check if in the corresponding id there's already a task if (tasks.size() >= MAX_TASKS)
if ((*tasks)[id].enabled)
{ {
// Unlock the mutex for expensive operation // Unlock the mutex to release the scheduler resources before logging
Unlock<FastMutex> unlock(mutex); Unlock<FastMutex> unlock(mutex);
LOG_ERR(logger, "Full task scheduler, id = {:zu}", id); LOG_ERR(logger, "Full task scheduler");
return 0; return 0;
} }
// Create a new task with the given parameters // Insert a new task with the given parameters
(*tasks)[id] = Task(function, period, policy); tasks.emplace_back(function, period, policy);
size_t id = tasks.size() - 1;
if (policy == Policy::ONE_SHOT) if (policy == Policy::ONE_SHOT)
{ {
...@@ -96,14 +82,14 @@ size_t TaskScheduler::addTask(function_t function, uint32_t period, ...@@ -96,14 +82,14 @@ size_t TaskScheduler::addTask(function_t function, uint32_t period,
void TaskScheduler::enableTask(size_t id) void TaskScheduler::enableTask(size_t id)
{ {
if (id > TASKS_SIZE) if (id > tasks.size() - 1)
{ {
LOG_ERR(logger, "Tried to enable an out-of-range task, id = {}", id); LOG_ERR(logger, "Tried to enable an out-of-range task, id = {}", id);
return; return;
} }
Lock<FastMutex> lock(mutex); Lock<FastMutex> lock(mutex);
Task& task = (*tasks)[id]; Task& task = tasks[id];
// Check that the task function is not empty // Check that the task function is not empty
// Attempting to run an empty function will throw a bad_function_call // Attempting to run an empty function will throw a bad_function_call
...@@ -122,14 +108,14 @@ void TaskScheduler::enableTask(size_t id) ...@@ -122,14 +108,14 @@ void TaskScheduler::enableTask(size_t id)
void TaskScheduler::disableTask(size_t id) void TaskScheduler::disableTask(size_t id)
{ {
if (id > TASKS_SIZE) if (id > tasks.size() - 1)
{ {
LOG_ERR(logger, "Tried to disable an out-of-range task, id = {}", id); LOG_ERR(logger, "Tried to disable an out-of-range task, id = {}", id);
return; return;
} }
Lock<FastMutex> lock(mutex); Lock<FastMutex> lock(mutex);
(*tasks)[id].enabled = false; tasks[id].enabled = false;
} }
bool TaskScheduler::start() bool TaskScheduler::start()
...@@ -161,9 +147,9 @@ vector<TaskStatsResult> TaskScheduler::getTaskStats() ...@@ -161,9 +147,9 @@ vector<TaskStatsResult> TaskScheduler::getTaskStats()
vector<TaskStatsResult> result; vector<TaskStatsResult> result;
for (size_t id = 1; id < TASKS_SIZE; id++) for (size_t id = 1; id < tasks.size(); id++)
{ {
const Task& task = (*tasks)[id]; const Task& task = tasks[id];
if (task.enabled) if (task.enabled)
{ {
result.push_back(fromTaskIdPairToStatsResult(task, id)); result.push_back(fromTaskIdPairToStatsResult(task, id));
...@@ -185,7 +171,7 @@ void TaskScheduler::normalizeTasks() ...@@ -185,7 +171,7 @@ void TaskScheduler::normalizeTasks()
if (event.nextTick < currentTick) if (event.nextTick < currentTick)
{ {
Task& task = (*tasks)[event.taskId]; Task& task = tasks[event.taskId];
event.nextTick += event.nextTick +=
((currentTick - event.nextTick) / task.period + 1) * ((currentTick - event.nextTick) / task.period + 1) *
task.period; task.period;
...@@ -213,7 +199,7 @@ void TaskScheduler::run() ...@@ -213,7 +199,7 @@ void TaskScheduler::run()
int64_t startTick = getTick(); int64_t startTick = getTick();
Event nextEvent = agenda.top(); Event nextEvent = agenda.top();
Task& nextTask = (*tasks)[nextEvent.taskId]; Task& nextTask = tasks[nextEvent.taskId];
// If the task has the SKIP policy and its execution was missed, we need // If the task has the SKIP policy and its execution was missed, we need
// to move it forward to match the period // to move it forward to match the period
...@@ -260,7 +246,7 @@ void TaskScheduler::run() ...@@ -260,7 +246,7 @@ void TaskScheduler::run()
void TaskScheduler::updateStats(const Event& event, int64_t startTick, void TaskScheduler::updateStats(const Event& event, int64_t startTick,
int64_t endTick) int64_t endTick)
{ {
Task& task = (*tasks)[event.taskId]; Task& task = tasks[event.taskId];
// Activation stats // Activation stats
float activationError = startTick - event.nextTick; float activationError = startTick - event.nextTick;
...@@ -281,7 +267,7 @@ void TaskScheduler::updateStats(const Event& event, int64_t startTick, ...@@ -281,7 +267,7 @@ void TaskScheduler::updateStats(const Event& event, int64_t startTick,
void TaskScheduler::enqueue(Event event, int64_t startTick) void TaskScheduler::enqueue(Event event, int64_t startTick)
{ {
Task& task = (*tasks)[event.taskId]; Task& task = tasks[event.taskId];
switch (task.policy) switch (task.policy)
{ {
case Policy::ONE_SHOT: case Policy::ONE_SHOT:
......
...@@ -63,9 +63,9 @@ public: ...@@ -63,9 +63,9 @@ public:
using function_t = std::function<void()>; using function_t = std::function<void()>;
/** /**
* @brief It defines the tasks array maximum size * @brief The maximum number of tasks the scheduler can handle.
*/ */
static constexpr size_t TASKS_SIZE = 256; static constexpr size_t MAX_TASKS = 256;
/** /**
* @brief Task behavior policy. * @brief Task behavior policy.
...@@ -98,14 +98,10 @@ public: ...@@ -98,14 +98,10 @@ public:
explicit TaskScheduler(miosix::Priority priority = miosix::PRIORITY_MAX - explicit TaskScheduler(miosix::Priority priority = miosix::PRIORITY_MAX -
1); 1);
~TaskScheduler();
/** /**
* @brief Add a task function to the scheduler with an auto generated id. * @brief Add a task function to the scheduler with an auto generated ID.
* *
* Note that each task has it's own unique ID, even one shot tasks! * Note that each task has it's own unique ID, even one shot tasks!
* Therefore, if a task already exists with the same id, the function will
* fail and return false.
* *
* For one shot tasks, the period is used as a delay. If 0 the task will be * For one shot tasks, the period is used as a delay. If 0 the task will be
* executed immediately, otherwise after the given period. * executed immediately, otherwise after the given period.
...@@ -114,7 +110,7 @@ public: ...@@ -114,7 +110,7 @@ public:
* @param period Inter call period [ms]. * @param period Inter call period [ms].
* @param policy Task policy, default is SKIP. * @param policy Task policy, default is SKIP.
* @param startTick First activation time, useful for synchronizing tasks. * @param startTick First activation time, useful for synchronizing tasks.
* @return true if the task was added successfully. * @return The ID of the task if it was added successfully, 0 otherwise.
*/ */
size_t addTask(function_t function, uint32_t period, size_t addTask(function_t function, uint32_t period,
Policy policy = Policy::SKIP, Policy policy = Policy::SKIP,
...@@ -252,7 +248,8 @@ private: ...@@ -252,7 +248,8 @@ private:
} }
miosix::FastMutex mutex; ///< Mutex to protect tasks and agenda. miosix::FastMutex mutex; ///< Mutex to protect tasks and agenda.
std::array<Task, TASKS_SIZE>* tasks; ///< Holds all tasks to be scheduled. std::vector<Task> tasks; ///< Holds all tasks to be scheduled, preallocated
///< to MAX_TASKS avoid dynamic allocations.
miosix::ConditionVariable condvar; ///< Used when agenda is empty. miosix::ConditionVariable condvar; ///< Used when agenda is empty.
EventQueue agenda; ///< Ordered list of functions. EventQueue agenda; ///< Ordered list of functions.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment