Optimization: a slot for the next task to run (#529)

* Optimization: a slot for the next task to run

* Only notify workers when a task is pushed into a queue
pull/531/head
Stjepan Glavina 5 years ago committed by GitHub
parent d546ee3d92
commit 0c2282ffdc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,9 +2,7 @@
extern crate test; extern crate test;
use std::sync::Arc; use async_std::sync::{Arc, Mutex};
use async_std::sync::Mutex;
use async_std::task; use async_std::task;
use test::Bencher; use test::Bencher;

@ -170,6 +170,7 @@ impl<T> Mutex<T> {
/// # /// #
/// # }) /// # })
/// ``` /// ```
#[inline]
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> { pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
if !self.locked.swap(true, Ordering::SeqCst) { if !self.locked.swap(true, Ordering::SeqCst) {
Some(MutexGuard(self)) Some(MutexGuard(self))

@ -60,6 +60,7 @@ impl WakerSet {
} }
/// Inserts a waker for a blocked operation and returns a key associated with it. /// Inserts a waker for a blocked operation and returns a key associated with it.
#[cold]
pub fn insert(&self, cx: &Context<'_>) -> usize { pub fn insert(&self, cx: &Context<'_>) -> usize {
let w = cx.waker().clone(); let w = cx.waker().clone();
let mut inner = self.lock(); let mut inner = self.lock();
@ -70,6 +71,7 @@ impl WakerSet {
} }
/// Removes the waker of an operation. /// Removes the waker of an operation.
#[cold]
pub fn remove(&self, key: usize) { pub fn remove(&self, key: usize) {
let mut inner = self.lock(); let mut inner = self.lock();
@ -81,6 +83,7 @@ impl WakerSet {
/// Removes the waker of a cancelled operation. /// Removes the waker of a cancelled operation.
/// ///
/// Returns `true` if another blocked operation from the set was notified. /// Returns `true` if another blocked operation from the set was notified.
#[cold]
pub fn cancel(&self, key: usize) -> bool { pub fn cancel(&self, key: usize) -> bool {
let mut inner = self.lock(); let mut inner = self.lock();
@ -147,6 +150,7 @@ impl WakerSet {
/// Notifies blocked operations, either one or all of them. /// Notifies blocked operations, either one or all of them.
/// ///
/// Returns `true` if at least one operation was notified. /// Returns `true` if at least one operation was notified.
#[cold]
fn notify(&self, n: Notify) -> bool { fn notify(&self, n: Notify) -> bool {
let mut inner = &mut *self.lock(); let mut inner = &mut *self.lock();
let mut notified = false; let mut notified = false;
@ -172,7 +176,6 @@ impl WakerSet {
} }
/// Locks the list of entries. /// Locks the list of entries.
#[cold]
fn lock(&self) -> Lock<'_> { fn lock(&self) -> Lock<'_> {
let backoff = Backoff::new(); let backoff = Backoff::new();
while self.flag.fetch_or(LOCKED, Ordering::Acquire) & LOCKED != 0 { while self.flag.fetch_or(LOCKED, Ordering::Acquire) & LOCKED != 0 {

@ -1,10 +1,11 @@
use std::cell::UnsafeCell; use std::cell::Cell;
use std::iter; use std::iter;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use crossbeam_deque::{Injector, Stealer, Worker}; use crossbeam_deque::{Injector, Stealer, Worker};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use once_cell::unsync::OnceCell;
use crate::task::executor::Sleepers; use crate::task::executor::Sleepers;
use crate::task::Runnable; use crate::task::Runnable;
@ -32,9 +33,18 @@ static POOL: Lazy<Pool> = Lazy::new(|| {
let worker = Worker::new_fifo(); let worker = Worker::new_fifo();
stealers.push(worker.stealer()); stealers.push(worker.stealer());
let proc = Processor {
worker,
slot: Cell::new(None),
slot_runs: Cell::new(0),
};
thread::Builder::new() thread::Builder::new()
.name("async-std/executor".to_string()) .name("async-std/executor".to_string())
.spawn(|| abort_on_panic(|| main_loop(worker))) .spawn(|| {
let _ = PROCESSOR.with(|p| p.set(proc));
abort_on_panic(|| main_loop());
})
.expect("cannot start a thread driving tasks"); .expect("cannot start a thread driving tasks");
} }
@ -45,59 +55,75 @@ static POOL: Lazy<Pool> = Lazy::new(|| {
} }
}); });
/// The state of a worker thread.
struct Processor {
/// The local task queue.
worker: Worker<Runnable>,
/// Contains the next task to run as an optimization that skips queues.
slot: Cell<Option<Runnable>>,
/// How many times in a row tasks have been taked from the slot rather than the queue.
slot_runs: Cell<u32>,
}
thread_local! { thread_local! {
/// Local task queue associated with the current worker thread. /// Worker thread state.
static QUEUE: UnsafeCell<Option<Worker<Runnable>>> = UnsafeCell::new(None); static PROCESSOR: OnceCell<Processor> = OnceCell::new();
} }
/// Schedules a new runnable task for execution. /// Schedules a new runnable task for execution.
pub(crate) fn schedule(task: Runnable) { pub(crate) fn schedule(task: Runnable) {
QUEUE.with(|queue| { PROCESSOR.with(|proc| {
let local = unsafe { (*queue.get()).as_ref() }; // If the current thread is a worker thread, store it into its task slot or push it into
// its local task queue. Otherwise, push it into the global task queue.
// If the current thread is a worker thread, push the task into its local task queue. match proc.get() {
// Otherwise, push it into the global task queue. Some(proc) => {
match local { // Replace the task in the slot.
None => POOL.injector.push(task), if let Some(task) = proc.slot.replace(Some(task)) {
Some(q) => q.push(task), // If the slot already contained a task, push it into the local task queue.
proc.worker.push(task);
POOL.sleepers.notify_one();
}
}
None => {
POOL.injector.push(task);
POOL.sleepers.notify_one();
}
} }
}); })
// Notify a sleeping worker that new work just came in.
POOL.sleepers.notify_one();
} }
/// Main loop running a worker thread. /// Main loop running a worker thread.
fn main_loop(local: Worker<Runnable>) { fn main_loop() {
// Initialize the local task queue. /// Number of yields when no runnable task is found.
QUEUE.with(|queue| unsafe { *queue.get() = Some(local) }); const YIELDS: u32 = 3;
/// Number of short sleeps when no runnable task in found.
const SLEEPS: u32 = 1;
// The number of times the thread didn't find work in a row. // The number of times the thread didn't find work in a row.
let mut step = 0; let mut fails = 0;
loop { loop {
// Try to find a runnable task. // Try to find a runnable task.
match find_runnable() { match find_runnable() {
Some(task) => { Some(task) => {
// Found. Now run the task. fails = 0;
// Run the found task.
task.run(); task.run();
step = 0;
} }
None => { None => {
fails += 1;
// Yield the current thread or put it to sleep. // Yield the current thread or put it to sleep.
match step { if fails <= YIELDS {
0..=2 => { thread::yield_now();
thread::yield_now(); } else if fails <= YIELDS + SLEEPS {
step += 1; thread::sleep(Duration::from_micros(10));
} } else {
3 => { POOL.sleepers.wait();
thread::sleep(Duration::from_micros(10)); fails = 0;
step += 1;
}
_ => {
POOL.sleepers.wait();
step = 0;
}
} }
} }
} }
@ -106,29 +132,42 @@ fn main_loop(local: Worker<Runnable>) {
/// Find the next runnable task. /// Find the next runnable task.
fn find_runnable() -> Option<Runnable> { fn find_runnable() -> Option<Runnable> {
let pool = &*POOL; /// Maximum number of times the slot can be used in a row.
const SLOT_LIMIT: u32 = 16;
QUEUE.with(|queue| {
let local = unsafe { (*queue.get()).as_ref().unwrap() }; PROCESSOR.with(|proc| {
let proc = proc.get().unwrap();
// Try taking a task from the slot.
let runs = proc.slot_runs.get();
if runs < SLOT_LIMIT {
if let Some(task) = proc.slot.take() {
proc.slot_runs.set(runs + 1);
return Some(task);
}
}
proc.slot_runs.set(0);
// Pop a task from the local queue, if not empty. // Pop a task from the local queue, if not empty.
local.pop().or_else(|| { proc.worker.pop().or_else(|| {
// Otherwise, we need to look for a task elsewhere. // Otherwise, we need to look for a task elsewhere.
iter::repeat_with(|| { iter::repeat_with(|| {
// Try stealing a batch of tasks from the global queue. // Try stealing a batch of tasks from the global queue.
pool.injector POOL.injector
.steal_batch_and_pop(&local) .steal_batch_and_pop(&proc.worker)
// Or try stealing a batch of tasks from one of the other threads. // Or try stealing a batch of tasks from one of the other threads.
.or_else(|| { .or_else(|| {
// First, pick a random starting point in the list of local queues. // First, pick a random starting point in the list of local queues.
let len = pool.stealers.len(); let len = POOL.stealers.len();
let start = random(len as u32) as usize; let start = random(len as u32) as usize;
// Try stealing a batch of tasks from each local queue starting from the // Try stealing a batch of tasks from each local queue starting from the
// chosen point. // chosen point.
let (l, r) = pool.stealers.split_at(start); let (l, r) = POOL.stealers.split_at(start);
let rotated = r.iter().chain(l.iter()); let stealers = r.iter().chain(l.iter());
rotated.map(|s| s.steal_batch_and_pop(&local)).collect() stealers
.map(|s| s.steal_batch_and_pop(&proc.worker))
.collect()
}) })
}) })
// Loop while no task was stolen and any steal operation needs to be retried. // Loop while no task was stolen and any steal operation needs to be retried.

Loading…
Cancel
Save