Skip to content

Commit a6407af

Browse files
hi-ogawaAriPerkkio
andauthoredSep 25, 2024··
fix(benchmark): clear BenchmarkResult.samples array to reduce memory usage (#6541)
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
1 parent f131f93 commit a6407af

File tree

9 files changed

+51
-21
lines changed

9 files changed

+51
-21
lines changed
 

‎packages/vitest/src/defaults.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const benchmarkConfigDefaults: Required<
2424
exclude: defaultExclude,
2525
includeSource: [],
2626
reporters: ['default'],
27+
includeSamples: false,
2728
}
2829

2930
const defaultCoverageExcludes = [

‎packages/vitest/src/node/config/serializeConfig.ts

+3
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,8 @@ export function serializeConfig(
160160
standalone: config.standalone,
161161
printConsoleTrace:
162162
config.printConsoleTrace ?? coreConfig.printConsoleTrace,
163+
benchmark: config.benchmark && {
164+
includeSamples: config.benchmark.includeSamples,
165+
},
163166
}
164167
}

‎packages/vitest/src/node/reporters/benchmark/table/index.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,8 @@ interface FormattedBenchmarkGroup {
167167
benchmarks: FormattedBenchmarkResult[]
168168
}
169169

170-
export type FormattedBenchmarkResult = Omit<BenchmarkResult, 'samples'> & {
170+
export type FormattedBenchmarkResult = BenchmarkResult & {
171171
id: string
172-
sampleCount: number
173-
median: number
174172
}
175173

176174
function createFormattedBenchmarkReport(files: File[]) {
@@ -183,18 +181,7 @@ function createFormattedBenchmarkReport(files: File[]) {
183181
for (const t of task.tasks) {
184182
const benchmark = t.meta.benchmark && t.result?.benchmark
185183
if (benchmark) {
186-
const { samples, ...rest } = benchmark
187-
benchmarks.push({
188-
id: t.id,
189-
sampleCount: samples.length,
190-
median:
191-
samples.length % 2
192-
? samples[Math.floor(samples.length / 2)]
193-
: (samples[samples.length / 2]
194-
+ samples[samples.length / 2 - 1])
195-
/ 2,
196-
...rest,
197-
})
184+
benchmarks.push({ id: t.id, ...benchmark, samples: [] })
198185
}
199186
}
200187
if (benchmarks.length) {

‎packages/vitest/src/node/reporters/benchmark/table/tableRender.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ function renderBenchmarkItems(result: BenchmarkResult) {
6868
formatNumber(result.p995 || 0),
6969
formatNumber(result.p999 || 0),
7070
${(result.rme || 0).toFixed(2)}%`,
71-
result.samples.length.toString(),
71+
(result.sampleCount || 0).toString(),
7272
]
7373
}
7474

@@ -124,10 +124,7 @@ export function renderTree(
124124
}
125125
const baseline = options.compare?.[t.id]
126126
if (baseline) {
127-
benchMap[t.id].baseline = {
128-
...baseline,
129-
samples: Array.from({ length: baseline.sampleCount }),
130-
}
127+
benchMap[t.id].baseline = baseline
131128
}
132129
}
133130
}

‎packages/vitest/src/node/types/benchmark.ts

+7
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,11 @@ export interface BenchmarkUserOptions {
5050
* benchmark output file
5151
*/
5252
outputJson?: string
53+
54+
/**
55+
* Include `samples` array of benchmark results for API or custom reporter usages.
56+
* This is disabled by default to reduce memory usage.
57+
* @default false
58+
*/
59+
includeSamples?: boolean
5360
}

‎packages/vitest/src/runtime/config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export interface SerializedConfig {
129129
standalone: boolean
130130
logHeapUsage: boolean | undefined
131131
coverage: SerializedCoverageConfig
132+
benchmark?: {
133+
includeSamples: boolean
134+
}
132135
}
133136

134137
export interface SerializedCoverageConfig {

‎packages/vitest/src/runtime/runners/benchmark.ts

+9
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ async function runBenchmarkSuite(suite: Suite, runner: NodeBenchmarkRunner) {
7272
const taskRes = task.result!
7373
const result = benchmark.result!.benchmark!
7474
Object.assign(result, taskRes)
75+
// compute extra stats and free raw samples as early as possible
76+
const samples = result.samples
77+
result.sampleCount = samples.length
78+
result.median = samples.length % 2
79+
? samples[Math.floor(samples.length / 2)]
80+
: (samples[samples.length / 2] + samples[samples.length / 2 - 1]) / 2
81+
if (!runner.config.benchmark?.includeSamples) {
82+
result.samples.length = 0
83+
}
7584
updateTask(benchmark)
7685
},
7786
{

‎packages/vitest/src/runtime/types/benchmark.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export interface Benchmark extends Custom {
1818
export interface BenchmarkResult extends TinybenchResult {
1919
name: string
2020
rank: number
21+
sampleCount: number
22+
median: number
2123
}
2224

2325
export type BenchFunction = (this: BenchFactory) => Promise<void> | void

‎test/benchmark/test/reporter.test.ts

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, it } from 'vitest'
1+
import { assert, expect, it } from 'vitest'
22
import * as pathe from 'pathe'
33
import { runVitest } from '../../test-utils'
44

@@ -27,3 +27,24 @@ it('non-tty', async () => {
2727
`
2828
expect(lines).toMatchObject(expected.trim().split('\n').map(s => expect.stringContaining(s)))
2929
})
30+
31+
it.for([true, false])('includeSamples %s', async (includeSamples) => {
32+
const result = await runVitest(
33+
{
34+
root: pathe.join(import.meta.dirname, '../fixtures/reporter'),
35+
benchmark: { includeSamples },
36+
},
37+
['summary.bench.ts'],
38+
'benchmark',
39+
)
40+
assert(result.ctx)
41+
const allSamples = [...result.ctx.state.idMap.values()]
42+
.filter(t => t.meta.benchmark)
43+
.map(t => t.result?.benchmark?.samples)
44+
if (includeSamples) {
45+
expect(allSamples[0]).not.toEqual([])
46+
}
47+
else {
48+
expect(allSamples[0]).toEqual([])
49+
}
50+
})

0 commit comments

Comments
 (0)
Please sign in to comment.