Limit background compilation and reduce cold compile cost

This commit is contained in:
2026-11-06 21:39:22 +00:00
parent cd324678fe
commit e8732ede15
2 changed files with 6 additions and 115 deletions
+5 -110
View File
@@ -54,13 +54,10 @@ pub(super) struct Compiler {
}
impl Compiler {
pub fn new(directory: &Path) -> anyhow::Result<Arc<Self>> {
Self::with_opt(directory, wasmtime::OptLevel::Speed)
}
fn with_opt(directory: &Path, opt: wasmtime::OptLevel) -> anyhow::Result<Arc<Self>> {
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let mut config = wasmtime::Config::new();
config.epoch_interruption(true);
config.cranelift_opt_level(opt);
config.cranelift_opt_level(wasmtime::OptLevel::None);
let mut cache = wasmtime::CacheConfig::new();
cache.with_directory(directory);
config.cache(Some(wasmtime::Cache::new(cache)?));
@@ -145,6 +142,8 @@ pub(super) fn precompile(
) -> tokio::task::JoinHandle<anyhow::Result<usize>> {
tokio::spawn(async move {
let mut tasks = tokio::task::JoinSet::new();
// Leave CPU and file bandwidth for commands during startup.
let slots = Arc::new(tokio::sync::Semaphore::new(2));
for folder in ["/bin", "/usr/local/bin"] {
let entries = match root.list(folder).await {
Ok(entries) => entries,
@@ -155,7 +154,9 @@ pub(super) fn precompile(
let root = root.clone();
let compiler = compiler.clone();
let path = format!("{folder}/{}", entry.name);
let slots = slots.clone();
tasks.spawn(async move {
let _permit = slots.acquire().await?;
let file = root
.open(
&path,
@@ -190,112 +191,6 @@ mod tests {
use crate::exec::{Command, Exec, Options};
use std::{future::Future, sync::atomic::Ordering};
#[tokio::test(flavor = "multi_thread")]
#[ignore = "Temporary T15 timing probe; remove before review"]
async fn measure_world() {
let bundle = Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/userland/bundle");
let temp = tempfile::tempdir().unwrap();
for (index, script) in ["echo hi | tr a-z A-Z", "git --version", "rg --version"]
.into_iter()
.enumerate()
{
let cache = temp.path().join(format!("cache-{index}"));
let start = std::time::Instant::now();
let world = crate::world::World::open_with_userland(
&temp.path().join(format!("cold-{index}")),
Some(&bundle),
)
.unwrap();
let runtime = super::super::Runtime::new_with_cache(world, &cache).unwrap();
let mut output = |_: crate::exec::Stream, _: &[u8]| {};
assert_eq!(
runtime
.exec(
Command::Shell(script.into()),
Options::default(),
&mut output
)
.await
.unwrap(),
0
);
println!(
"profile={} state=cold command={script:?} seconds={:.6}",
if cfg!(debug_assertions) {
"debug"
} else {
"release"
},
start.elapsed().as_secs_f64()
);
let count = runtime.precompile().await.unwrap().unwrap();
println!(
"background commands={count} seconds={:.6}",
start.elapsed().as_secs_f64()
);
let start = std::time::Instant::now();
assert_eq!(
runtime
.exec(
Command::Shell(script.into()),
Options::default(),
&mut output
)
.await
.unwrap(),
0
);
println!(
"state=same-host command={script:?} seconds={:.6}",
start.elapsed().as_secs_f64()
);
drop(runtime);
let start = std::time::Instant::now();
let world = crate::world::World::open_with_userland(
&temp.path().join(format!("warm-{index}")),
Some(&bundle),
)
.unwrap();
let runtime = super::super::Runtime::new_with_cache(world, &cache).unwrap();
assert_eq!(
runtime
.exec(
Command::Shell(script.into()),
Options::default(),
&mut output
)
.await
.unwrap(),
0
);
println!(
"state=fresh-warm command={script:?} seconds={:.6}",
start.elapsed().as_secs_f64()
);
runtime.precompile().await.unwrap().unwrap();
}
}
#[test]
#[ignore = "Temporary T15 timing probe; remove before review"]
fn measure_optimizer() {
let bundle = Path::new(env!("CARGO_MANIFEST_DIR")).join("../target/userland/bundle/bin");
let temp = tempfile::tempdir().unwrap();
for opt in [wasmtime::OptLevel::Speed, wasmtime::OptLevel::None] {
let compiler = Compiler::with_opt(&temp.path().join(format!("{opt:?}")), opt).unwrap();
for name in ["sh", "tr", "git", "rg"] {
let bytes = std::fs::read(bundle.join(format!("{name}.wasm"))).unwrap();
let start = std::time::Instant::now();
compiler.compile(&bytes).unwrap();
println!(
"optimizer={opt:?} program={name} bytes={} compile_seconds={:.6}",
bytes.len(),
start.elapsed().as_secs_f64()
);
}
}
}
#[tokio::test(flavor = "multi_thread")]
async fn command_joins_precompile_even_after_waiter_cancellation() {
let temp = tempfile::tempdir().unwrap();
+1 -5
View File
@@ -167,13 +167,9 @@ impl Runtime {
entries: Mutex::new(HashMap::new()),
next: AtomicU32::new(1),
});
runtime.precompile();
cache::precompile(runtime.compiler.clone(), runtime.world.root.clone());
Ok(runtime)
}
/// Join startup precompilation of both command folders through the shared content slots.
pub fn precompile(&self) -> tokio::task::JoinHandle<anyhow::Result<usize>> {
cache::precompile(self.compiler.clone(), self.world.root.clone())
}
/// Number of distinct program contents compiled by this runtime.
pub fn compiled_programs(&self) -> usize {
self.cache.lock().unwrap().len()