Skip to content

Commit 85c3f2e

Browse files
authored
Turbopack: bincode: Migrate TaskInput serialization to bincode (#86631)
#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 a8f7b54 && 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) ```
1 parent 6169e78 commit 85c3f2e

File tree

36 files changed

+474
-509
lines changed

36 files changed

+474
-509
lines changed

Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/next-api/src/operation.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ fn pick_route(entrypoints: OperationVc<Entrypoints>, key: RcStr, route: &Route)
123123
ValueDebugFormat,
124124
NonLocalValue,
125125
OperationValue,
126+
Encode,
127+
Decode,
126128
)]
127129
enum EndpointSelector {
128130
RoutePageHtml(RcStr),

crates/next-api/src/pages.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,18 @@ enum PageEndpointType {
612612
}
613613

614614
#[derive(
615-
Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug, TaskInput, TraceRawVcs,
615+
Copy,
616+
Clone,
617+
Serialize,
618+
Deserialize,
619+
PartialEq,
620+
Eq,
621+
Hash,
622+
Debug,
623+
TaskInput,
624+
TraceRawVcs,
625+
Encode,
626+
Decode,
616627
)]
617628
enum SsrChunkType {
618629
Page,
@@ -621,7 +632,18 @@ enum SsrChunkType {
621632
}
622633

623634
#[derive(
624-
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, TaskInput, TraceRawVcs,
635+
Copy,
636+
Clone,
637+
Debug,
638+
PartialEq,
639+
Eq,
640+
Hash,
641+
Serialize,
642+
Deserialize,
643+
TaskInput,
644+
TraceRawVcs,
645+
Encode,
646+
Decode,
625647
)]
626648
enum EmitManifests {
627649
/// Don't emit any manifests

crates/next-api/src/project.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,6 @@ pub struct ProjectOptions {
209209
pub current_node_js_version: RcStr,
210210
}
211211

212-
#[derive(
213-
Debug, Serialize, Deserialize, Clone, TaskInput, PartialEq, Eq, Hash, TraceRawVcs, NonLocalValue,
214-
)]
215-
#[serde(rename_all = "camelCase")]
216212
pub struct PartialProjectOptions {
217213
/// A root path from which all files must be nested under. Trying to access
218214
/// a file outside this root will fail. Think of this as a chroot.

crates/next-core/src/next_client/context.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::BTreeSet;
22

33
use anyhow::Result;
4+
use bincode::{Decode, Encode};
45
use serde::{Deserialize, Serialize};
56
use turbo_rcstr::{RcStr, rcstr};
67
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
@@ -32,11 +33,13 @@ use turbopack_node::{
3233
};
3334
use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
3435

35-
use super::transforms::get_next_client_transforms_rules;
3636
use crate::{
3737
mode::NextMode,
3838
next_build::get_postcss_package_mapping,
39-
next_client::runtime_entry::{RuntimeEntries, RuntimeEntry},
39+
next_client::{
40+
runtime_entry::{RuntimeEntries, RuntimeEntry},
41+
transforms::get_next_client_transforms_rules,
42+
},
4043
next_config::NextConfig,
4144
next_font::local::NextFontLocalResolvePlugin,
4245
next_import_map::{
@@ -405,7 +408,19 @@ pub async fn get_client_module_options_context(
405408
Ok(module_options_context)
406409
}
407410

408-
#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Serialize, Deserialize)]
411+
#[derive(
412+
Clone,
413+
Debug,
414+
PartialEq,
415+
Eq,
416+
Hash,
417+
TaskInput,
418+
TraceRawVcs,
419+
Serialize,
420+
Deserialize,
421+
Encode,
422+
Decode,
423+
)]
409424
pub struct ClientChunkingContextOptions {
410425
pub mode: Vc<NextMode>,
411426
pub root_path: FileSystemPath,

crates/next-core/src/next_edge/context.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anyhow::Result;
2+
use bincode::{Decode, Encode};
23
use serde::{Deserialize, Serialize};
34
use turbo_rcstr::{RcStr, rcstr};
45
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
@@ -196,7 +197,19 @@ pub async fn get_edge_resolve_options_context(
196197
.cell())
197198
}
198199

199-
#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Serialize, Deserialize)]
200+
#[derive(
201+
Clone,
202+
Debug,
203+
PartialEq,
204+
Eq,
205+
Hash,
206+
TaskInput,
207+
TraceRawVcs,
208+
Serialize,
209+
Deserialize,
210+
Encode,
211+
Decode,
212+
)]
200213
pub struct EdgeChunkingContextOptions {
201214
pub mode: Vc<NextMode>,
202215
pub root_path: FileSystemPath,

crates/next-core/src/next_root_params/mod.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use either::Either;
55
use indoc::formatdoc;
66
use itertools::Itertools;
77
use turbo_rcstr::RcStr;
8-
use turbo_tasks::{ResolvedVc, Vc};
8+
use turbo_tasks::{EitherTaskInput, ResolvedVc, Vc};
99
use turbo_tasks_fs::{FileContent, FileSystemPath};
1010
use turbopack_core::{
1111
asset::AssetContent,
@@ -36,17 +36,21 @@ pub async fn insert_next_root_params_mapping(
3636
) -> Result<()> {
3737
import_map.insert_exact_alias(
3838
"next/root-params",
39-
get_next_root_params_mapping(is_root_params_enabled, ty, collected_root_params)
40-
.to_resolved()
41-
.await?,
39+
get_next_root_params_mapping(
40+
is_root_params_enabled,
41+
EitherTaskInput(ty),
42+
collected_root_params,
43+
)
44+
.to_resolved()
45+
.await?,
4246
);
4347
Ok(())
4448
}
4549

4650
#[turbo_tasks::function]
4751
async fn get_next_root_params_mapping(
4852
is_root_params_enabled: Vc<bool>,
49-
ty: Either<ServerContextType, ClientContextType>,
53+
ty: EitherTaskInput<ServerContextType, ClientContextType>,
5054
collected_root_params: Option<Vc<CollectedRootParams>>,
5155
) -> Result<Vc<ImportMapping>> {
5256
// This mapping goes into the global resolve options, so we want to avoid invalidating it if
@@ -77,12 +81,12 @@ impl NextRootParamsMapper {
7781
#[turbo_tasks::function]
7882
pub fn new(
7983
is_root_params_enabled: ResolvedVc<bool>,
80-
context_type: Either<ServerContextType, ClientContextType>,
84+
context_type: EitherTaskInput<ServerContextType, ClientContextType>,
8185
collected_root_params: Option<ResolvedVc<CollectedRootParams>>,
8286
) -> Vc<Self> {
8387
NextRootParamsMapper {
8488
is_root_params_enabled,
85-
context_type,
89+
context_type: context_type.0,
8690
collected_root_params,
8791
}
8892
.cell()

crates/next-core/src/next_server/context.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::BTreeSet;
22

33
use anyhow::{Result, bail};
4+
use bincode::{Decode, Encode};
45
use serde::{Deserialize, Serialize};
56
use turbo_rcstr::{RcStr, rcstr};
67
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
@@ -38,18 +39,17 @@ use turbopack_node::{
3839
use turbopack_nodejs::NodeJsChunkingContext;
3940
use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
4041

41-
use super::{
42-
resolve::ExternalCjsModulesResolvePlugin,
43-
transforms::{get_next_server_internal_transforms_rules, get_next_server_transforms_rules},
44-
};
4542
use crate::{
4643
app_structure::CollectedRootParams,
4744
mode::NextMode,
4845
next_build::get_postcss_package_mapping,
4946
next_config::NextConfig,
5047
next_font::local::NextFontLocalResolvePlugin,
5148
next_import_map::{get_next_edge_and_server_fallback_import_map, get_next_server_import_map},
52-
next_server::resolve::ExternalPredicate,
49+
next_server::{
50+
resolve::{ExternalCjsModulesResolvePlugin, ExternalPredicate},
51+
transforms::{get_next_server_internal_transforms_rules, get_next_server_transforms_rules},
52+
},
5353
next_shared::{
5454
resolve::{
5555
ModuleFeatureReportResolvePlugin, NextExternalResolvePlugin,
@@ -981,7 +981,19 @@ pub async fn get_server_module_options_context(
981981
Ok(module_options_context)
982982
}
983983

984-
#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Serialize, Deserialize)]
984+
#[derive(
985+
Clone,
986+
Debug,
987+
PartialEq,
988+
Eq,
989+
Hash,
990+
TaskInput,
991+
TraceRawVcs,
992+
Serialize,
993+
Deserialize,
994+
Encode,
995+
Decode,
996+
)]
985997
pub struct ServerChunkingContextOptions {
986998
pub mode: Vc<NextMode>,
987999
pub root_path: FileSystemPath,

crates/next-core/src/util.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,18 @@ pub fn defines(define_env: &FxIndexMap<RcStr, Option<RcStr>>) -> CompileTimeDefi
5959
}
6060

6161
#[derive(
62-
Debug, Clone, Copy, PartialEq, Eq, Hash, TaskInput, Serialize, Deserialize, TraceRawVcs,
62+
Debug,
63+
Clone,
64+
Copy,
65+
PartialEq,
66+
Eq,
67+
Hash,
68+
TaskInput,
69+
Serialize,
70+
Deserialize,
71+
TraceRawVcs,
72+
Encode,
73+
Decode,
6374
)]
6475
pub enum PathType {
6576
PagesPage,

turbopack/crates/turbo-bincode/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#[doc(hidden)]
22
pub mod macro_helpers;
33

4-
use std::ptr::copy_nonoverlapping;
4+
use std::{any::Any, ptr::copy_nonoverlapping};
55

66
use ::smallvec::SmallVec;
77
use bincode::{
@@ -17,6 +17,8 @@ pub type TurboBincodeEncoder<'a> =
1717
EncoderImpl<TurboBincodeWriter<'a>, bincode::config::Configuration>;
1818
pub type TurboBincodeDecoder<'a> =
1919
DecoderImpl<TurboBincodeReader<'a>, bincode::config::Configuration, ()>;
20+
pub type AnyEncodeFn = fn(&dyn Any, &mut TurboBincodeEncoder<'_>) -> Result<(), EncodeError>;
21+
pub type AnyDecodeFn<T> = fn(&mut TurboBincodeDecoder<'_>) -> Result<T, DecodeError>;
2022

2123
fn new_turbo_bincode_encoder(buf: &mut TurboBincodeBuffer) -> TurboBincodeEncoder<'_> {
2224
EncoderImpl::new(TurboBincodeWriter::new(buf), TURBO_BINCODE_CONFIG)

0 commit comments

Comments
 (0)