avoid string copy in scheduler for const strings

This commit is contained in:
J. Nick Koston
2025-06-25 19:08:18 +02:00
parent a35e476be5
commit 0946f28511
2 changed files with 91 additions and 9 deletions

View File

@@ -22,8 +22,17 @@ static const uint32_t MAX_LOGICALLY_DELETED_ITEMS = 10;
// iterating over them from the loop task is fine; but iterating from any other context requires the lock to be held to
// avoid the main thread modifying the list while it is being accessed.
void HOT Scheduler::set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func) {
return this->set_timeout_(component, name, timeout, func, false);
}
void HOT Scheduler::set_timeout(Component *component, const std::string &name, uint32_t timeout,
std::function<void()> func) {
return this->set_timeout_(component, name, timeout, func, true);
}
void HOT Scheduler::set_timeout_(Component *component, const std::string &name, uint32_t timeout,
std::function<void()> func, bool make_copy) {
const auto now = this->millis_();
if (!name.empty())
@@ -34,7 +43,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u
auto item = make_unique<SchedulerItem>();
item->component = component;
item->name = name;
item->set_name(name.c_str(), make_copy);
item->type = SchedulerItem::TIMEOUT;
item->next_execution_ = now + timeout;
item->callback = std::move(func);
@@ -49,6 +58,14 @@ bool HOT Scheduler::cancel_timeout(Component *component, const std::string &name
}
void HOT Scheduler::set_interval(Component *component, const std::string &name, uint32_t interval,
std::function<void()> func) {
this->set_interval_(component, name, interval, func, true);
}
void HOT Scheduler::set_interval(Component *component, const char *name, uint32_t interval,
std::function<void()> func) {
this->set_interval_(component, name, interval, func, false);
}
void HOT Scheduler::set_interval_(Component *component, const std::string &name, uint32_t interval,
std::function<void()> func, bool make_copy) {
const auto now = this->millis_();
if (!name.empty())
@@ -64,7 +81,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name,
auto item = make_unique<SchedulerItem>();
item->component = component;
item->name = name;
item->set_name(name.c_str(), make_copy);
item->type = SchedulerItem::INTERVAL;
item->interval = interval;
item->next_execution_ = now + offset;
@@ -85,7 +102,7 @@ struct RetryArgs {
uint8_t retry_countdown;
uint32_t current_interval;
Component *component;
std::string name;
std::string name; // Keep as std::string since retry uses it dynamically
float backoff_increase_factor;
Scheduler *scheduler;
};
@@ -303,14 +320,16 @@ bool HOT Scheduler::cancel_item_(Component *component, const std::string &name,
LockGuard guard{this->lock_};
bool ret = false;
for (auto &it : this->items_) {
if (it->component == component && it->name == name && it->type == type && !it->remove) {
const char *item_name = it->get_name();
if (it->component == component && item_name != nullptr && name == item_name && it->type == type && !it->remove) {
to_remove_++;
it->remove = true;
ret = true;
}
}
for (auto &it : this->to_add_) {
if (it->component == component && it->name == name && it->type == type) {
const char *item_name = it->get_name();
if (it->component == component && item_name != nullptr && name == item_name && it->type == type) {
it->remove = true;
ret = true;
}

View File

@@ -12,11 +12,19 @@ class Component;
class Scheduler {
public:
// Public API - accepts std::string for backward compatibility
void set_timeout(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func);
void set_timeout(Component *component, const char *name, uint32_t timeout, std::function<void()> func);
void set_timeout_(Component *component, const std::string &name, uint32_t timeout, std::function<void()> func,
bool make_copy);
bool cancel_timeout(Component *component, const std::string &name);
void set_interval(Component *component, const std::string &name, uint32_t interval, std::function<void()> func);
bool cancel_interval(Component *component, const std::string &name);
void set_interval(Component *component, const char *name, uint32_t interval, std::function<void()> func);
void set_interval_(Component *component, const std::string &name, uint32_t interval, std::function<void()> func,
bool make_copy);
bool cancel_interval(Component *component, const std::string &name);
void set_retry(Component *component, const std::string &name, uint32_t initial_wait_time, uint8_t max_attempts,
std::function<RetryResult(uint8_t)> func, float backoff_increase_factor = 1.0f);
bool cancel_retry(Component *component, const std::string &name);
@@ -36,10 +44,65 @@ class Scheduler {
// with a 16-bit rollover counter to create a 64-bit time that won't roll over for
// billions of years. This ensures correct scheduling even when devices run for months.
uint64_t next_execution_;
std::string name;
// Optimized name storage using tagged union
union {
const char *static_name; // For string literals (no allocation)
char *dynamic_name; // For allocated strings
} name_;
std::function<void()> callback;
enum Type : uint8_t { TIMEOUT, INTERVAL } type;
bool remove;
// Bit-packed fields to minimize padding
enum Type : uint8_t { TIMEOUT, INTERVAL } type : 1;
bool remove : 1;
bool owns_name : 1; // True if name_.dynamic_name needs to be freed
// 5 bits padding
// Constructor
SchedulerItem()
: component(nullptr),
interval(0),
next_execution_(0),
callback(nullptr),
type(TIMEOUT),
remove(false),
owns_name(false) {
name_.static_name = nullptr;
}
// Destructor to clean up dynamic names
~SchedulerItem() {
if (owns_name && name_.dynamic_name) {
delete[] name_.dynamic_name;
}
}
// Helper to get the name regardless of storage type
const char *get_name() const { return owns_name ? name_.dynamic_name : name_.static_name; }
// Helper to set name with proper ownership
void set_name(const char *name, bool make_copy = false) {
// Clean up old dynamic name if any
if (owns_name && name_.dynamic_name) {
delete[] name_.dynamic_name;
}
if (name == nullptr || name[0] == '\0') {
name_.static_name = nullptr;
owns_name = false;
} else if (make_copy) {
// Make a copy for dynamic strings
size_t len = strlen(name);
name_.dynamic_name = new char[len + 1];
strcpy(name_.dynamic_name, name);
owns_name = true;
} else {
// Use static string directly
name_.static_name = name;
owns_name = false;
}
}
static bool cmp(const std::unique_ptr<SchedulerItem> &a, const std::unique_ptr<SchedulerItem> &b);
const char *get_type_str() {