Files
vercel__next.js/bench/fuzzponent/bin/fuzzponent.js
Tim Neutkens 00106b7ef4 Optimize webpack memory cache garbage collection (#54397)
## What

Adds a reworked version of webpack's [built-in memory cache garbage
collection
plugin](https://github.com/webpack/webpack/blob/853bfda35a0080605c09e1bdeb0103bcb9367a10/lib/cache/MemoryWithGcCachePlugin.js#L15)
that is more aggressive in cleaning up unused modules than the default.

The default marks 1/5th of the modules as "up for potentially being
garbage collected". The new plugin always checks all modules. In my
testing this does not cause much overhead compared to the current
approach which leverages writing to two separate maps. The change also
makes the memory cache eviction more predictable: when an item has not
been accessed for 5 compilations it is evicted from the memory cache, it
could still be in the disk cache.


In order to test this change I had to spin up the benchmarks but these
were a bit outdated so I've cleaned up the benchmark applications.

---------

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-08-23 15:46:32 +02:00

184 lines
5.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
const path = require('path')
const fs = require('fs')
const getSequenceGenerator = require('random-seed')
const generate = require('@babel/generator').default
const t = require('@babel/types')
const MIN_COMPONENT_NAME_LEN = 18
const MAX_COMPONENT_NAME_LEN = 24
const MIN_CHILDREN = 4
const MAX_CHILDREN = 80
const arrayUntil = (len) => [...Array(len)].map((_, i) => i)
const generateFunctionalComponentModule = (componentName, children = []) => {
const body = [
generateImport('React', 'react'),
...children.map((childName) => generateImport(childName, `./${childName}`)),
t.variableDeclaration('const', [
t.variableDeclarator(
t.identifier(componentName),
t.arrowFunctionExpression(
[],
t.parenthesizedExpression(
generateJSXElement(
'div',
children.map((childName) => generateJSXElement(childName))
)
)
)
),
]),
t.exportDefaultDeclaration(t.identifier(componentName)),
]
return t.program(body, [], 'module')
}
const generateJSXElement = (componentName, children = null) =>
t.JSXElement(
t.JSXOpeningElement(t.JSXIdentifier(componentName), [], !children),
children ? t.JSXClosingElement(t.JSXIdentifier(componentName)) : null,
children || [],
!children
)
const generateImport = (componentName, requireString) =>
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(componentName))],
t.stringLiteral(requireString)
)
const validFirstChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const validOtherChars = validFirstChars.toLowerCase()
function generateComponentName(seqGenerator, opts) {
const numOtherChars = seqGenerator.intBetween(opts.minLen, opts.maxLen)
const firstChar = validFirstChars[seqGenerator.range(validFirstChars.length)]
const otherChars = arrayUntil(numOtherChars).map(
() => validOtherChars[seqGenerator.range(validOtherChars.length)]
)
return `${firstChar}${otherChars.join('')}`
}
function* generateModules(name, remainingDepth, seqGenerator, opts) {
const filename = `${name}.${opts.extension}`
let ast
if (name === 'index') {
name = 'RootComponent'
}
if (remainingDepth === 0) {
ast = generateFunctionalComponentModule(name)
} else {
const numChildren = seqGenerator.intBetween(opts.minChild, opts.maxChild)
const children = arrayUntil(numChildren).map(() =>
generateComponentName(seqGenerator, opts)
)
ast = generateFunctionalComponentModule(name, children)
for (const child of children) {
yield* generateModules(child, remainingDepth - 1, seqGenerator, opts)
}
}
yield {
filename,
content: generate(ast).code,
}
}
function generateFuzzponents(outdir, seed, depth, opts) {
const seqGenerator = getSequenceGenerator(seed)
const filenames = new Set()
for (const { filename, content } of generateModules(
'index',
depth,
seqGenerator,
opts
)) {
if (filenames.has(filename)) {
throw new Error(
`Seed "${seed}" generates output with filename collisions.`
)
} else {
filenames.add(filename)
}
const fpath = path.join(outdir, filename)
fs.writeFileSync(fpath, `// ${filename}\n\n${content}`)
}
}
if (require.main === module) {
const { outdir, seed, depth, ...opts } = require('yargs')
.option('depth', {
alias: 'd',
demandOption: true,
describe: 'component hierarchy depth',
type: 'number',
})
.option('seed', {
alias: 's',
demandOption: true,
describe: 'prng seed',
type: 'number',
})
.option('outdir', {
alias: 'o',
demandOption: false,
default: process.cwd(),
describe: 'the directory where components should be written',
type: 'string',
normalize: true,
})
.option('minLen', {
demandOption: false,
default: MIN_COMPONENT_NAME_LEN,
describe: 'the smallest acceptable component name length',
type: 'number',
})
.option('maxLen', {
demandOption: false,
default: MAX_COMPONENT_NAME_LEN,
describe: 'the largest acceptable component name length',
type: 'number',
})
.option('minLen', {
demandOption: false,
default: MIN_COMPONENT_NAME_LEN,
describe: 'the smallest acceptable component name length',
type: 'number',
})
.option('maxLen', {
demandOption: false,
default: MAX_COMPONENT_NAME_LEN,
describe: 'the largest acceptable component name length',
type: 'number',
})
.option('minChild', {
demandOption: false,
default: MIN_CHILDREN,
describe: 'the smallest number of acceptable component children',
type: 'number',
})
.option('maxChild', {
demandOption: false,
default: MAX_CHILDREN,
describe: 'the largest number of acceptable component children',
type: 'number',
})
.option('extension', {
default: 'jsx',
describe: 'extension to use for generated components',
type: 'string',
}).argv
fs.mkdirSync(outdir, { recursive: true })
generateFuzzponents(outdir, seed, depth, opts)
}
module.exports = generateFuzzponents