1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-18 21:21:21 +02:00

Benchmark consistence && Allow users to select benches to run (#1765)

* Use slate rather than relative path

* Move benchmark to one dir

* Use slate-* instead of relative path

* Before and After Function

* Remove un-necessary cross-env

* Hard fix

* Lint the hard fix

* Reset memory in bench()

* Working on Benchmark Frameworks

* Rename to slate-dev-benchmark

* Add packages

* Fix prettier bug

* Benchmark framework is in working

* Do not log in test

* max times test

* mute logger in test

* add hr time

* Better support for maxTime; add support of split runs to save memory space

* Fix maxTries

* Add global.gc

* Global gc for each bench

* Better test interface

* Test max-time

* Test max-time done

* Add Benchmark among packages

* Starting to get benchmark running

* Pure Node lib

* Change babelrc for pure Node benchmark

* Moving Benchmarks

* Get benchmark and test running

* Get benchmark for slate-html-serializer

* add slate-react

* add slate/changes

* all benchmarks are converted

* Run benchmark by yarn

* Run benchmark with expose-gc

* Annotate Bench.js

* Do not bundle slate-dev-benchmark in rollup

* Add annotation

* Allow config file to enable part benchmark compare

* Add config for compare

* support compare.js

* Do not re-allocate memory; due to a large heap taken influence result

* Render with Decorations

* get active marks at range

* Fix bug in showing percents

* Fix percent showing bug

* chore: add more benches

* Better output of benchmark

* Fix linting

* decoration and normal as different benchmark test

* Fix deserialize benchmark

* README.md

* Fix Readme.md

* README.md

* block-spacing config

* safer user config loading

* use package.json to load package in test

* Consistent linting

* move components to parent directory

* Annotation styling in package

* margin line before multi-line block

* Fix naive bug

* Fix naive bug

* Fix a blank line

* only log user and hr

* Better name

* Better annotation for runBundleTasks

* Fix typo

* Better logger

* Move async to test

* Omit skip

* Only log the user space time

* Single line async sleep

* file name fix

* Fix annotation

* Better output of compare

* Remove get-characters(-at-range) benchmarks

* Restore emoji

* Capitalize types

* Remove compare to another area

* Add grep and config interface

* Linting files

* Linting benchmarks

* Linting benchmarks

* Update yarn.lock
This commit is contained in:
Jinxuan Zhu
2018-07-19 16:01:55 -04:00
committed by Ian Storm Taylor
parent 09c93a6cd4
commit 8f9bfdac2b
79 changed files with 1884 additions and 482 deletions

View File

@@ -23,7 +23,19 @@
},
"test": {
"presets": [
"env",
["env", {
"exclude": ["transform-regenerator"]
}],
"react",
"stage-0"
],
"plugins": ["transform-runtime"]
},
"benchmark": {
"presets": [
["env", {
"exclude": ["transform-regenerator"]
}],
"react",
"stage-0"
],

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@ packages/*/yarn.lock
.DS_Store
.idea/
.vscode/
# Editor files
.tern-port

View File

@@ -90,6 +90,16 @@ yarn benchmark
There will be some subtle changes in iteration speed always, but the comparison reporter will highlight any changes that seem meaningful. You can run `benchmark` multiple times to ensure the speed up persists.
### Run Selected Benchmarks
To run selected benchmarks, create `tmp/benchmark-config.js` with `module.exports.include`. For example, to run slate-core benchmarks only with `get-*`, we can create a `tmp/benchmark-config.js` as
```
module.exports.include = {
slate: /^get/
}
```
## Adding Browser Support
Slate aims to targeted all of the modern browsers, and eventually the modern mobile platforms. Right now browser support is limited to the latest versions of [Chrome](https://www.google.com/chrome/browser/desktop/), [Firefox](https://www.mozilla.org/en-US/firefox/new/), and [Safari](http://www.apple.com/safari/), but if you are interested in adding support for another modern platform, that is welcomed!

115
benchmark/compare.js Normal file
View File

@@ -0,0 +1,115 @@
/* eslint-disable no-console */
const chalk = require('chalk')
const figures = require('figures')
const emojis = require('emojis')
const { resolve } = require('path')
const baseline = require(resolve(process.cwd(), 'tmp/benchmark-baseline'))
const comparison = require(resolve(process.cwd(), 'tmp/benchmark-comparison'))
const { existsSync } = require('fs')
/**
* Constants.
*/
let THRESHOLD = 0.333
const configPath = '../../tmp/benchmark-config.js'
if (existsSync(configPath)) {
const alternative = require(configPath).THRESHOLD
if (typeof alternative === 'number' && alternative > 0) {
THRESHOLD = alternative
}
}
/**
* Print.
*/
console.log()
console.log(` benchmarks`)
baseline.forEach((suite, i) => {
console.log(` ${suite.name}`)
suite.benchmarks.forEach((base, j) => {
const compared = { user: {}, hr: {} }
for (const key of Object.keys(compared)) {
const comp = comparison[i].benchmarks[j]
if (!comp) return
const b = base.iterations / base[key] * 1000
const c = comp.iterations / comp[key] * 1000
const balancePercent =
b > c ? Math.round(Math.abs(b - c) / c * 100) : (c - b) / b * 100
const output = `${b.toFixed(2)} -> ${c.toFixed(2)} ops/sec`
compared[key].baseOutput = output
compared[key].percentOutput = `${balancePercent.toFixed(2)}% ${
c > b ? 'faster' : 'slower'
}`
compared[key].percentValue = balancePercent
compared[key].b = b
compared[key].c = c
compared[key].isFaster = c > b
if (balancePercent > 1000) {
compared[key].percentOutput += emojis.unicode(' :scream: ')
} else if (balancePercent > 100) {
if (c > b) {
compared[key].percentOutput += emojis.unicode(' :raised_hands: ')
} else {
compared[key].percentOutput += emojis.unicode(' :worried: ')
}
}
}
const { user, hr } = compared
if (
user.percentValue < THRESHOLD * 100 &&
hr.percentValue < THRESHOLD * 100
) {
console.log(
chalk.grey(
` ${figures.tick} ${base.name}: ${user.baseOutput} (${
user.percentOutput
})`
)
)
return
}
if (user.isFaster === hr.isFaster) {
if (user.isFaster) {
console.log(chalk.green(` ${figures.star} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.cross} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.questionMarkPrefix} ${base.name}:`))
console.log(` user: ${user.baseOutput} (${user.percentOutput})`)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
})
})
console.log()

39
benchmark/config.js Normal file
View File

@@ -0,0 +1,39 @@
const { resolve } = require('path')
const { existsSync } = require('fs')
const program = require('commander')
program
.option('-g, --grep []', 'Add grep pattern to filter running benchmarks')
.option('-c, --config [file]', 'Add config to filter running benchmarks')
.parse(process.argv)
const { grep } = program
if (grep) {
const pattern = new RegExp(grep)
module.exports.include = {
slate: pattern,
'slate-html-serializer': pattern,
'slate-plain-serializer': pattern,
'slate-react': pattern,
}
} else {
let { config = 'tmp/benchmark-config.js' } = program
config = resolve(config)
const userConfig = existsSync(config) ? require(config) : {}
if (userConfig.include) {
module.exports.include = userConfig.include
} else if (userConfig.default) {
module.exports.inlcude = userConfig.default
} else {
module.exports.include = {
slate: /^/,
'slate-html-serializer': /^/,
'slate-plain-serializer': /^/,
'slate-react': /^/,
}
}
}

View File

@@ -0,0 +1,57 @@
const { writeFileSync } = require('fs')
function convertRepo(report) {
const result = []
for (const name in report) {
const suite = report[name]
result.push({
name,
type: 'suite',
benchmarks: convertSuite(suite),
})
}
return result
}
function convertSuite(suite) {
const result = []
for (const name in suite) {
const bench = suite[name]
const { user, cycles } = bench
result.push({
name,
type: 'bench',
elapsed: user,
iterations: cycles,
ops: 1000 * cycles / user,
...bench,
})
}
return result
}
const IS_COMPARE = process.env.COMPARE
const filePath = IS_COMPARE
? './tmp/benchmark-comparison.json'
: './tmp/benchmark-baseline.json'
function generateReport(repo) {
repo
.run()
.then(report => {
const data = JSON.stringify(convertRepo(report))
writeFileSync(filePath, data)
return report
})
.then(report => {
if (IS_COMPARE) {
require('./compare')
}
})
}
module.exports = { generateReport }

45
benchmark/helpers/h.js Normal file
View File

@@ -0,0 +1,45 @@
/* eslint-disable import/no-extraneous-dependencies */
const { createHyperscript } = require('slate-hyperscript')
/**
* Define a hyperscript.
*
* @type {Function}
*/
const h = createHyperscript({
blocks: {
line: 'line',
paragraph: 'paragraph',
quote: 'quote',
code: 'code',
list: 'list',
item: 'item',
image: {
type: 'image',
isVoid: true,
},
},
inlines: {
link: 'link',
hashtag: 'hashtag',
comment: 'comment',
emoji: {
type: 'emoji',
isVoid: true,
},
},
marks: {
b: 'bold',
i: 'italic',
u: 'underline',
},
})
/**
* Export.
*
* @type {Function}
*/
module.exports = h

20
benchmark/index.js Normal file
View File

@@ -0,0 +1,20 @@
const { repo } = require('slate-dev-benchmark')
const { resolve } = require('path')
const { readdirSync } = require('fs')
const { generateReport } = require('./generate-report')
const { include } = require('./config')
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c.match(/^slate/)
)
categories.forEach(dir => {
if (include && include[dir]) {
const { run } = require(`./${dir}`)
run(include[dir])
}
})
generateReport(repo)

View File

@@ -0,0 +1,58 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const Html = require('slate-html-serializer').default
const { JSDOM } = require('jsdom') // eslint-disable-line import/no-extraneous-dependencies
const html = new Html({
parseHtml: JSDOM.fragment,
rules: [
{
deserialize(el, next) {
switch (el.tagName.toLowerCase()) {
case 'blockquote':
return {
object: 'block',
type: 'quote',
nodes: next(el.childNodes),
}
case 'p': {
return {
object: 'block',
type: 'paragraph',
nodes: next(el.childNodes),
}
}
case 'strong': {
return {
object: 'mark',
type: 'bold',
nodes: next(el.childNodes),
}
}
case 'em': {
return {
object: 'mark',
type: 'italic',
nodes: next(el.childNodes),
}
}
}
},
},
],
})
module.exports.default = function(string) {
html.deserialize(string)
}
module.exports.input = `
<blockquote>
<p>
This is editable <strong>rich</strong> text, <em>much</em> better than a textarea!
</p>
</blockquote>
`
.trim()
.repeat(10)

View File

@@ -1,10 +1,10 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import Html from '../..'
import React from 'react'
import h from '../../test/helpers/h'
import { JSDOM } from 'jsdom' // eslint-disable-line import/no-extraneous-dependencies
const Html = require('slate-html-serializer').default
const React = require('react')
const h = require('../../helpers/h')
const { JSDOM } = require('jsdom') // eslint-disable-line import/no-extraneous-dependencies
const html = new Html({
parseHtml: JSDOM.fragment,
@@ -34,11 +34,11 @@ const html = new Html({
],
})
export default function(state) {
module.exports.default = function(state) {
html.serialize(state)
}
export const input = (
module.exports.input = (
<value>
<document>
{Array.from(Array(10)).map(() => (

View File

@@ -0,0 +1,37 @@
const { readdirSync } = require('fs')
const { basename, extname, resolve } = require('path')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, { minTries: 100, minTime: 1000 })
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(() => module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -0,0 +1,40 @@
const { basename, extname, resolve } = require('path')
const { readdirSync } = require('fs')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, {
minTries: 100,
minTime: 1000,
})
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(() => module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -1,14 +1,16 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import Plain from '../..'
const Plain = require('slate-plain-serializer').default
export default function(string) {
Plain.deserialize(string)
}
export const input = `
const input = `
This is editable plain text, just like a text area.
`
.trim()
.repeat(10)
module.exports.input = input
module.exports.default = function(string) {
Plain.deserialize(string)
}

View File

@@ -1,14 +1,14 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import Plain from '../..'
import h from '../../test/helpers/h'
const Plain = require('slate-plain-serializer').default
const h = require('../../helpers/h')
export default function(state) {
module.exports.default = function(state) {
Plain.serialize(state)
}
export const input = (
module.exports.input = (
<value>
<document>
{Array.from(Array(10)).map(() => (

View File

@@ -0,0 +1,40 @@
const { basename, extname, resolve } = require('path')
const { readdirSync } = require('fs')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, {
minTries: 100,
minTime: 1000,
})
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(() => module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -0,0 +1,46 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const React = require('react')
const ReactDOM = require('react-dom/server')
const h = require('../../helpers/h')
const { Editor } = require('slate-react')
module.exports.default = function(value) {
const el = React.createElement(Editor, { value })
ReactDOM.renderToStaticMarkup(el)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const texts = value.document.getTexts()
const decorations = texts.flatMap((t, index) => {
if (index % 4 !== 0) return []
if (t.length === 0) return []
return [
{
anchorKey: t.key,
anchorOffset: 0,
focusKey: t.key,
focusOffset: 1,
marks: [{ type: 'underline' }],
},
]
})
module.exports.input = value.change().setValue({ decorations }).value

View File

@@ -1,17 +1,17 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import React from 'react'
import ReactDOM from 'react-dom/server'
import h from '../../test/helpers/h'
import { Editor } from '../..'
const React = require('react')
const ReactDOM = require('react-dom/server')
const h = require('../../helpers/h')
const { Editor } = require('slate-react')
export default function(value) {
module.exports.default = function(value) {
const el = React.createElement(Editor, { value })
ReactDOM.renderToStaticMarkup(el)
}
export const input = (
module.exports.input = (
<value>
<document>
{Array.from(Array(10)).map(() => (

View File

@@ -1,18 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(change) {
module.exports.default = function(change) {
change.deleteBackward()
}
export function before(value) {
const change = value.change()
return change
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document>
</value>
)
module.exports.input = () => {
return value.change()
}

View File

@@ -1,18 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(change) {
module.exports.default = function(change) {
change.deleteForward()
}
export function before(value) {
const change = value.change()
return change
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document>
</value>
)
module.exports.input = () => {
return value.change()
}

View File

@@ -0,0 +1,32 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ change, block }) {
change.insertNodeByKey(block.key, 0, <paragraph>Hello world</paragraph>)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
{i == 0 ? <cursor /> : ''}
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const block = value.document.getBlocks().last()
module.exports.input = function() {
const change = value.change()
return { change, block }
}

View File

@@ -1,26 +1,15 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function({ change, keys }) {
module.exports.default = function({ change, keys }) {
for (const key of keys) {
change.insertTextByKey(key, 0, 'a')
}
}
export function before(value) {
const change = value.change()
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
resetMemoization()
return { change, keys }
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -37,3 +26,12 @@ export const input = (
</document>
</value>
)
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
module.exports.input = function() {
const change = value.change()
return { change, keys }
}

View File

@@ -1,21 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function({ change, text }) {
module.exports.default = function({ change, text }) {
change.insertTextByKey(text.key, 0, 'a')
}
export function before(value) {
const change = value.change()
const text = value.document.getLastText()
resetMemoization()
return { change, text }
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -32,3 +24,9 @@ export const input = (
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
const change = value.change()
return { change, text }
}

View File

@@ -1,18 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(change) {
module.exports.default = function(change) {
change.insertText('a')
}
export function before(value) {
const change = value.change()
return change
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value.change()
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(change) {
module.exports.default = function(change) {
change.normalize()
}
export function before(value) {
return value.change()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -28,3 +24,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value.change()
}

View File

@@ -0,0 +1,32 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ change, text }) {
change.removeNodeByKey(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
{i == 0 ? <cursor /> : ''}
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
const change = value.change()
return { change, text }
}

View File

@@ -1,18 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(change) {
module.exports.default = function(change) {
change.splitBlock()
}
export function before(value) {
const change = value.change()
return change
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map((v, i) => (
@@ -29,3 +24,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value.change()
}

37
benchmark/slate/index.js Normal file
View File

@@ -0,0 +1,37 @@
const { readdirSync } = require('fs')
const { basename, extname, resolve } = require('path')
const { resetMemoization } = require('slate')
const { Suite, Bench } = require('slate-dev-benchmark')
/**
* Benchmarks.
*/
module.exports.run = function(include) {
const categoryDir = resolve(__dirname)
const categories = readdirSync(categoryDir).filter(
c => c[0] != '.' && c != 'index.js'
)
categories.forEach(category => {
const suite = new Suite(category, { minTries: 100, minTime: 1000 })
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
if (include && !benchmark.match(include)) return
const bench = new Bench(suite, benchmark)
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
bench.input(module.input)
bench.run(input => {
fn(input)
resetMemoization()
})
})
})
}

View File

@@ -1,12 +1,12 @@
/* eslint-disable react/jsx-key */
import { Value } from '../..'
const { Value } = require('slate')
export default function(json) {
module.exports.default = function(json) {
Value.fromJSON(json)
}
export const input = {
const input = {
document: {
nodes: Array.from(Array(100)).map(() => ({
type: 'list',
@@ -64,3 +64,7 @@ export const input = {
})),
},
}
module.exports.input = function() {
return input
}

View File

@@ -1,12 +1,12 @@
/* eslint-disable react/jsx-key */
import { Value } from '../..'
const { Value } = require('slate')
export default function(json) {
module.exports.default = function(json) {
Value.fromJSON(json)
}
export const input = {
const input = {
document: {
nodes: Array.from(Array(10)).map(() => ({
object: 'block',
@@ -44,3 +44,7 @@ export const input = {
})),
},
}
module.exports.input = function() {
return input
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
value.document.getCharactersAtRange(value.selection)
module.exports.default = function(value) {
value.document.getActiveMarksAtRange(value.selection)
}
export function before(value) {
return value.change().selectAll().value
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document>
</value>
)
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getAncestors(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getBlocksAtRange(value.selection)
}
export function before(value) {
return value.change().selectAll().value
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document>
</value>
)
.change()
.selectAll().value
module.exports.input = () => {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getBlocks()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value
}

View File

@@ -1,20 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function(text) {
text.getLeaves()
module.exports.default = function({ value, first, last }) {
value.document.getCommonAncestor(first.key, last.key)
}
export function before(value) {
const text = value.document.getFirstText()
resetMemoization()
return text
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -30,3 +23,10 @@ export const input = (
</document>
</value>
)
const first = value.document.getFirstText()
const last = value.document.getLastText()
module.exports.input = function() {
return { value, first, last }
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getFurthestAncestor(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getInlinesAtRange(value.selection)
}
export function before(value) {
return value.change().selectAll().value
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document>
</value>
)
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getInlines()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
value.document.getCharacters()
module.exports.default = function(text) {
text.getLeaves()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -23,3 +23,8 @@ export const input = (
</document>
</value>
)
const text = value.document.getFirstText()
module.exports.input = function() {
return text
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getMarksAtRange(value.selection)
}
export function before(value) {
return value.change().selectAll().value
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document>
</value>
)
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getMarks()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getParent(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = () => {
return { value, text }
}

View File

@@ -1,20 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function({ value, text }) {
module.exports.default = function({ value, text }) {
value.document.getPath(text.key)
}
export function before(value) {
const text = value.document.getLastText()
resetMemoization()
return { value, text }
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -30,3 +23,8 @@ export const input = (
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = () => {
return { value, text }
}

View File

@@ -1,17 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getTextsAtRange(value.selection)
}
export function before(value) {
return value.change().selectAll().value
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -27,3 +23,9 @@ export const input = (
</document>
</value>
)
.change()
.selectAll().value
module.exports.input = function() {
return value
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.document.getTexts()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value
}

View File

@@ -0,0 +1,30 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
const h = require('../../helpers/h')
module.exports.default = function({ value, text }) {
value.document.getDescendant(text.key)
}
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
<quote>
<paragraph>
<paragraph>
This is editable <b>rich</b> text, <i>much</i> better than a
textarea!
</paragraph>
</paragraph>
</quote>
))}
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,25 +1,15 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function({ value, keys }) {
module.exports.default = function({ value, keys }) {
keys.forEach(key => {
value.document.hasNode(key)
})
}
export function before(value) {
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
resetMemoization()
return { value, keys }
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -35,3 +25,11 @@ export const input = (
</document>
</value>
)
const keys = value.document
.getTexts()
.toArray()
.map(t => t.key)
module.exports.input = function() {
return { value, keys }
}

View File

@@ -1,20 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function({ value, text }) {
module.exports.default = function({ value, text }) {
value.document.hasNode(text.key)
}
export function before(value) {
const text = value.document.getLastText()
resetMemoization()
return { value, text }
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -30,3 +23,8 @@ export const input = (
</document>
</value>
)
const text = value.document.getLastText()
module.exports.input = function() {
return { value, text }
}

View File

@@ -1,13 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
const h = require('../../helpers/h')
export default function(value) {
module.exports.default = function(value) {
value.toJSON()
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -23,3 +23,7 @@ export const input = (
</document>
</value>
)
module.exports.input = function() {
return value
}

View File

@@ -1,23 +1,13 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import h from '../../test/helpers/h'
import { resetMemoization } from '../..'
const h = require('../../helpers/h')
export default function({ value, next }) {
module.exports.default = function({ value, next }) {
value.document.updateNode(next)
}
export function before(value) {
const texts = value.document.getTexts()
const { size } = texts
const text = texts.get(Math.round(size / 2))
const next = text.insertText(0, 'some text')
resetMemoization()
return { value, next }
}
export const input = (
const value = (
<value>
<document>
{Array.from(Array(10)).map(() => (
@@ -33,3 +23,12 @@ export const input = (
</document>
</value>
)
const texts = value.document.getTexts()
const { size } = texts
const text = texts.get(Math.round(size / 2))
const next = text.insertText(0, 'some text')
module.exports.input = function() {
return { value, next }
}

View File

@@ -16,17 +16,20 @@
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0",
"chalk": "^1.1.3",
"commander": "^2.15.1",
"copy-webpack-plugin": "^4.4.1",
"cross-env": "^5.1.3",
"css-loader": "^0.28.9",
"emotion": "^9.2.4",
"eslint": "^4.19.1",
"emojis": "^1.0.10",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-prettier": "^2.5.0",
"eslint-plugin-react": "^7.6.0",
"extract-text-webpack-plugin": "^3.0.2",
"faker": "^3.1.0",
"figures": "^2.0.0",
"fs-promise": "^1.0.0",
"gh-pages": "^0.11.0",
"html-webpack-plugin": "^2.30.1",
@@ -62,6 +65,7 @@
"rollup-plugin-sourcemaps": "^0.4.2",
"rollup-plugin-uglify": "^3.0.0",
"slate-collapse-on-escape": "^0.6.0",
"slate-dev-benchmark": "*",
"slate-soft-break": "^0.6.0",
"source-map-loader": "^0.2.3",
"source-map-support": "^0.4.0",
@@ -79,13 +83,15 @@
"slate-schema-violations": "*"
},
"scripts": {
"benchmark": "mkdir -p ./tmp && cross-env BABEL_ENV=test babel-node ./node_modules/.bin/_matcha --reporter ./support/benchmark/reporter ./packages/*/benchmark/index.js > ./tmp/benchmark-comparison.json && cross-env BABEL_ENV=test babel-node ./support/benchmark/compare",
"benchmark:save": "mkdir -p ./tmp && cross-env BABEL_ENV=test babel-node ./node_modules/.bin/_matcha --reporter ./support/benchmark/reporter ./packages/*/benchmark/index.js > ./tmp/benchmark-baseline.json",
"benchmark": "cross-env COMPARE=compare node --expose-gc ./tmp/benchmark/index.js",
"benchmark:save": " yarn benchmark:prepare && node --expose-gc ./tmp/benchmark/index.js",
"benchmark:prepare": "mkdir -p ./tmp && cross-env BABEL_ENV=benchmark babel benchmark --out-dir tmp/benchmark/",
"bootstrap": "lerna bootstrap && yarn build",
"build": "rollup --config ./support/rollup/config.js",
"build:production": "cross-env NODE_ENV=production rollup --config ./support/rollup/config.js && cross-env NODE_ENV=production webpack --config support/webpack/config.js",
"clean": "lerna run clean && rm -rf ./node_modules ./dist ./build",
"gh-pages": "gh-pages --dist ./build",
"lint": "eslint packages/*/src packages/*/test examples/*/*.js examples/dev/*/*.js && prettier --list-different '**/*.{js,jsx,md,json,css}'",
"lint": "eslint benchmark packages/*/src packages/*/test examples/*/*.js examples/dev/*/*.js && prettier --list-different '**/*.{js,jsx,md,json,css}'",
"open": "open http://localhost:8080",
"prettier": "prettier --write '**/*.{js,jsx,md,json,css}'",
"release": "yarn build:production && yarn test && yarn lint && lerna publish && yarn gh-pages",

View File

@@ -0,0 +1 @@
This package contains 'Suite' and 'Bench' that Slate uses to compare computation efficiency under nodeJS.

View File

@@ -0,0 +1,15 @@
{
"name": "slate-dev-benchmark",
"description": "A simple, development-only benchmark for Slate",
"version": "0.0.1",
"license": "MIT",
"repository": "git://github.com/ianstormtaylor/slate.git",
"main": "src/index.js",
"devDependencies": {
"mocha": "^2.5.3"
},
"scripts": {
"clean": "rm -rf ./dist ./lib ./node_modules"
},
"keywords": ["slate"]
}

View File

@@ -0,0 +1,227 @@
/* global Promise */
const { BenchType } = require('./types')
const { makeOptions } = require('./makeOptions')
const { Timer } = require('./Timer')
const { logger } = require('./logger')
const errorReport = {
cycles: NaN,
user: NaN,
system: NaN,
all: NaN,
}
/**
* Run a task and calculate the time consuming of tasks
*/
class Bench {
/**
* Construct a bench and register it to a Suite
* @param {Suite} suite
* @param {string} name
* @param {Object} options
*/
constructor(suite, name, options = {}) {
this.name = name
this.options = makeOptions({ ...suite.options, ...options })
this.isFinished = false
this.inputer = () => undefined
this.runner = () => {}
this.report = { ...errorReport }
suite.addBench(this)
}
/**
* Is a Bench?
* @param {any} obj
* @return {boolean}
*/
isBench(obj) {
return obj && obj[BenchType]
}
/**
* Set the method to generate (different} inputs for each run
* @param {Array|Function|Scalar} inputer
* @return {void}
*/
input(inputer) {
if (Array.isArray(inputer)) {
this.inputer = index => inputer[index % inputer.length]
return
}
if (typeof inputer === 'function') {
this.inputer = inputer
return
}
this.inputer = () => inputer
}
/**
* Set the task runner
* @param {Function} runner
* @return {void}
*/
run(runner) {
this.runner = runner
}
/**
* Tries to run tasks in `times`, if the time consuming excedes the max-time, then stop;
* After run, generate report and return
* If initial is the initial index to run the task, for continueing a task in adaptive mode
* @param {number} times
* @param {number} initial
*/
async compose(times, initial) {
times = Math.floor(times)
const isAsync = this.options.async
const { runner, inputer } = this
const { maxTime } = this.options
let seq = Number.isFinite(this.options.maxTries) ? 1 : NaN
let nextCheckIndex = seq
const hrStart = process.hrtime()
if (global.gc) {
global.gc()
}
const report = { user: 0, system: 0, all: 0, hr: 0, cycles: 0 }
for (
let initialIndex = initial;
initialIndex < times;
initialIndex += this.options.allocationTries
) {
const tries = Math.min(times - initialIndex, this.options.allocationTries)
const thisTryReport = await runBundleTasks.call(this, tries, initialIndex)
if (global.gc) {
global.gc()
}
for (const key in report) {
report[key] += thisTryReport[key]
}
}
return report
/**
* Run a bundle of tasks;
* the Bench estimate the time consuming of every `tries` tasks, then explictly run gc, and caculate the time consuming of next bundle tasks
* @param {number} tries
* @param {number} initialIndex
* @return {Promise< Object , *>}
*/
function runBundleTasks(tries, initialIndex) {
const inputs = Array.from({ length: tries }).map(index =>
inputer(index + initialIndex)
)
const timer = new Timer()
timer.start()
return runFrom(0).then(cycles => {
timer.end()
const { elapsed } = timer
return { ...elapsed, cycles }
})
/**
* Run a single task run; If the task is end, return a Promise with the index when the task ends
* @param {number} index
* @return {Promise<number, *>}
*/
function runFrom(index) {
if (index === tries) return Promise.resolve(tries)
if (index === nextCheckIndex) {
const hrEnd = process.hrtime(hrStart)
const elapsed = hrEnd[0] * 1e3 + hrEnd[1] / 1e6
if (elapsed > maxTime) {
return Promise.resolve(index)
} else {
if (elapsed < maxTime / 20) {
seq *= 2
}
nextCheckIndex = seq + nextCheckIndex
}
}
if (!isAsync) {
const inputVar = inputs[index]
runner(inputVar)
return runFrom(index + 1)
} else {
return Promise.resolve(runner(inputs[index])).then(() =>
runFrom(index + 1)
)
}
}
}
}
/*
* Run the bench
* @return {void}
*/
makeRun() {
if (this.isFinished) return true
logger(this)
const { options } = this
const { minTries, maxTime, maxTries } = options
let { minTime } = options
if (minTime > maxTime) minTime = maxTime
return this.compose(minTries, 0)
.then(report => {
if (this.options.mode === 'static') return report
const { all } = report
if (all > minTime) return report
const times = (minTime / all - 1) * minTries
return this.compose(Math.min(times, maxTries), minTries).then(
newReport => {
return mergeResults(report, newReport)
}
)
})
.then(report => {
this.report = report
this.isFinished = true
logger(this)
return true
})
}
}
Bench.prototype[BenchType] = true
/*
* Merge two different report
* @param {Object} res1
* @param {Object} res2
*/
function mergeResults(res1, res2) {
const result = {}
for (const key in res1) {
result[key] = res1[key] + res2[key]
}
return result
}
module.exports = { Bench }

View File

@@ -0,0 +1,73 @@
/* global Promise */
const { RepositoryType } = require('./types')
const { logger } = require('./logger')
const { compose } = require('./compose')
/**
* Repository Class for holding Suite
*/
class Repository {
/**
* Construct a Repository with a name
* @param {string} name
*/
constructor(name = 'default') {
this.name = name
this.suites = []
this.report = {}
this.isFinished = false
}
/**
* Check whether {obj} is repository
* @param {any} obj
* @return {boolean}
*/
isRepository(obj) {
return obj && obj[RepositoryType]
}
/**
* Register a suite to the repository
* @param {Suite} suite
* @return {void}
*/
addSuite(suite) {
this.isFinished = false
this.suites.push(suite)
}
/**
* Run all suites (and all benches under suites) and generate a report
* @return {Promise<Object, *>}
*/
run() {
if (this.isFinished) return Promise.resolve(this.report)
logger(this)
return compose(this.suites).then(() => {
this.isFinished = true
const report = {}
for (const suite of this.suites) {
report[suite.name] = suite.report
}
this.report = report
return report
})
}
}
Repository.prototype[RepositoryType] = true
/**
* By default, all suites are registers to the following {repo}
*/
const repo = new Repository()
module.exports = { Repository, repo }

View File

@@ -0,0 +1,85 @@
/* global Promise */
const { repo } = require('./Repository.js')
const { SuiteType } = require('./types')
const { logger } = require('./logger')
const { compose } = require('./compose')
const { makeOptions } = require('./makeOptions')
/**
* Suite is for holding Benches
*/
class Suite {
/**
* Construct a Suite and regiester it to repository
* @param {string} name
* @param {Object} options
* @property {void|Repository} repository
* @property {any} ...rest
*/
constructor(name, options = {}) {
const { repository = repo } = options
if (repository[name]) {
throw Error(`The suite name ${name} has benn occupied in repository`)
}
if (typeof name !== 'string') {
throw Error(`The suite name must be a string`)
}
this.name = name
this.options = makeOptions(options)
this.isFinished = false
this.benches = []
this.report = {}
repository.addSuite(this)
}
/**
* Whether it is a Suite
* @param {any} obj
* @return {boolean}
*/
isSuite(obj) {
return obj && obj[SuiteType]
}
/**
* Register an bench to the repository
* @param {Bench} bench
* @return {void}
*/
addBench(bench) {
this.isFinished = false
this.benches.push(bench)
}
/**
* Run all benches, and generate report for consumed time
* @return {Promise<Object, *>
*/
makeRun() {
if (this.isFinished) return Promise.resolve(this.report)
logger(this)
return compose(this.benches).then(() => {
this.isFinished = true
const report = {}
for (const bench of this.benches) {
report[bench.name] = bench.report
}
this.report = report
return report
})
}
}
Suite.prototype[SuiteType] = true
module.exports = { Suite }

View File

@@ -0,0 +1,66 @@
const { TimerType } = require('./types')
class Timer {
constructor() {
this.cpuStartTime = {}
this.hrStartTime = null
this.isStopped = false
this.elapsed = {}
}
/**
* Whether it is a Timer
* @param {any} obj
*/
isTimer(obj) {
return obj && obj[TimerType]
}
/**
* Start the timer
* @return {void}
*/
start() {
this.isStopped = false
this.cpuStartTime = process.cpuUsage()
this.hrStartTime = process.hrtime()
this.elapsed = {}
}
/**
* Stop the timer and store restore in this.elapsed
* @return {Object}
*/
end() {
if (this.isStopped) return this.elapsed
const cpuElapsed = process.cpuUsage(this.cpuStartTime)
const hrElapsed = process.hrtime(this.hrStartTime)
const { user, system } = cpuElapsed
const hr = hrElapsed[0] * 1000 + hrElapsed[1] / 1e6
/**
* user: cpu time consumed in user space
* system: cpu time consumed in system space
* all: user+system
* hr: real world time
* (unit): ms
*/
this.elapsed = {
user: user / 1000,
system: system / 1000,
all: (user + system) / 1000,
hr,
}
this.isStopped = true
return this.elapsed
}
}
Timer.prototype[TimerType] = true
module.exports = { Timer }

View File

@@ -0,0 +1,22 @@
/* global Promise */
const { errorLog } = require('./logger')
/**
* Run all benches/suites with Promise; Ensure an error would not block the whole process
* @param {Array<Suite>|Array<Bench>}
* @param {string} name; where to call the run method
*/
function compose(list, name = 'makeRun') {
return dispatch(0)
function dispatch(index) {
if (index === list.length) return Promise.resolve(true)
const node = list[index]
return new Promise(resolve => resolve(node[name]()))
.catch(err => errorLog(err))
.then(() => dispatch(index + 1))
}
}
module.exports = { compose }

View File

@@ -0,0 +1,5 @@
const { Repository, repo } = require('./Repository')
const { Suite } = require('./Suite')
const { Bench } = require('./Bench')
module.exports = { Repository, Suite, Bench, repo }

View File

@@ -0,0 +1,74 @@
/* eslint-disable no-console */
/**
* IS in test
*/
const IS_TEST =
typeof process !== 'undefined' &&
process.env &&
process.env.BABEL_ENV === 'test'
/**
* Log a `message`
*
* @param {String} message
* @param {Any} ...args
* @retrun {void}
*/
function log(message, ...args) {
if (IS_TEST) return
return console.log(message, ...args)
}
/*
* Log a error `message`
*/
function errorLog(message, ...args) {
console.error(message, ...args)
}
/**
* Logging benchmark result
*/
function logger(obj) {
const prefix = ' '
if (obj.isRepository) {
return log(`Repository ${obj.name} is running`)
}
if (obj.isSuite) {
return log(`${prefix}- Suite ${obj.name} is running`)
}
if (obj.isBench) {
if (!obj.isFinished) {
return log(`${prefix + prefix}- Bench ${obj.name} is running`)
}
const { report } = obj
const { cycles } = report
const header = {
user: 'user:',
hr: 'real:',
}
for (const key of ['user', 'hr']) {
log(
`${prefix + prefix + prefix}${header[key]} * ${cycles} cycles: ${
report[key]
} ms; ( ${cycles * 1000 / report[key]} ops/sec)`
)
}
return log(`${prefix + prefix + prefix}cycles: ${cycles}`)
}
return log(obj)
}
module.exports = { logger, errorLog, log }

View File

@@ -0,0 +1,40 @@
const defaultOptions = {
minTime: 1000,
maxTime: 2000,
minTries: 100,
maxTries: Infinity,
allocationTries: 1000,
async: false,
mode: 'adaptive',
}
/**
* Merge two options for configuring a bench run
* @param {Object} options
* @returns {Object}
* @property {number} minTime
* @property {number} maxTime
* @property {number} minTries
* @property {number} maxTries
* @property {number} allocationTries
* @property {boolean} async
* @property {"static"|"adaptive"} mode
*/
function makeOptions(options) {
const result = { ...defaultOptions, ...options }
for (const key in defaultOptions) {
const shallType = typeof defaultOptions[key]
const inputType = typeof result[key]
if (shallType !== inputType) {
throw TypeError(
`Wrong Input in Config Suite, options[${key}] should be ${shallType}, but the input type is ${inputType}`
)
}
}
return result
}
module.exports = { makeOptions }

View File

@@ -0,0 +1,11 @@
const RepositoryType = '@@__SLATE_REPOSITORY__@@'
const SuiteType = '@@__SLATE_SUITE__@@'
const BenchType = '@@__SLATE_BENCH__@@'
const TimerType = '@@__SLATE_BENCH_TIMER_@@'
module.exports = {
RepositoryType,
SuiteType,
BenchType,
TimerType,
}

View File

@@ -0,0 +1,12 @@
/**
* Dependencies.
*/
/**
* Tests.
*/
describe('slate-dev-benchmark', () => {
require('./tries/')
require('./time/')
})

View File

@@ -0,0 +1,22 @@
import { repo, Suite } from '../..'
import fs from 'fs'
import { resolve } from 'path'
describe('time', async () => {
const suite = new Suite('tries')
const testDir = resolve(__dirname)
const files = fs
.readdirSync(testDir)
.filter(x => x[0] !== '.' && x !== 'index.js')
for (const file of files) {
const module = require(`./${file}`)
it(module.experiment, () => {
module.default(suite)
const { expected } = module
repo.isFinished = false
return repo.run().then(() => expected())
})
}
})

View File

@@ -0,0 +1,31 @@
/* global Promise */
import { Bench } from '../..'
import assert from 'assert'
export const experiment = 'max-time-async'
let index = 0
// A wider range than sync, becuase Promise intialization, babel-node takes time
export function expected() {
assert(
index > 5 && index < 12,
`index should be 10, but is actually ${index}`
)
return true
}
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 100,
maxTries: 200,
minTime: 1,
maxTime: 100,
async: true,
})
bench.run(
() => new Promise(resolve => setTimeout(() => resolve(index++), 10))
)
}

View File

@@ -0,0 +1,31 @@
import { Bench } from '../..'
import { syncSleep } from '../utils/sleep'
import assert from 'assert'
export const experiment = 'max-time'
let index = 0
export function expected() {
assert(
index > 85 && index < 115,
`index should be around 100, but is actually ${index}`
)
return true
}
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 1000,
maxTries: 2000,
minTime: 1,
maxTime: 1000,
async: false,
})
bench.run(() => {
syncSleep(10)
index++
})
}

View File

@@ -0,0 +1,28 @@
import { Bench } from '../..'
import { syncSleep } from '../utils/sleep'
import assert from 'assert'
export const experiment = 'max-time'
let index = 0
export function expected() {
assert(index === 10, `index should be 10, but is actually ${index}`)
return true
}
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 100,
maxTries: 200,
minTime: 1,
maxTime: 100,
async: false,
})
bench.run(() => {
syncSleep(10)
index++
})
}

View File

@@ -0,0 +1,25 @@
import assert from 'assert'
import { repo, Suite } from '../..'
import fs from 'fs'
import { resolve } from 'path'
describe('tries', async () => {
const suite = new Suite('tries')
const testDir = resolve(__dirname)
const files = fs
.readdirSync(testDir)
.filter(x => x[0] !== '.' && x !== 'index.js')
for (const file of files) {
const module = require(`./${file}`)
it(module.experiment, () => {
module.default(suite)
const { actual, expected } = module
repo.isFinished = false
return repo.run().then(() => {
assert.deepEqual(actual, expected)
})
})
}
})

View File

@@ -0,0 +1,19 @@
import { Bench } from '../..'
export const experiment = 'max-tries adaptive mode'
export const actual = { index: 0 }
export const expected = { index: 200 }
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'adaptive',
minTries: 100,
maxTries: 200,
minTime: 100,
maxTime: Infinity,
})
bench.run(() => {
actual.index++
})
}

View File

@@ -0,0 +1,18 @@
import { Bench } from '../..'
export const experiment = 'min-tries static mode'
export const actual = { index: 0 }
export const expected = { index: 100 }
export default function(suite) {
const bench = new Bench(suite, experiment, {
mode: 'static',
minTries: 100,
maxTries: 200,
minTime: 100,
})
bench.run(() => {
actual.index++
})
}

View File

@@ -0,0 +1,10 @@
function syncSleep(ms) {
const start = process.hrtime()
while (true) {
const end = process.hrtime(start)
if (end[0] * 1000 + end[1] / 1e6 > ms) return undefined
}
}
export { syncSleep }

View File

@@ -1,48 +0,0 @@
/** @jsx h */
/* eslint-disable react/jsx-key */
import Html from '../..'
import React from 'react'
import { JSDOM } from 'jsdom' // eslint-disable-line import/no-extraneous-dependencies
const html = new Html({
parseHtml: JSDOM.fragment,
rules: [
{
serialize(obj, children) {
switch (obj.object) {
case 'block': {
switch (obj.type) {
case 'paragraph':
return React.createElement('p', {}, children)
case 'quote':
return React.createElement('blockquote', {}, children)
}
}
case 'mark': {
switch (obj.type) {
case 'bold':
return React.createElement('strong', {}, children)
case 'italic':
return React.createElement('em', {}, children)
}
}
}
},
},
],
})
export default function(string) {
html.deserialize(string)
}
export const input = `
<blockquote>
<p>
This is editable <strong>rich</strong> text, <em>much</em> better than a textarea!
</p>
</blockquote>
`
.trim()
.repeat(10)

View File

@@ -1,46 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
import { resetMemoization } from 'slate'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 50)
set('mintime', 1000)
if (category == 'models') {
after(() => {
resetMemoization()
})
}
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,46 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
import { resetMemoization } from 'slate'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 50)
set('mintime', 1000)
if (category == 'models') {
after(() => {
resetMemoization()
})
}
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,39 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 50)
set('mintime', 1000)
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,46 +0,0 @@
/* global suite, set, bench */
import fs from 'fs'
import { basename, extname, resolve } from 'path'
import { resetMemoization } from '..'
/**
* Benchmarks.
*/
const categoryDir = resolve(__dirname)
const categories = fs
.readdirSync(categoryDir)
.filter(c => c[0] != '.' && c != 'index.js')
categories.forEach(category => {
suite(category, () => {
set('iterations', 100)
set('mintime', 1000)
if (category == 'models') {
after(() => {
resetMemoization()
})
}
const benchmarkDir = resolve(categoryDir, category)
const benchmarks = fs
.readdirSync(benchmarkDir)
.filter(b => b[0] != '.' && !!~b.indexOf('.js'))
.map(b => basename(b, extname(b)))
benchmarks.forEach(benchmark => {
const dir = resolve(benchmarkDir, benchmark)
const module = require(dir)
const fn = module.default
let { input, before, after } = module
if (before) input = before(input)
bench(benchmark, () => {
fn(input)
if (after) after()
})
})
})
})

View File

@@ -1,14 +1,24 @@
/* eslint-disable no-console */
import chalk from 'chalk'
import figures from 'figures'
import emojis from 'emojis'
import baseline from '../../tmp/benchmark-baseline'
import comparison from '../../tmp/benchmark-comparison'
import { existsSync } from 'fs'
/**
* Constants.
*/
const THRESHOLD = 0.333
let THRESHOLD = 0.333
const configPath = '../../tmp/benchmark-config.js'
if (existsSync(configPath)) {
const alternative = require(configPath).THRESHOLD
if (typeof alternative === 'number' && alternative > 0) {
THRESHOLD = alternative
}
}
/**
* Print.
@@ -21,27 +31,72 @@ baseline.forEach((suite, i) => {
console.log(` ${suite.name}`)
suite.benchmarks.forEach((base, j) => {
const comp = comparison[i].benchmarks[j]
if (!comp) return
const compared = { user: {}, hr: {} }
const b = base.iterations / base.elapsed * 1000
const c = comp.iterations / comp.elapsed * 1000
const threshold = b * THRESHOLD
const slower = b - c > threshold
const faster = b - c < 0 - threshold
const percent = Math.round(Math.abs(b - c) / b * 100)
for (const key of Object.keys(compared)) {
const comp = comparison[i].benchmarks[j]
if (!comp) return
const b = base.iterations / base[key] * 1000
const c = comp.iterations / comp[key] * 1000
const balancePercent =
b > c ? Math.round(Math.abs(b - c) / c * 100) : (c - b) / b * 100
let output = `${b.toFixed(2)} ${c.toFixed(2)} ops/sec`
if (slower) output = chalk.red(`${output} (${percent}% slower)`)
else if (faster) output = chalk.green(`${output} (${percent}% faster)`)
else output = chalk.gray(output)
const output = `${b.toFixed(2)} -> ${c.toFixed(2)} ops/sec`
compared[key].baseOutput = output
compared[key].percentOutput = `${balancePercent.toFixed(2)}% ${
c > b ? 'faster' : 'slower'
}`
compared[key].percentValue = balancePercent
compared[key].b = b
compared[key].c = c
compared[key].isFaster = c > b
if (balancePercent > 1000) {
compared[key].percentOutput += emojis.unicode(' :scream: ')
} else if (balancePercent > 100) {
if (c > b) {
compared[key].percentOutput += emojis.unicode(' :raised_hands: ')
} else {
compared[key].percentOutput += emojis.unicode(' :worried: ')
}
}
}
if (percent > 1000) output += ' 😱'
else if (faster && percent > 100) output += ' 🙌'
else if (slower && percent > 100) output += ' 😟'
const { user, hr } = compared
console.log(` ${base.title}`)
console.log(` ${output}`)
if (
user.percentValue < THRESHOLD * 100 &&
hr.percentValue < THRESHOLD * 100
) {
console.log(
chalk.grey(
` ${figures.tick} ${base.name}: ${user.baseOutput} (${
user.percentOutput
})`
)
)
return
}
if (user.isFaster === hr.isFaster) {
if (user.isFaster) {
console.log(chalk.green(` ${figures.star} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.cross} ${base.name}:`))
console.log(
` user: ${user.baseOutput} (${user.percentOutput})`
)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
return
}
console.log(chalk.red(` ${figures.questionMarkPrefix} ${base.name}:`))
console.log(` user: ${user.baseOutput} (${user.percentOutput})`)
console.log(` real: ${hr.baseOutput} (${hr.percentOutput})`)
})
})

View File

@@ -1,31 +0,0 @@
const { stdout } = process
module.exports = function(runner, utils) {
let hasSuite = false
let hasBench = false
runner.on('start', () => {
stdout.write('[')
})
runner.on('end', () => {
stdout.write(']')
})
runner.on('suite start', suite => {
if (hasSuite) stdout.write(',')
stdout.write(`{"name":"${suite.title}","benchmarks":[`)
hasSuite = true
})
runner.on('suite end', suite => {
hasBench = false
stdout.write(']}')
})
runner.on('bench end', bench => {
if (hasBench) stdout.write(',')
stdout.write(JSON.stringify(bench))
hasBench = true
})
}

View File

@@ -11,6 +11,7 @@ import slatePropTypes from '../../packages/slate-prop-types/package.json'
import slateReact from '../../packages/slate-react/package.json'
import slateSchemaViolations from '../../packages/slate-schema-violations/package.json'
import slateSimulator from '../../packages/slate-simulator/package.json'
// Do not import slateDevBenchmark here. The benchmark shall be a pure nodeJS program and can be run without babel-node
const configurations = [
...factory(slate),

View File

@@ -1926,6 +1926,10 @@ commander@2.9.0:
dependencies:
graceful-readlink ">= 1.0.0"
commander@^2.15.1:
version "2.16.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50"
commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
@@ -2796,6 +2800,10 @@ emojis-list@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
emojis@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/emojis/-/emojis-1.0.10.tgz#2558133df0dff13313c99531647f693d7adb57da"
emotion@^9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/emotion/-/emotion-9.2.4.tgz#0139e7cc154b2845f4b9afaa996dd4de13bb90e3"