Files
next.js/turbopack/crates/turbo-tasks-backend/tests/transient_collectible.rs
Benjamin WoodruffandGitHub 85c3f2e7d7 Turbopack: bincode: Migrate TaskInput serialization to bincode (#86631)
https://github.com/vercel/next.js/pull/86338 switches cell storage to use bincode, but it uses a compatibility shim for storing `TaskInput`s using `serde` + `pot`. This PR completes the migration by switching `TaskInput` to use `bincode` as well.

## Benchmark Summary

`next build` is consistently faster across a variety of repositories when persistent caching is enabled, and the resulting cache size is consistently smaller.

![Screenshot 2025-12-01 at 9.07.20 PM.png](https://app.graphite.com/user-attachments/assets/2e19f83a-5810-4c55-b67a-aa0dd1c5da22.png)

`cargo build` runs only slightly faster, but there's potential for improving this further by removing unused `serde` macro derives.

## Benchmarking Notes

Performance measurements (aside from cargo build) are taken using `next build`​. This should have proportional benefits for `next dev`​ too, but `next build`​ is more reproducible and easier to measure. `canary` refers to `a8f7b5467485647def4c0f4479a0a83bc4a3673a`.

These numbers are taken with persistent caching enabled with an empty cache, so it's the worst-case scenario for the serialization codepath. We don't care nearly as much about the performance of deserialization because it's less common.

## `cargo build -p next-swc-napi`

This makes builds _slightly_ faster. This branch doesn't clean up the serde derives yet, so there's a bunch of unused macro expansion here, so I think this number is actually pretty good.

```
~/next.js $ hyperfine -w 1 -r 5 -p 'git co a8f7b54674 && cargo clean' -p 'git co bgw/bincode-task-inputs && cargo clean' 'cargo build -p next-swc-napi' 'cargo build -p next-swc-napi'
Benchmark 1: cargo build -p next-swc-napi
  Time (mean ± σ):     237.476 s ±  1.420 s    [User: 2001.820 s, System: 84.957 s]
  Range (min … max):   235.294 s … 238.862 s    5 runs

Benchmark 2: cargo build -p next-swc-napi
  Time (mean ± σ):     232.366 s ±  1.549 s    [User: 1945.355 s, System: 83.940 s]
  Range (min … max):   230.550 s … 234.485 s    5 runs

Summary
  cargo build -p next-swc-napi ran
    1.02 ± 0.01 times faster than cargo build -p next-swc-napi
```

## next-site

Using `~/front/apps/next-site` on macos with a M2 Pro. Nothing else was running on the machine.

```
hyperfine -p 'rm -rf .next' -w 2 -r 20 'TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile'
du -sh .next/cache/turbopack/
du -s .next/cache/turbopack/
```

#### Canary

```
Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     14.999 s ±  0.258 s    [User: 84.137 s, System: 8.683 s]
  Range (min … max):   14.400 s … 15.509 s    20 runs
```

```
855M    .next/cache/turbopack/
1751640 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)
```

#### This Branch

```
Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     14.106 s ±  0.255 s    [User: 79.976 s, System: 8.421 s]
  Range (min … max):   13.829 s … 14.939 s    20 runs
```

```
767M    .next/cache/turbopack/
1571072 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)
```

## vercel-site

Using `~/front/apps/vercel-site` on macos with a M2 Pro. Nothing else was running on the machine.

```
hyperfine -p 'rm -rf .next' -w 2 -r 20 'TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile'
du -sh .next/cache/turbopack/
du -s .next/cache/turbopack/
```

#### Canary

```
Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     93.274 s ±  1.630 s    [User: 671.120 s, System: 57.888 s]
  Range (min … max):   91.111 s … 96.881 s    10 runs
```

```
4.3G    .next/cache/turbopack/
9002568 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)
```

#### This Branch

```
Benchmark 1: TURBOPACK_PERSISTENT_CACHE=1 TURBO_ENGINE_IGNORE_DIRTY=1 pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     88.694 s ±  0.963 s    [User: 633.801 s, System: 54.576 s]
  Range (min … max):   87.357 s … 90.037 s    10 runs
```

```
3.7G    .next/cache/turbopack/
7788472 .next/cache/turbopack/    (Note: macos measures size in 512B blocks)
```

## shadcn/ui

Using a low-noise Linux machine: https://github.com/bgw/benchmark-scripts/

```
diff --git a/apps/v4/next.config.mjs b/apps/v4/next.config.mjs
index 7fa0f012..d1137d01 100644
--- a/apps/v4/next.config.mjs
+++ b/apps/v4/next.config.mjs
@@ -27,6 +27,7 @@ const nextConfig = {
   },
   experimental: {
     turbopackFileSystemCacheForDev: true,
+    turbopackFileSystemCacheForBuild: true,
   },
   redirects() {
     return [
```

```
cd ~/shadcn-ui/apps/v4
hyperfine -p 'rm -rf .next' -w 2 'pnpm next build --turbopack --experimental-build-mode=compile'
du -sh .next/cache/turbopack/
du -s .next/cache/turbopack/
```

#### Canary

```
Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     62.772 s ±  0.939 s    [User: 163.153 s, System: 11.170 s]
  Range (min … max):   60.739 s … 64.220 s    10 runs
```

```
596M    .next/cache/turbopack/
609636  .next/cache/turbopack/    (Note: Linux measures size in 1024B blocks)
```

#### This Branch

```
Benchmark 1: pnpm next build --turbopack --experimental-build-mode=compile
  Time (mean ± σ):     59.017 s ±  1.014 s    [User: 151.359 s, System: 9.978 s]
  Range (min … max):   57.124 s … 60.343 s    10 runs
```

```
491M    .next/cache/turbopack/
502240  .next/cache/turbopack/    (Note: Linux measures size in 1024B blocks)
```
2025-12-05 20:33:20 +01:00

50 lines
1.5 KiB
Rust

#![feature(arbitrary_self_types)]
#![feature(arbitrary_self_types_pointers)]
use bincode::{Decode, Encode};
use turbo_tasks::{NonLocalValue, ResolvedVc, TaskInput, trace::TraceRawVcs};
use turbo_tasks_testing::{Registration, register, run_once_without_cache_check};
static REGISTRATION: Registration = register!();
const EXPECTED_MSG: &str =
"Collectible is transient, transient collectibles cannot be emitted from persistent tasks";
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_transient_emit_from_persistent() {
let result = run_once_without_cache_check(&REGISTRATION, async {
emit_incorrect_task_input_operation(IncorrectTaskInput(U32Wrapper(123).resolved_cell()))
.read_strongly_consistent()
.await?;
anyhow::Ok(())
})
.await;
let message = format!("{:#}", result.unwrap_err());
assert!(message.contains(&EXPECTED_MSG.to_string()));
}
#[turbo_tasks::function(operation)]
fn emit_incorrect_task_input_operation(value: IncorrectTaskInput) {
turbo_tasks::emit(ResolvedVc::upcast::<Box<dyn Number>>(value.0));
}
/// Has an intentionally incorrect `TaskInput` implementation
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode, NonLocalValue)]
struct IncorrectTaskInput(ResolvedVc<U32Wrapper>);
impl TaskInput for IncorrectTaskInput {
fn is_transient(&self) -> bool {
false
}
}
#[turbo_tasks::value_trait]
trait Number {}
#[turbo_tasks::value]
struct U32Wrapper(u32);
#[turbo_tasks::value_impl]
impl Number for U32Wrapper {}