Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions benchmarkFixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,18 +171,31 @@ async function writeRegistryConfig (pm, cwd, opts) {
* directory the benchmark doesn't control. When the scenario offloads
* resolution, `pnprServer` names the server it happens on.
*
* `minimumReleaseAge` is deliberately left at pnpm's default here, unlike
* the directories the managers are installed into. The hold — and the
* lockfile verification pass it drives — is pnpm's default behavior, so it
* belongs in the measurement; the client sends the setting along with its
* resolve and verify-lockfile requests, so the server applies the same
* cutoff and both pnpm columns still install the same graph. Setting it to
* zero would leave no verifier configured at all, silently deleting the
* verification work whose offload to the server the accelerated column
* exists to measure.
* `minimumReleaseAge` is deliberately left at pnpm's default, unlike the
* directories the managers are installed into. The hold is pnpm's default
* behavior and it shapes what resolution costs, which is the thing the rows
* without a lockfile measure; the client sends the setting along with its
* resolve request, so the server applies the same cutoff and both pnpm
* columns still install the same graph.
*
* `trustLockfile` turns off the other half of that policy: the supply-chain
* pass that re-checks every entry of a lockfile it is about to install. That
* pass is not free and it is not comparable — it fetches a packument per
* package, measured at 1158 requests on top of the 1346 tarball fetches this
* fixture needs, and none of npm, Yarn or Bun does anything of the sort, so
* the rows with an up-to-date lockfile were timing pnpm doing work no other
* column was asked to do. The rows say `trusted lockfile` for that reason.
* pnpm 11 honors the setting too, so all three pnpm columns skip the same
* pass and the version comparison stays a comparison.
*
* It costs the accelerated column its lockfile rows, which is the trade
* being made knowingly: pnpr answers that pass in one request where the
* client spends a round trip per package, and with nothing to verify the two
* pnpm 12 columns now really do measure the same install there — which is
* what the page has always claimed of them.
*/
export function pnpmWorkspaceYaml (opts = {}) {
let yaml = "packages:\n - '.'\n"
let yaml = "packages:\n - '.'\ntrustLockfile: true\n"
if (opts.pnprServer) {
yaml += `pnprServer: ${opts.pnprServer}\n`
}
Expand Down
23 changes: 12 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ const tests = [
const testDescriptions = {
firstInstall: ['clean'],
withWarmModules: ['node_modules'],
withLockfile: ['lockfile'],
withLockfile: ['trusted lockfile'],
withWarmCacheAndModules: ['cache', 'node_modules'],
withWarmCache: ['cache'],
withWarmCacheAndLockfile: ['cache', 'lockfile'],
withWarmModulesAndLockfile: ['lockfile', 'node_modules'],
repeatInstall: ['cache', 'lockfile', 'node_modules'],
withWarmCacheAndLockfile: ['cache', 'trusted lockfile'],
withWarmModulesAndLockfile: ['trusted lockfile', 'node_modules'],
repeatInstall: ['cache', 'trusted lockfile', 'node_modules'],
updatedDependencies: ['update'],
}

Expand All @@ -113,12 +113,12 @@ const tableRows = [
const explanationByTest = {
firstInstall: '`clean`: a brand-new clone — nothing cached, no lockfile, no `node_modules`.',
withWarmModules: '`node_modules`: the cache and lockfile are deleted and install is run again.',
withLockfile: '`lockfile`: a CI server doing its first install.',
withLockfile: '`trusted lockfile`: a CI server doing its first install.',
withWarmCacheAndModules: '`cache+node_modules`: the lockfile is deleted and install is run again.',
withWarmCache: '`cache`: a developer reinstalling without a lockfile.',
withWarmCacheAndLockfile: '`cache+lockfile`: a developer reinstalling a known project.',
withWarmModulesAndLockfile: '`lockfile+node_modules`: the cache is deleted and install is run again.',
repeatInstall: '`cache+lockfile+node_modules`: re-running install when nothing has changed.',
withWarmCacheAndLockfile: '`cache+trusted lockfile`: a developer reinstalling a known project.',
withWarmModulesAndLockfile: '`trusted lockfile+node_modules`: the cache is deleted and install is run again.',
repeatInstall: '`cache+trusted lockfile+node_modules`: re-running install when nothing has changed.',
updatedDependencies: '`update`: dependency versions are bumped in `package.json` and install is run again.',
}

Expand Down Expand Up @@ -383,13 +383,14 @@ async function writePage ({ formattedNow, registryVersion, sections, svgs, sorte
- **Every manager crosses the same link.** The round trip is applied to all of them, and to pnpm's resolution requests as well, so no client gets a cheaper connection than another. The bandwidth cap is the link's, shared across all of a manager's connections — opening more connections in parallel spreads the latency, as on a real network, but cannot multiply the ${BANDWIDTH_MBPS} Mbit/s.
- **pnpr's cache is warmed before anything is timed** — with the fixture's dependency graph and with the one the update row installs — so no manager pays to pull either into the registry on behalf of the ones measured after it.
- **Server-side resolution pays off when there is a graph to resolve.** Resolving one means walking it level by level, and each level costs a round trip, so the cost is roughly the depth of the graph times the latency. pnpr does that walk next to the registry — its own metadata access stays on loopback, the co-located shape the [pnpm monorepo's integrated benchmark](https://github.com/pnpm/pnpm) measures — and answers with the whole resolved lockfile at once, which is why the rows without a lockfile, and the row that changes dependencies, are the ones where it pulls ahead of plain pnpm.
- **The lockfile is trusted, so every manager is asked for the same work.** pnpm verifies a lockfile against the registry before installing it — a supply-chain pass that costs a packument per package, and one no other manager here performs. The rows with a lockfile run every pnpm column with [\`trustLockfile\`](https://pnpm.io/settings#trustlockfile), so what they compare is the install rather than a safety check only one participant was asked for. It is on by default outside this benchmark, and pnpm's own resolution still applies its release-age policy on the rows that resolve.
- **With an up-to-date lockfile there is nothing to resolve.** pnpm doesn't ask the server then, so those rows measure the same install in both pnpm 12 columns.
- Tarballs are still fetched by the client, in parallel and directly, on every row.
`

const explanationItems = sortedTests.map(t => `- ${explanationByTest[t]}`).join('\n ')
const explanation = stripIndents`
Each row's label lists which of \`cache\`, \`lockfile\`, and \`node_modules\` are warm/present before install runs. Quick mapping to the real world (ordered from slowest to fastest scenario):
Each row's label lists which of \`cache\`, \`trusted lockfile\`, and \`node_modules\` are warm/present before install runs. Quick mapping to the real world (ordered from slowest to fastest scenario):

${explanationItems}
`
Expand Down Expand Up @@ -470,7 +471,7 @@ async function benchmarkFixtures ({ pmCommands, formattedNow, nodeVersion, runFi
sections.push(stripIndents`
${fixture.mdDesc}

| action | cache | lockfile | node_modules| ${headerLegends} |
| action | cache | trusted lockfile | node_modules| ${headerLegends} |
| --- | --- | --- | --- | ${headerSep} |
${rows}

Expand Down Expand Up @@ -516,7 +517,7 @@ async function benchmarkFixtures ({ pmCommands, formattedNow, nodeVersion, runFi

pnpm v12 will use a new installation engine for fetching and linking written in Rust. See [pacquet](https://github.com/pnpm/pacquet).

| action | cache | lockfile | node_modules| ${pnpmHeaderLegends} |
| action | cache | trusted lockfile | node_modules| ${pnpmHeaderLegends} |
| --- | --- | --- | --- | ${pnpmHeaderSep} |
${pnpmRows}

Expand Down
8 changes: 4 additions & 4 deletions regenerate-svgs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ const tests = [
const testDescriptions = {
firstInstall: ['clean'],
withWarmModules: ['node_modules'],
withLockfile: ['lockfile'],
withLockfile: ['trusted lockfile'],
withWarmCacheAndModules: ['cache', 'node_modules'],
withWarmCache: ['cache'],
withWarmCacheAndLockfile: ['cache', 'lockfile'],
withWarmModulesAndLockfile: ['lockfile', 'node_modules'],
repeatInstall: ['cache', 'lockfile', 'node_modules'],
withWarmCacheAndLockfile: ['cache', 'trusted lockfile'],
withWarmModulesAndLockfile: ['trusted lockfile', 'node_modules'],
repeatInstall: ['cache', 'trusted lockfile', 'node_modules'],
updatedDependencies: ['update'],
}

Expand Down
150 changes: 0 additions & 150 deletions results/pnpm11/11.22.0/alotta-files-pnpr.yaml

This file was deleted.

60 changes: 0 additions & 60 deletions results/pnpm12/12.0.0-rc.6/alotta-files-pnpr.yaml

This file was deleted.

Loading