Skip to content

Commit ced6470

Browse files
committed
scheduler: avoid redundant scheduler wake on process handoff
If SMP is enabled, instead of waking the polling scheduler, grab the first ready process immediately if any. Also simplify scheduler_make_ready a little bit to reduce the number of locks. Signed-off-by: Paul Guyot <pguyot@kallisys.net>
1 parent cba052c commit ced6470

2 files changed

Lines changed: 59 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4343
longer lines return `{error, {parser, {line_too_long, Prefix}}}` with the first 128 bytes of
4444
the offending line. Callers whose upstream servers emit unusually large headers must account
4545
for this limit
46+
- Improved performance of SMP scheduler
4647

4748
### Removed
4849
- Removed `ahttp_client` support for obsolete line folding (RFC 9112 §5.2); folded header and

src/libAtomVM/scheduler.c

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,32 @@ static void scheduler_process_native_signal_messages(Context *ctx)
152152
}
153153
}
154154

155+
static Context *scheduler_first_runnable_ready(GlobalContext *global)
156+
{
157+
Context *result = NULL;
158+
SMP_SPINLOCK_LOCK(&global->processes_spinlock);
159+
// Pick first ready which is not running.
160+
struct ListHead *next_ready = list_first(&global->ready_processes);
161+
while (next_ready != &global->ready_processes) {
162+
result = GET_LIST_ENTRY(next_ready, Context, processes_list_head);
163+
if (!(result->flags & Running)) {
164+
list_remove(next_ready);
165+
context_update_flags(result, ~Ready, Running);
166+
if (result->native_handler) {
167+
// Native handlers are marked as waiting
168+
list_append(&global->waiting_processes, next_ready);
169+
} else {
170+
list_append(&global->running_processes, next_ready);
171+
}
172+
break;
173+
}
174+
next_ready = next_ready->next;
175+
result = NULL;
176+
}
177+
SMP_SPINLOCK_UNLOCK(&global->processes_spinlock);
178+
return result;
179+
}
180+
155181
static Context *scheduler_run0(GlobalContext *global)
156182
{
157183
// This function should return a new process to run.
@@ -209,6 +235,13 @@ static Context *scheduler_run0(GlobalContext *global)
209235
return NULL;
210236
}
211237
if (!is_waiting) {
238+
// If a process is ready, process it instead of waking up
239+
// the poller scheduler
240+
result = scheduler_first_runnable_ready(global);
241+
if (result != NULL) {
242+
break;
243+
}
244+
212245
// Before entering the condition variable, signal the poll events
213246
// so the thread polling on events can check the ready queue.
214247
sys_signal(global);
@@ -232,41 +265,35 @@ static Context *scheduler_run0(GlobalContext *global)
232265
int32_t wait_timeout = update_timer_list(global);
233266
SMP_SPINLOCK_UNLOCK(&global->timer_spinlock);
234267

235-
SMP_SPINLOCK_LOCK(&global->processes_spinlock);
236-
// Pick first ready which is not running.
237-
struct ListHead *next_ready = list_first(&global->ready_processes);
238-
while (next_ready != &global->ready_processes) {
239-
result = GET_LIST_ENTRY(next_ready, Context, processes_list_head);
240-
if (!(result->flags & Running)) {
241-
list_remove(next_ready);
242-
context_update_flags(result, ~Ready, Running);
243-
if (result->native_handler) {
244-
// Native handlers are marked as waiting
245-
list_append(&global->waiting_processes, next_ready);
246-
} else {
247-
list_append(&global->running_processes, next_ready);
248-
}
249-
break;
250-
}
251-
next_ready = next_ready->next;
252-
result = NULL;
268+
if (result == NULL) {
269+
result = scheduler_first_runnable_ready(global);
253270
}
254-
SMP_SPINLOCK_UNLOCK(&global->processes_spinlock);
255271

256-
if (result == NULL && !global->scheduler_stop_all) {
257-
sys_poll_events(global, wait_timeout);
258-
} else {
259-
sys_poll_events(global, SYS_POLL_EVENTS_DO_NOT_WAIT);
272+
// Only the poller scheduler drives the event loop.
273+
#ifndef AVM_NO_SMP
274+
if (is_waiting) {
275+
#endif
276+
if (result == NULL && !global->scheduler_stop_all) {
277+
// The poller may block waiting for events.
278+
sys_poll_events(global, wait_timeout);
279+
} else {
280+
sys_poll_events(global, SYS_POLL_EVENTS_DO_NOT_WAIT);
281+
}
282+
#ifndef AVM_NO_SMP
260283
}
284+
#endif
261285
#ifdef AVM_TASK_DRIVER_ENABLED
262286
globalcontext_process_task_driver_queues(global);
263287
#endif
264288
SMP_MUTEX_LOCK(global->schedulers_mutex);
265289
} while (result == NULL);
266290

267291
#ifndef AVM_NO_SMP
268-
global->waiting_scheduler = false;
269-
smp_condvar_signal(global->schedulers_cv);
292+
// Only the polling scheduler relinquishes the poller role.
293+
if (is_waiting) {
294+
global->waiting_scheduler = false;
295+
smp_condvar_signal(global->schedulers_cv);
296+
}
270297
SMP_MUTEX_UNLOCK(global->schedulers_mutex);
271298
#endif
272299

@@ -356,6 +383,12 @@ static void scheduler_make_ready(Context *ctx)
356383
return;
357384
}
358385
list_remove(&ctx->processes_list_head);
386+
// Move to ready queue (from waiting or running)
387+
// The process may be running (it would be signaled), so mark it
388+
// as ready
389+
context_update_flags(ctx, ~NoFlags, Ready);
390+
list_append(&global->ready_processes, &ctx->processes_list_head);
391+
SMP_SPINLOCK_UNLOCK(&global->processes_spinlock);
359392
#ifndef AVM_NO_SMP
360393
if (SMP_MUTEX_TRYLOCK(global->schedulers_mutex)) {
361394
// Start a new scheduler if none are going to take this process.
@@ -366,17 +399,6 @@ static void scheduler_make_ready(Context *ctx)
366399
global->running_schedulers++;
367400
smp_scheduler_start(global);
368401
}
369-
SMP_MUTEX_UNLOCK(global->schedulers_mutex);
370-
}
371-
#endif
372-
// Move to ready queue (from waiting or running)
373-
// The process may be running (it would be signaled), so mark it
374-
// as ready
375-
context_update_flags(ctx, ~NoFlags, Ready);
376-
list_append(&global->ready_processes, &ctx->processes_list_head);
377-
SMP_SPINLOCK_UNLOCK(&global->processes_spinlock);
378-
#ifndef AVM_NO_SMP
379-
if (SMP_MUTEX_TRYLOCK(global->schedulers_mutex)) {
380402
if (global->waiting_scheduler) {
381403
sys_signal(global);
382404
}

0 commit comments

Comments
 (0)