Skip to content
Dashboard

Making Turborepo 96% faster with agents, sandboxes, and humans

Turborepo

Link to headingHow Turborepo schedules your tasks

Sequential vs parallel task scheduling with TurborepoSequential vs parallel task scheduling with Turborepo
Sequential vs parallel task scheduling with Turborepo

Link to headingStarting with unattended agents

Look for a performance speedup in our Rust code. It has to be something that is well-tested, and on our hot path. Make sure to add benches to check your work. I'm particularly interested in our hashing code.
Anthony Shew

Link to headingMaking profiling work for agents and humans

Link to headingMaybe Chrome Tracing JSON isn't the best format

profile.json
[
{"ph":"M","pid":1,"name":"process_name","args":{"name":"turbo 2.8.21-canary.9"}},
{"ph":"M","pid":1,"name":"process_labels","args":{"labels":"macos, 14 CPUs"}},
{"ph":"M","pid":1,"name":"thread_name","tid":0,"args":{"name":"main"}},
{"ph":"e","pid":1,"ts":8.167,"name":"enable_chrome_tracing","cat":"turborepo_lib::tracing","tid":0,"id":1,".file":"crates/turborepo-lib/src/tracing.rs",".line":325},
{"ph":"b","pid":1,"ts":52.917,"name":"shim_run","cat":"turborepo_lib::shim","tid":0,"id":2251799813685249,".file":"crates/turborepo-lib/src/shim.rs",".line":224},
{"ph":"b","pid":1,"ts":58.959,"name":"run_with_args","cat":"turborepo_shim::run","tid":0,"id":2251799813685249,".file":"crates/turborepo-shim/src/run.rs",".line":189},
{"ph":"i","pid":1,"ts":77.584,"name":"event crates/turborepo-shim/src/run.rs:223","cat":"turborepo_shim::run","tid":0,"s":"t",".file":"crates/turborepo-shim/src/run.rs",".line":223},
{"ph":"b","pid":1,"ts":78.792,"name":"repo_inference","cat":"turborepo_shim::run","tid":0,"id":2251799813685249,".file":"crates/turborepo-shim/src/run.rs",".line":251},
{"ph":"b","pid":1,"ts":88.209,"name":"infer","cat":"turborepo_repository::inference","tid":0,"id":2251799813685249,".file":"crates/turborepo-repository/src/inference.rs",".line":76},
{"ph":"i","pid":1,"ts":130.375,"name":"event crates/turborepo-repository/src/package_json.rs:166","cat":"turborepo_repository::package_json","tid":0,"s":"t",".file":"crates/turborepo-repository/src/package_json.rs",".line":166},
{"ph":"b","pid":1,"ts":456.709,"name":"parse","cat":"biome_json_parser","tid":0,"id":2251799813685249,".file":"/Users/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/biome_json_parser-0.5.7/src/lib.rs",".line":32},
{"ph":"e","pid":1,"ts":546.042,"name":"parse","cat":"biome_json_parser","tid":0,"id":2251799813685249,".file":"/Users/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/biome_json_parser-0.5.7/src/lib.rs",".line":32},
{"ph":"i","pid":1,"ts":5418.584,"name":"event crates/turborepo-repository/src/package_json.rs:166","cat":"turborepo_repository::package_json","tid":0,"s":"t",".file":"crates/turborepo-repository/src/package_json.rs",".line":166},
// Many more lines of JSON...
]

An abbreviated Chrome Tracing profile created by Turborepo

Link to headingBuilding LLM-friendly profiles

profile.md
# CPU Profile
| Duration | Spans | Functions |
| 21.6s | 871 | 97 |
**Top 10:** `visit_recv_wait` 69.8%, `put` 30.6%, `build_http_client` 0.6%, `capture_scm_state` 0.5%, `find_untracked_files` 0.2%, `repo_index_untracked_await` 0.2%, `walk_glob` 0.2%, `cache_save` 0.1%, `parse_lockfile` 0.1%, `hash_scope` 0.1%
## Hot Functions (Self Time)
| Self% | Self | Total% | Total | Function | Location |
| 69.8% | 15.1s | 69.8% | 15.1s | `visit_recv_wait` | `crates/turborepo-lib/src/task_graph/visitor/mod.rs:358` |
| 30.6% | 6.6s | 30.6% | 6.6s | `put` | `crates/turborepo-cache/src/fs.rs:196` |
| 0.6% | 127.0ms | 0.6% | 127.0ms | `build_http_client` | `crates/turborepo-api-client/src/lib.rs:623` |
| 0.5% | 109.1ms | 0.5% | 109.1ms | `capture_scm_state` | `crates/turborepo-lib/src/run/builder.rs:573` |
## Call Tree (Total Time)
| Total% | Total | Self% | Self | Function | Location |
| 69.9% | 15.1s | 0.0% | 10us | `run` | `crates/turborepo-lib/src/run/mod.rs:876` |
| 69.9% | 15.1s | 0.0% | 447us | `execute_visitor` | `crates/turborepo-lib/src/run/mod.rs:659` |
| 69.8% | 15.1s | 0.0% | 1.7ms | `visit` | `crates/turborepo-lib/src/task_graph/visitor/mod.rs:315` |
| 69.8% | 15.1s | 69.8% | 15.1s | `visit_recv_wait` | `crates/turborepo-lib/src/task_graph/visitor/mod.rs:358` |
| 30.6% | 6.6s | 0.0% | 171us | `cache worker: cache PUT` | `crates/turborepo-cache/src/async_cache.rs:80` |
| 30.6% | 6.6s | 30.6% | 6.6s | `put` | `crates/turborepo-cache/src/fs.rs:196` |
| 0.6% | 127.0ms | 0.0% | 8us | `http_client_init` | `crates/turborepo-api-client/src/shared_http_client.rs:68` |
| 0.6% | 127.0ms | 0.6% | 127.0ms | `build_http_client` | `crates/turborepo-api-client/src/lib.rs:623` |
| 0.5% | 109.1ms | 0.5% | 109.1ms | `capture_scm_state` | `crates/turborepo-lib/src/run/builder.rs:573` |

An abbreviated Markdown profile from Turborepo

Link to headingThe iterative loop

Link to headingYour source code is the best feedback loop

Link to headingHitting a wall at 85%

Link to headingVercel Sandbox for benchmarking

bench.sh
# Cross-compile Turborepo binaries for Linux on macOS using Zig
zig cc -target x86_64-linux-gnu ...
cargo build --release --target x86_64-unknown-linux-gnu
# Create a Sandbox from a snapshot with test repos pre-loaded
sandbox create --snapshot turbo-bench-snapshot
# Upload both binaries (main and branch) into the Sandbox
sandbox cp ./target/release/turbo-main sandbox:/usr/local/bin/turbo-main
sandbox cp ./target/release/turbo-branch sandbox:/usr/local/bin/turbo-branch
# Run hyperfine comparing both binaries across test repos
sandbox exec -- hyperfine \
--warmup 2 --runs 15 \
'turbo-main run build --dry' \
'turbo-branch run build --dry'
# Generate Markdown profiles for both and download reports
sandbox exec -- turbo-main run build --profile=main-profile
sandbox exec -- turbo-branch run build --profile=branch-profile
sandbox cp sandbox:/reports/ ./local-reports/

Sandbox benchmarking workflow

One caveat: Vercel Sandboxes don't guarantee dedicated hardware today. Comparing reports from different Sandbox instances might not be useful. All comparisons should come from a single instance where both binaries run under identical conditions.

Link to headingBreaking through the wall

scm/lib.rs
/// Fixed-size stack-allocated type for SHA-1 hex strings.
/// Clone is a 40-byte memcpy instead of alloc + memcpy.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct OidHash([u8; 40]);
impl OidHash {
pub fn from_hex_str(s: &str) -> Self {
let mut buf = [0u8; 40];
buf.copy_from_slice(s.as_bytes());
Self(buf)
}
}
impl std::ops::Deref for OidHash {
type Target = str;
fn deref(&self) -> &str {
// SAFETY: OidHash is always constructed from valid ASCII hex bytes.
unsafe { std::str::from_utf8_unchecked(&self.0) }
}
}

Stack-allocated OidHash type

cache/fs.rs
// Before: 3 syscalls per cache hit
let cache_path = if uncompressed_cache_path.exists() { // stat(.tar) → ENOENT
uncompressed_cache_path
} else if compressed_cache_path.exists() { // stat(.tar.zst) → OK
compressed_cache_path
};
let mut cache_reader = CacheReader::open(&cache_path)?; // open(.tar.zst)
// After: 1 syscall per cache hit
let mut cache_reader = match CacheReader::open(&cache_path) { // open(.tar.zst)
Ok(reader) => reader,
Err(CacheError::IO(ref e, _))
if e.kind() == std::io::ErrorKind::NotFound => {
return Ok(None); // cache miss
}
Err(e) => return Err(e),
};

Eliminating redundant syscalls in cache fetch

Link to headingResults

Link to headingReleased in Turborepo 2.9