Files
Luke SandbergandGitHub 723c06200d switch our test benchmark runs to release-with-assertions (#94538)
In ci when testing our benchmarks use our test profile
`release-with-assertions`

This avoids an expensive `lto` step and ensures that our tests run with
debug asserts.

I noticed that our workflow for 'test cargo benches' was very slow and a
lot of that time was the build presumably due to lto overheads across so
many benchmark binaries.


Compare:

* cargo benches for this pr:
https://github.com/vercel/next.js/runs/79974366863?pr=94538
   - build=4m42s
   - test=1m46s
* cargo benches before this pr:
https://github.com/vercel/next.js/actions/runs/27097792020/job/79973082715#logs
   - build=8m23s
   - test=6m56s

Of course doing this revealed a few things

* a birthday paradox panic in one of the persistence tests
* some 'top level read' errors to address in the benchmarks and in the
turbopack-cli server used for some benchmarks.

And finally, that the persistence tests are just extremely slow in test
mode due to their initialization overhead (populating >1GB dbs), so that
harness is rewritten to remove some variants and also to reduce max
sizes in test mode
2026-06-17 16:05:05 +00:00

158 lines
6.1 KiB
Rust

use std::{fs, path::PathBuf};
use criterion::{Bencher, BenchmarkId, Criterion};
use regex::Regex;
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{
Effects, OperationVc, TurboTasks, Vc, mark_top_level_task,
read_strongly_consistent_and_apply_effects, take_effects,
unmark_top_level_task_may_leak_eventually_consistent_state,
};
use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
use turbo_tasks_fs::{DiskFileSystem, FileSystem, NullFileSystem};
use turbopack::{
ModuleAssetContext, emit_assets_into_dir_operation,
module_options::{EcmascriptOptionsContext, ModuleOptionsContext},
};
use turbopack_core::{
compile_time_info::CompileTimeInfo,
context::AssetContext,
environment::{Environment, ExecutionEnvironment, NodeJsEnvironment},
file_source::FileSource,
ident::Layer,
rebase::RebasedAsset,
reference::all_assets_from_entry,
reference_type::ReferenceType,
};
use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
#[turbo_tasks::function(operation, root)]
async fn extract_effects_operation(op: OperationVc<()>) -> anyhow::Result<Vc<Effects>> {
let _ = op.resolve().strongly_consistent().await?;
Ok(take_effects(op).await?.cell())
}
// TODO this should move to the `node-file-trace` crate
pub fn benchmark(c: &mut Criterion) {
let bench_filter = Regex::new(r"(empty|simple|dynamic-in-package|react|whatwg-url|axios|azure-cosmos|cowsay|env-var|fast-glob)\.js$").unwrap();
let tests_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests");
let tests_dir = tests_root.join("node-file-trace/integration");
let mut group = c.benchmark_group("node-file-trace");
group.sample_size(10);
let results = fs::read_dir(tests_dir).unwrap();
for result in results {
let entry = result.unwrap();
if entry.file_type().unwrap().is_file() {
let name = entry.file_name().into_string().unwrap();
if !bench_filter.is_match(&name) {
continue;
}
let input = format!("node-file-trace/integration/{name}");
let tests_root = tests_root.to_string_lossy().to_string();
let bench_input = BenchInput { tests_root, input };
group.bench_with_input(
BenchmarkId::new("emit", &bench_input.input),
&bench_input,
bench_emit,
);
}
}
group.finish();
}
struct BenchInput {
tests_root: String,
input: String,
}
fn bench_emit(b: &mut Bencher, bench_input: &BenchInput) {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
b.to_async(rt).iter(move || {
let tt = TurboTasks::new(TurboTasksBackend::new(
BackendOptions::default(),
noop_backing_storage(),
));
let tests_root: RcStr = bench_input.tests_root.clone().into();
let input: RcStr = bench_input.input.clone().into();
async move {
tt.run_once(async move {
// This benchmark performs eventually-consistent Vc reads at the top level, which
// is fine for a throughput benchmark but trips the top-level-task assertion under
// debug-assertions otherwise. Unmark the top-level task for the reads, then re-mark
// it before `Effects::apply`, which *requires* a top-level task.
unmark_top_level_task_may_leak_eventually_consistent_state();
let input_fs = DiskFileSystem::new(rcstr!("tests"), Vc::cell(tests_root.clone()));
let input = input_fs.root().await?.join(&input)?;
let input_dir = input.parent().parent();
let output_fs: Vc<NullFileSystem> = NullFileSystem.cell();
let output_dir = output_fs.root().owned().await?;
let source = FileSource::new(input);
let compile_time_info = CompileTimeInfo::builder(
Environment::new(ExecutionEnvironment::NodeJsLambda(
NodeJsEnvironment::default().resolved_cell(),
))
.to_resolved()
.await?,
)
.cell()
.await?;
let module_asset_context = ModuleAssetContext::new(
Default::default(),
compile_time_info,
ModuleOptionsContext {
ecmascript: EcmascriptOptionsContext {
enable_types: true,
..Default::default()
},
..Default::default()
}
.cell(),
ResolveOptionsContext {
emulate_environment: Some(
compile_time_info.environment().to_resolved().await?,
),
collect_affecting_sources: true,
..Default::default()
}
.cell(),
Layer::new(rcstr!("node_file_trace")),
);
let module = module_asset_context
.process(Vc::upcast(source), ReferenceType::Undefined)
.module();
let rebased = RebasedAsset::new(module, input_dir, output_dir.clone());
let assets = all_assets_from_entry(Vc::upcast(rebased))
.to_resolved()
.await?;
// `read_strongly_consistent_and_apply_effects` applies effects, which *requires* a
// top-level task. Restore the mark we cleared above for the eventually-consistent
// reads.
mark_top_level_task();
read_strongly_consistent_and_apply_effects(
extract_effects_operation(emit_assets_into_dir_operation(assets, output_dir)),
|e| e,
)
.await?;
anyhow::Ok(())
})
.await
.unwrap();
}
})
}