Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimization: Vendor jobserver impl and rm thread spawning in parallel compile_objects #889

Merged
merged 22 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c3e577f
Impl vendored jobserver implementation
NobodyXu Oct 21, 2023
14c1c9b
Convert parallel `compile_objects` to use future instead of threads
NobodyXu Oct 21, 2023
83eccca
Optimize parallel `compile_objects`
NobodyXu Oct 21, 2023
3ca2a7f
Fix `job_token`: Remove mpsc and make sure tokens are relased
NobodyXu Oct 21, 2023
1f1e857
Optimize `job_token`: Make `JobToken` zero-sized
NobodyXu Oct 22, 2023
dfcbae5
Fix `windows::JobServerClient::try_acquire` impl
NobodyXu Oct 22, 2023
19abb40
Fix `unix::JobServerClient::from_pipe`: Accept more fd access modes
NobodyXu Oct 22, 2023
7543bb6
Rm unnecessary `'static` bound in parameter of `job_token`
NobodyXu Oct 22, 2023
df659cd
Optimize parallel `compile_objects`: Sleep/yield if no progress is made
NobodyXu Oct 22, 2023
43527dd
Fix windows implementation: Match all return value explicitly
NobodyXu Oct 23, 2023
4f127d2
Use Result::ok() in job_token.rs
NobodyXu Oct 24, 2023
2f1b5aa
Fix grammer in comments
NobodyXu Oct 24, 2023
7bb7e40
simplify job_token impl
NobodyXu Oct 24, 2023
1a45c58
Add more comment explaining the design choice
NobodyXu Oct 25, 2023
31b3480
Refactor: Extract new mod `async_executor`
NobodyXu Oct 25, 2023
5cd8470
Update src/job_token/unix.rs
NobodyXu Nov 5, 2023
34ef631
Remove outdated comment
NobodyXu Nov 6, 2023
b06b920
Do not check for `--jobserver-fds` on windows
NobodyXu Nov 6, 2023
c36493f
Accept ASCII only in windows `JobServerClient::open` impl
NobodyXu Nov 6, 2023
c7f2d74
Use acquire and release ordering for atomic operation in `JobServer`
NobodyXu Nov 6, 2023
4e5536c
Add a TODO for use of `NUM_JOBS`
NobodyXu Nov 7, 2023
e7dbd3e
Simplify windows jobserver `WAIT_ABANDONED` errmsg
NobodyXu Nov 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,13 @@ exclude = ["/.github"]
edition = "2018"
rust-version = "1.53"

[dependencies]
jobserver = { version = "0.1.16", optional = true }

[target.'cfg(unix)'.dependencies]
# Don't turn on the feature "std" for this, see https://github.com/rust-lang/cargo/issues/4866
# which is still an issue with `resolver = "1"`.
libc = { version = "0.2.62", default-features = false }

[features]
parallel = ["jobserver"]
parallel = []

[dev-dependencies]
tempfile = "3"
13 changes: 13 additions & 0 deletions gen-windows-sys-binding/windows_sys.list
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Windows.Win32.Foundation.SysFreeString
Windows.Win32.Foundation.SysStringLen
Windows.Win32.Foundation.S_FALSE
Windows.Win32.Foundation.S_OK
Windows.Win32.Foundation.FALSE
Windows.Win32.Foundation.HANDLE
Windows.Win32.Foundation.WAIT_OBJECT_0
Windows.Win32.Foundation.WAIT_TIMEOUT
Windows.Win32.Foundation.WAIT_FAILED
Windows.Win32.Foundation.WAIT_ABANDONED

Windows.Win32.System.Com.SAFEARRAY
Windows.Win32.System.Com.SAFEARRAYBOUND
Expand All @@ -25,3 +31,10 @@ Windows.Win32.System.Registry.HKEY_LOCAL_MACHINE
Windows.Win32.System.Registry.KEY_READ
Windows.Win32.System.Registry.KEY_WOW64_32KEY
Windows.Win32.System.Registry.REG_SZ

Windows.Win32.System.Threading.ReleaseSemaphore
Windows.Win32.System.Threading.WaitForSingleObject
Windows.Win32.System.Threading.SEMAPHORE_MODIFY_STATE
Windows.Win32.System.Threading.THREAD_SYNCHRONIZE

Windows.Win32.System.WindowsProgramming.OpenSemaphoreA
118 changes: 118 additions & 0 deletions src/async_executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use std::{
cell::Cell,
future::Future,
pin::Pin,
ptr,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
thread,
time::Duration,
};

use crate::Error;

const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
// Cloning just returns a new no-op raw waker
|_| NOOP_RAW_WAKER,
// `wake` does nothing
|_| {},
// `wake_by_ref` does nothing
|_| {},
// Dropping does nothing as we don't allocate anything
|_| {},
);
const NOOP_RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE);

#[derive(Default)]
pub(super) struct YieldOnce(bool);

impl Future for YieldOnce {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
let flag = &mut std::pin::Pin::into_inner(self).0;
if !*flag {
*flag = true;
Poll::Pending
} else {
Poll::Ready(())
}
}
}

/// Execute the futures and return when they are all done.
///
/// Here we use our own homebrew async executor since cc is used in the build
/// script of many popular projects, pulling in additional dependencies would
/// significantly slow down its compilation.
pub(super) fn block_on<Fut1, Fut2>(
mut fut1: Fut1,
mut fut2: Fut2,
has_made_progress: &Cell<bool>,
) -> Result<(), Error>
where
Fut1: Future<Output = Result<(), Error>>,
Fut2: Future<Output = Result<(), Error>>,
{
// Shadows the future so that it can never be moved and is guaranteed
// to be pinned.
//
// The same trick used in `pin!` macro.
//
// TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!`
let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) });
let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) });

// TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version
// which it is stablised, replace this wth `Waker::noop`.
let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) };
let mut context = Context::from_waker(&waker);

let mut backoff_cnt = 0;

loop {
has_made_progress.set(false);

if let Some(fut) = fut2.as_mut() {
if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
fut2 = None;
res?;
}
}

if let Some(fut) = fut1.as_mut() {
if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
fut1 = None;
res?;
}
}

if fut1.is_none() && fut2.is_none() {
return Ok(());
}

if !has_made_progress.get() {
if backoff_cnt > 3 {
// We have yielded at least three times without making'
// any progress, so we will sleep for a while.
let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10));
NobodyXu marked this conversation as resolved.
Show resolved Hide resolved
thread::sleep(duration);
} else {
// Given that we spawned a lot of compilation tasks, it is unlikely
// that OS cannot find other ready task to execute.
//
// If all of them are done, then we will yield them and spawn more,
// or simply return.
//
// Thus this will not be turned into a busy-wait loop and it will not
// waste CPU resource.
thread::yield_now();
}
}

backoff_cnt = if has_made_progress.get() {
0
} else {
backoff_cnt + 1
};
}
}
Loading