mirror of
https://github.com/danbulant/markdown-wasm
synced 2026-05-19 04:18:38 +00:00
adds benchmarks
This commit is contained in:
parent
96d9547723
commit
3e65f82ae1
40 changed files with 12549 additions and 8 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,4 +2,5 @@
|
|||
**/.DS_Store
|
||||
/build
|
||||
/node_modules
|
||||
/test/spec/spec.html
|
||||
*.sublime*
|
||||
|
|
|
|||
2
LICENSE
2
LICENSE
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2019 Rasmus Andersson <https://rsms.me/>
|
||||
Copyright (c) 2019-2020 Rasmus Andersson <https://rsms.me/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
53
README.md
53
README.md
|
|
@ -12,26 +12,30 @@ Based on [md4c](http://github.com/mity/md4c)
|
|||
|
||||
## Examples
|
||||
|
||||
In Nodejs
|
||||
In NodeJS, single file with embedded compressed WASM
|
||||
|
||||
```js
|
||||
const markdown = require("./dist/markdown.node.js")
|
||||
console.log(markdown.parse("# hello\n*world*"))
|
||||
```
|
||||
|
||||
ES module
|
||||
ES module, WASM loaded async from separate file
|
||||
|
||||
```js
|
||||
import * as markdown from "./dist/markdown.es"
|
||||
import * as markdown from "./dist/markdown.es.js"
|
||||
await markdown.ready
|
||||
console.log(markdown.parse("# hello\n*world*"))
|
||||
```
|
||||
|
||||
Separately loded wasm module (useful in web browsers)
|
||||
In web browser, WASM loaded async from separate file
|
||||
|
||||
```js
|
||||
require("./dist/markdown").ready.then(markdown => {
|
||||
```html
|
||||
<script src="markdown.js"></script>
|
||||
<script>
|
||||
window["markdown"].ready.then(markdown => {
|
||||
console.log(markdown.parse("# hello\n*world*"))
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -42,6 +46,41 @@ npm install markdown-wasm
|
|||
```
|
||||
|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The [`test/benchmark`](test/benchmark) directory contain a benchmark suite which you can
|
||||
run yourself. It tests a few popular markdown parser-renderers by parsing & rendering a bunch
|
||||
of different sample markdown files.
|
||||
|
||||
The following results were samples on a 2.9 GHz MacBook running macOS 10.15, NodeJS v14.11.0
|
||||
|
||||
|
||||
#### Average ops/second
|
||||
|
||||
Ops/second represents how many times a library is able to parse markdown and render HTML
|
||||
during a second, on average across all sample files.
|
||||
|
||||

|
||||
|
||||
|
||||
#### Average throughput
|
||||
|
||||
Throughput is the average amount of markdown data processed during a second while both parsing
|
||||
and rendering to HTML. The statistics does not include HTML generated but only bytes of markdown
|
||||
source text parsed.
|
||||
|
||||

|
||||
|
||||
|
||||
#### Min–max parse time
|
||||
|
||||
This graph shows the spread between the fastest and slowest parse-and-render operations
|
||||
for each library. Lower numbers are better.
|
||||
|
||||

|
||||
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
|
|
@ -114,7 +153,7 @@ npm install
|
|||
npx wasmc
|
||||
```
|
||||
|
||||
Build debug version of markdown-es into ./build/debug and watch source files:
|
||||
Build debug version of markdown into ./build/debug and watch source files:
|
||||
|
||||
```
|
||||
npx wasmc -g -w
|
||||
|
|
|
|||
2
test/benchmark/.gitignore
vendored
Normal file
2
test/benchmark/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/node_modules
|
||||
/results/*.csv
|
||||
12
test/benchmark/README.md
Normal file
12
test/benchmark/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Markdown-wasm benchmarks
|
||||
|
||||
This directory contains a benchmark suite.
|
||||
You'll need nodejs and npm installed.
|
||||
|
||||
1. `npm install`
|
||||
2. `npm run bench`
|
||||
|
||||
Running the benchmarks takes a while since in order to be accurate each parse-and-render
|
||||
operation is performed synchronously on a single CPU thread.
|
||||
|
||||
Results are written to the `results` directory; `bench.csv` along with SVG graphs.
|
||||
89
test/benchmark/bench.js
Executable file
89
test/benchmark/bench.js
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env node
|
||||
const Benchmark = require('benchmark').Benchmark
|
||||
const fs = require('fs')
|
||||
const Path = require('path')
|
||||
|
||||
const commonmark = require('commonmark')
|
||||
const Showdown = require('showdown')
|
||||
const marked = require('marked')
|
||||
const markdownit = require('markdown-it')('commonmark')
|
||||
const markdown_wasm = require('../../dist/markdown.node.js')
|
||||
|
||||
// setup markdownit
|
||||
// disable expensive IDNa links encoding:
|
||||
const markdownit_encode = markdownit.utils.lib.mdurl.encode;
|
||||
markdownit.normalizeLink = function(url) { return markdownit_encode(url); };
|
||||
markdownit.normalizeLinkText = function(str) { return str; };
|
||||
|
||||
// setup showdown
|
||||
var showdown = new Showdown.Converter()
|
||||
|
||||
// setup commonmark
|
||||
var parser = new commonmark.Parser()
|
||||
var renderer = new commonmark.HtmlRenderer()
|
||||
|
||||
// parse CLI input
|
||||
let filename = process.argv[2]
|
||||
if (!filename) {
|
||||
console.error(`usage: bench.js <markdown-file>`)
|
||||
console.error(`usage: bench.js <dir-of-markdown-files>`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// print CSV header
|
||||
console.log(csv(["library","file","ops/sec","filesize"]))
|
||||
|
||||
// run tests on all files in a directory or a single file
|
||||
let st = fs.statSync(filename)
|
||||
if (st.isDirectory()) {
|
||||
process.chdir(filename)
|
||||
for (let fn of fs.readdirSync(".")) {
|
||||
benchmarkFile(fn)
|
||||
}
|
||||
} else {
|
||||
benchmarkFile(filename)
|
||||
}
|
||||
|
||||
// Benchmark.options.maxTime = 10
|
||||
|
||||
function csv(values) {
|
||||
return values.map(s => String(s).replace(/,/g,"\\,")).join(",")
|
||||
}
|
||||
|
||||
function benchmarkFile(benchfile) {
|
||||
|
||||
var contents = fs.readFileSync(benchfile, 'utf8');
|
||||
var contentsBuffer = fs.readFileSync(benchfile);
|
||||
|
||||
let csvLinePrefix = `${benchfile.replace(/,/g,"\\,")},${contentsBuffer.length},`
|
||||
|
||||
new Benchmark.Suite({
|
||||
onCycle(ev) {
|
||||
let b = ev.target
|
||||
// console.log("cycle", b)
|
||||
console.log(csv([b.name, benchfile, b.hz, contentsBuffer.length]))
|
||||
},
|
||||
// onComplete(ev) {
|
||||
// let b = ev.target
|
||||
// console.log("onComplete", {ev}, b.stats, b.times)
|
||||
// }
|
||||
}).add('commonmark', function() {
|
||||
renderer.render(parser.parse(contents));
|
||||
})
|
||||
.add('showdown', function() {
|
||||
showdown.makeHtml(contents);
|
||||
})
|
||||
.add('marked', function() {
|
||||
marked(contents);
|
||||
})
|
||||
.add('markdown-it', function() {
|
||||
markdownit.render(contents);
|
||||
})
|
||||
.add('markdown-wasm/string', function() {
|
||||
markdown_wasm.parse(contents);
|
||||
})
|
||||
.add('markdown-wasm/bytes', function() {
|
||||
markdown_wasm.parse(contentsBuffer, {asMemoryView:true});
|
||||
})
|
||||
.run();
|
||||
}
|
||||
493
test/benchmark/graph.js
Normal file
493
test/benchmark/graph.js
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
/*
|
||||
Generates benchmark graphs using d3.
|
||||
Intended to be run as a CLI script, accepting a single argument: CSV file from bench.js
|
||||
|
||||
Copyright (c) 2019-2020 Rasmus Andersson <https://rsms.me/>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
const fs = require("fs")
|
||||
const Path = require("path")
|
||||
const D3Node = require("d3-node")
|
||||
const d3 = require("d3")
|
||||
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 3) {
|
||||
console.error(`
|
||||
usage: graph.js <csvfile>
|
||||
<csvfile> should be a file produced by bench.js (or one with identical format)
|
||||
`.trim().replace(/\n\s+/g, "\n"))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const data = loadData(process.argv[2])
|
||||
const outdir = Path.dirname(Path.resolve(process.argv[2]))
|
||||
// console.log({data})
|
||||
|
||||
const commonGraphConfig = {
|
||||
width: 960,
|
||||
fontSize: 16,
|
||||
color: d3.scaleOrdinal(d3.schemeTableau10),
|
||||
// color: d3.scaleOrdinal().range(["#111", "#333", "#555", "#777", "#999"]),
|
||||
}
|
||||
|
||||
writefile(outdir, "avg-ops-per-sec.svg", createBarChart(data, "avg_ops", {
|
||||
...commonGraphConfig,
|
||||
format: d3.format(",.0f"),
|
||||
}))
|
||||
|
||||
writefile(outdir, "avg-throughput.svg", createBarChart(data, "avg_throughput", {
|
||||
...commonGraphConfig,
|
||||
xAxisFormat: ",.0f",
|
||||
xAxisTickCount: commonGraphConfig.width/50,
|
||||
format: mb => {
|
||||
const bytes = mb*1024000
|
||||
return (
|
||||
bytes <= 1024 ? `${bytes.toFixed(0)}B/s` :
|
||||
bytes <= 1024000 ? `${(bytes/1024).toFixed(0)}kB/s` :
|
||||
bytes <= 1024000000 ? `${(bytes/1024000).toFixed(0)}MB/s` :
|
||||
`${(bytes/1024000000).toFixed(0)}GB/s`
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
const rangeData = data.map(d => ({
|
||||
name: d.name,
|
||||
min: (1000000000/d.max_ops), // convert to nanoseconds/op
|
||||
max: (1000000000/d.min_ops), // convert to nanoseconds/op
|
||||
}))
|
||||
writefile(outdir, "minmax-parse-time.svg", createMinMaxBarChart(
|
||||
rangeData,
|
||||
{
|
||||
...commonGraphConfig,
|
||||
format: ns => (
|
||||
ns <= 1000 ? `${ns.toFixed(0)}ns` :
|
||||
ns <= 1000000 ? `${(ns/1000).toFixed(1)}us` :
|
||||
ns <= 1000000000 ? `${(ns/1000000).toFixed(1)}ms` :
|
||||
`${(ns/1000000000).toFixed(1)}s`
|
||||
),
|
||||
}
|
||||
))
|
||||
|
||||
// const stackedData = data.slice()
|
||||
// stackedData.columns = ["name", "min_ops", "mean_ops", "max_ops"]
|
||||
// writefile(outdir, "min-mean-max-candlestick.svg", createStackedBarChart(
|
||||
// stackedData,
|
||||
// {...commonGraphConfig}
|
||||
// ))
|
||||
}
|
||||
|
||||
|
||||
function loadData(csvfile) {
|
||||
const csvText = fs.readFileSync(csvfile, "utf8")
|
||||
|
||||
const libraries = {}
|
||||
const fileset = new Set()
|
||||
|
||||
d3.csvParse(csvText, d => {
|
||||
if (d.library == "markdown-wasm/string") {
|
||||
return
|
||||
}
|
||||
if (d.library == "markdown-wasm/bytes") {
|
||||
d.library = "markdown-wasm"
|
||||
}
|
||||
let lib = libraries[d.library] || (libraries[d.library] = {})
|
||||
const ops_sec = parseFloat(d["ops/sec"])
|
||||
const filesize = parseInt(d["filesize"])
|
||||
fileset.add(d.file)
|
||||
lib[d.file] = { ops_sec, filesize }
|
||||
})
|
||||
|
||||
const data = []
|
||||
const files = Array.from(fileset)
|
||||
|
||||
for (let library of Object.keys(libraries)) {
|
||||
const files = libraries[library]
|
||||
const filenames = Object.keys(files)
|
||||
|
||||
// ops/sec
|
||||
const ops_values = filenames.map(filename => files[filename].ops_sec)
|
||||
ops_values.sort((a, b) => a < b ? -1 : b < a ? 1 : 0)
|
||||
const avg_ops = vmath_avg(ops_values)
|
||||
const min_ops = ops_values[0]
|
||||
const max_ops = ops_values[ops_values.length - 1]
|
||||
const mean_ops = ops_values[Math.floor(ops_values.length/2)]
|
||||
const sum_ops = ops_values.reduce((a, v) => a + v, 0)
|
||||
|
||||
// throughput in megabytes
|
||||
const throughput_values = filenames.map(k =>
|
||||
(files[k].ops_sec * files[k].filesize)/1024000)
|
||||
throughput_values.sort((a, b) => a < b ? -1 : b < a ? 1 : 0)
|
||||
const avg_throughput = vmath_avg(throughput_values)
|
||||
const min_throughput = throughput_values[0] // Math.min(...throughput_values)
|
||||
const max_throughput = throughput_values[throughput_values.length-1]
|
||||
const mean_throughput = throughput_values[Math.floor(throughput_values.length/2)]
|
||||
const sum_throughput = throughput_values.reduce((a, v) => a + v, 0)
|
||||
|
||||
data.push({
|
||||
name: library,
|
||||
ops: ops_values,
|
||||
throughput: throughput_values,
|
||||
avg_ops, min_ops, max_ops, mean_ops, sum_ops,
|
||||
avg_throughput, min_throughput, max_throughput, mean_throughput, sum_throughput,
|
||||
})
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
function vmath_avg(numbers) {
|
||||
// http://www.heikohoffmann.de/htmlthesis/node134.html
|
||||
let avg = 0.0
|
||||
let t = 1
|
||||
for (let x of numbers) {
|
||||
avg += (x - avg) / t
|
||||
++t
|
||||
}
|
||||
return avg
|
||||
}
|
||||
|
||||
|
||||
const fontFamily = `-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif`
|
||||
const xAxisLabelOpacity = 0.5
|
||||
|
||||
|
||||
// dataValueKey should be one of the following strings:
|
||||
// avg_ops
|
||||
// min_ops
|
||||
// max_ops
|
||||
// mean_ops
|
||||
// sum_ops
|
||||
// avg_throughput
|
||||
// min_throughput
|
||||
// max_throughput
|
||||
// mean_throughput
|
||||
// sum_throughput
|
||||
//
|
||||
function createBarChart(data, dataValueKey, graphConfig) {
|
||||
const size = graphConfig.fontSize || 14
|
||||
const xAxisLabelSize = Math.round(size*10*0.8)/10
|
||||
const longestYAxisName = data.reduce((a, d) => Math.max(a, d.name.length), 0)
|
||||
const yAxisMinWidth = size*0.75*longestYAxisName // guesstimate text length
|
||||
const margin = { top: size*3, right: size, bottom: size*2, left:yAxisMinWidth }
|
||||
const barHeight = Math.round(size*2.25)
|
||||
const labelPadding = Math.round(barHeight*0.15)
|
||||
const height = Math.ceil((data.length + 0.1) * barHeight) + margin.top + margin.bottom
|
||||
const width = graphConfig.width || 600
|
||||
const format = graphConfig.format || d3.format(",.2f")
|
||||
const xAxisFormat = graphConfig.xAxisFormat || format
|
||||
const xAxisTickCount = graphConfig.xAxisTickCount || width/(xAxisLabelSize*8)
|
||||
const color = graphConfig.color
|
||||
|
||||
data = data.slice().sort((a, b) =>
|
||||
a[dataValueKey] < b[dataValueKey] ? 1 :
|
||||
b[dataValueKey] < a[dataValueKey] ? -1 :
|
||||
0
|
||||
)
|
||||
|
||||
const d3n = new D3Node()
|
||||
const svg = d3n.createSVG(width, height)
|
||||
|
||||
// Adapted from https://observablehq.com/@d3/horizontal-bar-chart
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, d3.max(data, d => d[dataValueKey])])
|
||||
.range([margin.left, width - margin.right])
|
||||
// .rangeRound([margin.left, width - margin.right])
|
||||
|
||||
const y = d3.scaleBand()
|
||||
.domain(d3.range(data.length))
|
||||
.rangeRound([margin.top, height - margin.bottom])
|
||||
.padding(0.1)
|
||||
|
||||
svg.attr("viewBox", [0, 0, width, height])
|
||||
.attr("font-family", fontFamily)
|
||||
.attr("font-size", size)
|
||||
|
||||
// y-axis labels
|
||||
let g = svg.append("g")
|
||||
.attr("text-anchor", "end")
|
||||
.attr("font-weight", 500)
|
||||
.selectAll("text")
|
||||
.data(data)
|
||||
.join("text")
|
||||
.attr("alignment-baseline", "central")
|
||||
.attr("x", d => x(0))
|
||||
.attr("y", (d, i) => y(i) + y.bandwidth()/2)
|
||||
.attr("dx", -labelPadding*2)
|
||||
.text(d => d.name)
|
||||
|
||||
// bars
|
||||
svg.append("g")
|
||||
.selectAll("rect")
|
||||
.data(data)
|
||||
.join("rect")
|
||||
.attr("fill", d => color(d.name))
|
||||
.attr("x", x(0))
|
||||
.attr("y", (d, i) => y(i))
|
||||
.attr("width", d => x(d[dataValueKey]) - x(0))
|
||||
.attr("height", y.bandwidth())
|
||||
|
||||
// value labes on top of bars
|
||||
svg.append("g")
|
||||
.attr("fill", "white")
|
||||
.attr("text-anchor", "end")
|
||||
.attr("font-weight", 500)
|
||||
.selectAll("text")
|
||||
.data(data)
|
||||
.join("text")
|
||||
.attr("x", d => x(d[dataValueKey]))
|
||||
.attr("y", (d, i) => y(i) + y.bandwidth() / 2)
|
||||
.attr("alignment-baseline", "central")
|
||||
.attr("dx", -labelPadding)
|
||||
.text(d => format(d[dataValueKey]))
|
||||
.call(text => text.filter(d => x(d[dataValueKey]) - x(0) < 40) // short bars
|
||||
.attr("dx", labelPadding)
|
||||
.attr("fill", "black")
|
||||
.attr("text-anchor", "start")
|
||||
)
|
||||
|
||||
// x axis
|
||||
svg.append("g").call(
|
||||
g => g.attr("transform", `translate(0,${margin.top})`)
|
||||
.call(d3.axisTop(x).ticks(xAxisTickCount, xAxisFormat))
|
||||
.call(g => g.select(".domain").remove())
|
||||
.attr("font-family", fontFamily)
|
||||
.attr("font-size", xAxisLabelSize)
|
||||
.attr("opacity", xAxisLabelOpacity)
|
||||
)
|
||||
|
||||
// y axis
|
||||
// const yAxis = g => g
|
||||
// .attr("transform", `translate(${margin.left},0)`)
|
||||
// .call(d3.axisLeft(y).tickFormat(i => data[i].name).tickSizeOuter(0))
|
||||
// .attr("font-family", fontFamily)
|
||||
// .attr("font-size", size)
|
||||
// svg.append("g")
|
||||
// .call(yAxis)
|
||||
|
||||
// return svg.node()
|
||||
return d3n.svgString()
|
||||
}
|
||||
|
||||
function createMinMaxBarChart(data, graphConfig) {
|
||||
// Adapted from https://observablehq.com/@d3/horizontal-bar-chart
|
||||
const size = graphConfig.fontSize || 14
|
||||
const xAxisLabelSize = Math.round(size*10*0.8)/10
|
||||
const margin = {top: size*3, right: size, bottom: size*2, left:size}
|
||||
const barHeight = Math.round(size*2.25)
|
||||
const labelPadding = Math.round(barHeight*0.15)
|
||||
const height = Math.ceil((data.length + 0.1) * barHeight) + margin.top + margin.bottom
|
||||
const width = graphConfig.width || 600
|
||||
const format = graphConfig.format || d3.format(",.2f")
|
||||
const color = graphConfig.color
|
||||
|
||||
const d3n = new D3Node()
|
||||
const svg = d3n.createSVG(width, height)
|
||||
|
||||
svg.attr("viewBox", [0, 0, width, height])
|
||||
.attr("font-family", fontFamily)
|
||||
.attr("font-size", size)
|
||||
|
||||
data = data.slice().sort((a, b) =>
|
||||
a.max < b.max ? -1 :
|
||||
b.max < a.max ? 1 :
|
||||
0
|
||||
)
|
||||
|
||||
const x = d3.scaleLog()
|
||||
.domain([d3.min(data, d => d.min), d3.max(data, d => d.max)])
|
||||
// .range([margin.left, width - margin.right])
|
||||
.rangeRound([margin.left, width - margin.right])
|
||||
|
||||
// console.log(x(1))
|
||||
|
||||
const y = d3.scaleBand()
|
||||
.domain(d3.range(data.length))
|
||||
.rangeRound([margin.top, height - margin.bottom])
|
||||
.padding(0.1)
|
||||
|
||||
// bars
|
||||
svg.append("g")
|
||||
.selectAll("rect")
|
||||
.data(data)
|
||||
.join("rect")
|
||||
.attr("fill", d => color(d.name))
|
||||
.attr("x", d => x(d.min))
|
||||
.attr("y", (d, i) => y(i))
|
||||
.attr("width", d => x(d.max) - x(d.min))
|
||||
.attr("height", y.bandwidth())
|
||||
|
||||
// name label inside bar, center aligned
|
||||
svg.append("g")
|
||||
.attr("fill", "white")
|
||||
.attr("font-weight", 500)
|
||||
.attr("text-anchor", "middle")
|
||||
.selectAll("text")
|
||||
.data(data)
|
||||
.join("text")
|
||||
.attr("x", d => x(d.min) + (x(d.max) - x(d.min))/2)
|
||||
.attr("y", (d, i) => y(i) + y.bandwidth() / 2)
|
||||
.attr("alignment-baseline", "central")
|
||||
.text(d => d.name)
|
||||
|
||||
// min value inside bar, left aligned
|
||||
svg.append("g")
|
||||
.attr("fill", "white")
|
||||
.attr("font-weight", 500)
|
||||
.attr("text-anchor", "start")
|
||||
.selectAll("text")
|
||||
.data(data)
|
||||
.join("text")
|
||||
.attr("x", d => x(d.min))
|
||||
.attr("y", (d, i) => y(i) + y.bandwidth() / 2)
|
||||
.attr("alignment-baseline", "central")
|
||||
.attr("dx", labelPadding)
|
||||
.text(d => format(d.min))
|
||||
|
||||
// max value inside bar, right aligned
|
||||
svg.append("g")
|
||||
.attr("fill", "white")
|
||||
.attr("font-weight", 500)
|
||||
.attr("text-anchor", "end")
|
||||
.selectAll("text")
|
||||
.data(data)
|
||||
.join("text")
|
||||
.attr("x", d => x(d.min) + (x(d.max) - x(d.min)))
|
||||
.attr("y", (d, i) => y(i) + y.bandwidth() / 2)
|
||||
.attr("alignment-baseline", "central")
|
||||
.attr("dx", -labelPadding)
|
||||
.text(d => format(d.max))
|
||||
|
||||
// x axis
|
||||
svg.append("g").call(g => g
|
||||
.attr("transform", `translate(0,${margin.top})`)
|
||||
.call(d3.axisTop(x).ticks(width / (xAxisLabelSize*10), format))
|
||||
.call(g => g.select(".domain").remove())
|
||||
.attr("font-family", fontFamily)
|
||||
.attr("font-size", xAxisLabelSize)
|
||||
.attr("opacity", xAxisLabelOpacity)
|
||||
)
|
||||
|
||||
// const yAxis = g => g
|
||||
// .attr("transform", `translate(${margin.left},0)`)
|
||||
// .call(d3.axisLeft(y).tickFormat(i => data[i].name).tickSizeOuter(0))
|
||||
// svg.append("g")
|
||||
// .call(yAxis)
|
||||
|
||||
// return svg.node()
|
||||
return d3n.svgString()
|
||||
}
|
||||
|
||||
function createStackedBarChart(data, graphConfig) {
|
||||
const barHeight = 25
|
||||
const margin = {top: 30, right: 0, bottom: 10, left: 100}
|
||||
const height = Math.ceil((data.length + 0.1) * barHeight) + margin.top + margin.bottom
|
||||
const width = graphConfig.width || 600
|
||||
const format = graphConfig.format || d3.format(",.2f")
|
||||
const color = graphConfig.color
|
||||
|
||||
const series = d3.stack()
|
||||
.keys(data.columns.slice(1))(data)
|
||||
.map(d => (d.forEach(v => v.key = d.key), d))
|
||||
|
||||
const x = d3.scaleLinear()
|
||||
.domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
|
||||
.range([margin.left, width - margin.right])
|
||||
|
||||
const y = d3.scaleBand()
|
||||
.domain(data.map(d => d.name))
|
||||
.range([margin.top, height - margin.bottom])
|
||||
.padding(0.08)
|
||||
|
||||
const xAxis = g => g
|
||||
.attr("transform", `translate(0,${margin.top})`)
|
||||
.call(d3.axisTop(x).ticks(width / 100, "s"))
|
||||
.call(g => g.selectAll(".domain").remove())
|
||||
|
||||
const yAxis = g => g
|
||||
.attr("transform", `translate(${margin.left},0)`)
|
||||
.call(d3.axisLeft(y).tickSizeOuter(0))
|
||||
.call(g => g.selectAll(".domain").remove())
|
||||
|
||||
const d3n = new D3Node()
|
||||
const svg = d3n.createSVG(width, height)
|
||||
|
||||
svg.attr("viewBox", [0, 0, width, height])
|
||||
|
||||
svg.append("g")
|
||||
.selectAll("g")
|
||||
.data(series)
|
||||
.join("g")
|
||||
.attr("fill", d => color(d.key))
|
||||
.selectAll("rect")
|
||||
.data(d => d)
|
||||
.join("rect")
|
||||
.attr("x", d => x(d[0]))
|
||||
.attr("y", (d, i) => y(d.data.name))
|
||||
.attr("width", d => x(d[1]) - x(d[0]))
|
||||
.attr("height", y.bandwidth())
|
||||
.append("title")
|
||||
.text(d => `${d.data.name} ${d.key}
|
||||
${format(d.data[d.key])}`)
|
||||
|
||||
svg.append("g")
|
||||
.call(xAxis)
|
||||
|
||||
svg.append("g")
|
||||
.call(yAxis)
|
||||
|
||||
return d3n.svgString()
|
||||
}
|
||||
|
||||
|
||||
function writefile(dir, filename, contents) {
|
||||
filename = Path.relative(process.cwd(), Path.resolve(dir, filename))
|
||||
console.log(`write ${filename}`)
|
||||
fs.writeFileSync(filename, contents, "utf8")
|
||||
}
|
||||
|
||||
|
||||
// const buf = canvas.toBuffer("image/png")
|
||||
// displayImageInTerminal(buf)
|
||||
|
||||
|
||||
// displayImageInTerminal(fs.readFileSync("output.png"))
|
||||
|
||||
function displayImageInTerminal(imageBuffer, name) {
|
||||
// iTerm image:
|
||||
// ESC ] 1337 ; File = [arguments] : base-64 encoded file contents ^G
|
||||
// https://www.iterm2.com/documentation-images.html
|
||||
process.stdout.write(`\x1B]1337;File=inline=1;width=100%;height=auto;inline=1`)
|
||||
if (name) {
|
||||
process.stdout.write(`;name=${b64("chart.png")}:`)
|
||||
} else {
|
||||
process.stdout.write(`:`)
|
||||
}
|
||||
process.stdout.write(imageBuffer.toString("base64"))
|
||||
process.stdout.write(`\x07\n`)
|
||||
function b64(s) {
|
||||
return Buffer.from(s, "utf8").toString("base64")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
main()
|
||||
1396
test/benchmark/package-lock.json
generated
Normal file
1396
test/benchmark/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
23
test/benchmark/package.json
Normal file
23
test/benchmark/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "markdown-wasm-benchmarks",
|
||||
"description": "Benchmark suite based on https://github.com/commonmark/commonmark.js bench",
|
||||
"version": "0.1.0",
|
||||
"author": "John MacFarlane",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jgm/commonmark.js/issues"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"main": "bench.js",
|
||||
"scripts": {
|
||||
"bench": "node bench.js ./samples | tee results/bench.csv && node graph.js result/bench.csv"
|
||||
},
|
||||
"dependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"commonmark": "^0.29.2",
|
||||
"d3": "^6.2.0",
|
||||
"d3-node": "^2.2.2",
|
||||
"markdown-it": "^10.0.0",
|
||||
"marked": "^0.7.0",
|
||||
"showdown": "^1.9.1"
|
||||
}
|
||||
}
|
||||
1
test/benchmark/results/avg-ops-per-sec.svg
Normal file
1
test/benchmark/results/avg-ops-per-sec.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="960" height="264" viewBox="0,0,960,264" font-family="-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif" font-size="16"><g text-anchor="end" font-weight="500"><text alignment-baseline="central" x="156" y="68" dx="-10">markdown-wasm</text><text alignment-baseline="central" x="156" y="104" dx="-10">markdown-it</text><text alignment-baseline="central" x="156" y="140" dx="-10">marked</text><text alignment-baseline="central" x="156" y="176" dx="-10">commonmark</text><text alignment-baseline="central" x="156" y="212" dx="-10">showdown</text></g><g><rect fill="#4e79a7" x="156" y="52" width="788" height="32"></rect><rect fill="#f28e2c" x="156" y="88" width="350.6581162871162" height="32"></rect><rect fill="#e15759" x="156" y="124" width="313.49798756784514" height="32"></rect><rect fill="#76b7b2" x="156" y="160" width="273.23224820605805" height="32"></rect><rect fill="#59a14f" x="156" y="196" width="34.61463024424154" height="32"></rect></g><g fill="white" text-anchor="end" font-weight="500"><text x="944" y="68" alignment-baseline="central" dx="-5">193,296</text><text x="506.6581162871162" y="104" alignment-baseline="central" dx="-5">86,016</text><text x="469.49798756784514" y="140" alignment-baseline="central" dx="-5">76,901</text><text x="429.23224820605805" y="176" alignment-baseline="central" dx="-5">67,024</text><text x="190.61463024424154" y="212" alignment-baseline="central" dx="5" fill="black" text-anchor="start">8,491</text></g><g transform="translate(0,48)" fill="none" font-size="12.8" font-family="-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif" text-anchor="middle" opacity="0.5"><g class="tick" opacity="1" transform="translate(156.5,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">0</text></g><g class="tick" opacity="1" transform="translate(238.03288848131865,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">20,000</text></g><g class="tick" opacity="1" transform="translate(319.56577696263736,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">40,000</text></g><g class="tick" opacity="1" transform="translate(401.098665443956,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">60,000</text></g><g class="tick" opacity="1" transform="translate(482.6315539252747,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">80,000</text></g><g class="tick" opacity="1" transform="translate(564.1644424065934,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">100,000</text></g><g class="tick" opacity="1" transform="translate(645.697330887912,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">120,000</text></g><g class="tick" opacity="1" transform="translate(727.2302193692307,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">140,000</text></g><g class="tick" opacity="1" transform="translate(808.7631078505494,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">160,000</text></g><g class="tick" opacity="1" transform="translate(890.295996331868,0)"><line stroke="currentColor" y2="-6"></line><text fill="currentColor" y="-9" dy="0em">180,000</text></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
1
test/benchmark/results/avg-throughput.svg
Normal file
1
test/benchmark/results/avg-throughput.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.8 KiB |
47
test/benchmark/results/index.html
Normal file
47
test/benchmark/results/index.html
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Benchmarks</title>
|
||||
<style>
|
||||
body {
|
||||
padding: 2em;
|
||||
font:14px sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
/*border: 1px solid #eee;*/
|
||||
margin: 1em 0 2em 0;
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
function update() {
|
||||
const r = Math.random()
|
||||
document.body.innerHTML = `
|
||||
Average ops/second
|
||||
<img src="avg-ops-per-sec.svg?${r}">
|
||||
|
||||
Average throughput in megabytes
|
||||
<img src="avg-throughput.svg?${r}">
|
||||
|
||||
Min–max parse time (lower is better)
|
||||
<img src="minmax-parse-time.svg?${r}">
|
||||
`
|
||||
|
||||
// min-mean-max candlestick ops/second
|
||||
// <img src="min-mean-max-candlestick.svg?${r}">
|
||||
}
|
||||
|
||||
update()
|
||||
// setInterval(update, 500)
|
||||
setTimeout(update, 1000)
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
test/benchmark/results/minmax-parse-time.svg
Normal file
1
test/benchmark/results/minmax-parse-time.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.9 KiB |
285
test/benchmark/samples/README.md
Executable file
285
test/benchmark/samples/README.md
Executable file
|
|
@ -0,0 +1,285 @@
|
|||
CommonMark
|
||||
==========
|
||||
|
||||
CommonMark is a rationalized version of Markdown syntax,
|
||||
with a [spec][the spec] and BSD3-licensed reference
|
||||
implementations in C and JavaScript.
|
||||
|
||||
[Try it now!](http://spec.commonmark.org/dingus.html)
|
||||
|
||||
The implementations
|
||||
-------------------
|
||||
|
||||
The C implementation provides both a shared library (`libcmark`) and a
|
||||
standalone program `cmark` that converts CommonMark to HTML. It is
|
||||
written in standard C99 and has no library dependencies. The parser is
|
||||
very fast (see [benchmarks](benchmarks.md)).
|
||||
|
||||
It is easy to use `libcmark` in python, lua, ruby, and other dynamic
|
||||
languages: see the `wrappers/` subdirectory for some simple examples.
|
||||
|
||||
The JavaScript implementation provides both an NPM package and a
|
||||
single JavaScript file, with no dependencies, that can be linked into
|
||||
an HTML page. For further information, see the
|
||||
[README in the js directory](js/README.md).
|
||||
|
||||
**A note on security:**
|
||||
Neither implementation attempts to sanitize link attributes or
|
||||
raw HTML. If you use these libraries in applications that accept
|
||||
untrusted user input, you must run the output through an HTML
|
||||
sanitizer to protect against
|
||||
[XSS attacks](http://en.wikipedia.org/wiki/Cross-site_scripting).
|
||||
|
||||
Installing (C)
|
||||
--------------
|
||||
|
||||
Building the C program (`cmark`) and shared library (`libcmark`)
|
||||
requires [cmake]. If you modify `scanners.re`, then you will also
|
||||
need [re2c], which is used to generate `scanners.c` from
|
||||
`scanners.re`. We have included a pre-generated `scanners.c` in
|
||||
the repository to reduce build dependencies.
|
||||
|
||||
If you have GNU make, you can simply `make`, `make test`, and `make
|
||||
install`. This calls [cmake] to create a `Makefile` in the `build`
|
||||
directory, then uses that `Makefile` to create the executable and
|
||||
library. The binaries can be found in `build/src`.
|
||||
|
||||
For a more portable method, you can use [cmake] manually. [cmake] knows
|
||||
how to create build environments for many build systems. For example,
|
||||
on FreeBSD:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. # optionally: -DCMAKE_INSTALL_PREFIX=path
|
||||
make # executable will be created as build/src/cmark
|
||||
make test
|
||||
make install
|
||||
|
||||
Or, to create Xcode project files on OSX:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -G Xcode ..
|
||||
make
|
||||
make test
|
||||
make install
|
||||
|
||||
The GNU Makefile also provides a few other targets for developers.
|
||||
To run a benchmark:
|
||||
|
||||
make bench
|
||||
|
||||
To run a "fuzz test" against ten long randomly generated inputs:
|
||||
|
||||
make fuzztest
|
||||
|
||||
To run a test for memory leaks using `valgrind`:
|
||||
|
||||
make leakcheck
|
||||
|
||||
To reformat source code using `astyle`:
|
||||
|
||||
make astyle
|
||||
|
||||
To make a release tarball and zip archive:
|
||||
|
||||
make archive
|
||||
|
||||
|
||||
Compiling for Windows
|
||||
---------------------
|
||||
|
||||
To compile with MSVC and NMAKE:
|
||||
|
||||
nmake
|
||||
|
||||
You can cross-compile a Windows binary and dll on linux if you have the
|
||||
`mingw32` compiler:
|
||||
|
||||
make mingw
|
||||
|
||||
The binaries will be in `build-mingw/windows/bin`.
|
||||
|
||||
Installing (JavaScript)
|
||||
-----------------------
|
||||
|
||||
The JavaScript library can be installed through `npm`:
|
||||
|
||||
npm install commonmark
|
||||
|
||||
This includes a command-line converter called `commonmark`.
|
||||
|
||||
If you want to use it in a client application, you can fetch
|
||||
a pre-built copy of `commonmark.js` from
|
||||
<http://spec.commonmark.org/js/commonmark.js>.
|
||||
|
||||
For further information, see the
|
||||
[README in the js directory](js/README.md).
|
||||
|
||||
The spec
|
||||
--------
|
||||
|
||||
[The spec] contains over 500 embedded examples which serve as conformance
|
||||
tests. To run the tests using an executable `$PROG`:
|
||||
|
||||
python3 test/spec_tests.py --program $PROG
|
||||
|
||||
If you want to extract the raw test data from the spec without
|
||||
actually running the tests, you can do:
|
||||
|
||||
python3 test/spec_tests.py --dump-tests
|
||||
|
||||
and you'll get all the tests in JSON format.
|
||||
|
||||
[The spec]: http://spec.commonmark.org/0.13/
|
||||
|
||||
The source of [the spec] is `spec.txt`. This is basically a Markdown
|
||||
file, with code examples written in a shorthand form:
|
||||
|
||||
.
|
||||
Markdown source
|
||||
.
|
||||
expected HTML output
|
||||
.
|
||||
|
||||
To build an HTML version of the spec, do `make spec.html`. To build a
|
||||
PDF version, do `make spec.pdf`. (Creating a PDF requires [pandoc]
|
||||
and a LaTeX installation. Creating the HTML version requires only
|
||||
`libcmark` and `python3`.)
|
||||
|
||||
The spec is written from the point of view of the human writer, not
|
||||
the computer reader. It is not an algorithm---an English translation of
|
||||
a computer program---but a declarative description of what counts as a block
|
||||
quote, a code block, and each of the other structural elements that can
|
||||
make up a Markdown document.
|
||||
|
||||
Because John Gruber's [canonical syntax
|
||||
description](http://daringfireball.net/projects/markdown/syntax) leaves
|
||||
many aspects of the syntax undetermined, writing a precise spec requires
|
||||
making a large number of decisions, many of them somewhat arbitrary.
|
||||
In making them, we have appealed to existing conventions and
|
||||
considerations of simplicity, readability, expressive power, and
|
||||
consistency. We have tried to ensure that "normal" documents in the many
|
||||
incompatible existing implementations of Markdown will render, as far as
|
||||
possible, as their authors intended. And we have tried to make the rules
|
||||
for different elements work together harmoniously. In places where
|
||||
different decisions could have been made (for example, the rules
|
||||
governing list indentation), we have explained the rationale for
|
||||
my choices. In a few cases, we have departed slightly from the canonical
|
||||
syntax description, in ways that we think further the goals of Markdown
|
||||
as stated in that description.
|
||||
|
||||
For the most part, we have limited ourselves to the basic elements
|
||||
described in Gruber's canonical syntax description, eschewing extensions
|
||||
like footnotes and definition lists. It is important to get the core
|
||||
right before considering such things. However, we have included a visible
|
||||
syntax for line breaks and fenced code blocks.
|
||||
|
||||
Differences from original Markdown
|
||||
----------------------------------
|
||||
|
||||
There are only a few places where this spec says things that contradict
|
||||
the canonical syntax description:
|
||||
|
||||
- It allows all punctuation symbols to be backslash-escaped,
|
||||
not just the symbols with special meanings in Markdown. We found
|
||||
that it was just too hard to remember which symbols could be
|
||||
escaped.
|
||||
|
||||
- It introduces an alternative syntax for hard line
|
||||
breaks, a backslash at the end of the line, supplementing the
|
||||
two-spaces-at-the-end-of-line rule. This is motivated by persistent
|
||||
complaints about the “invisible” nature of the two-space rule.
|
||||
|
||||
- Link syntax has been made a bit more predictable (in a
|
||||
backwards-compatible way). For example, `Markdown.pl` allows single
|
||||
quotes around a title in inline links, but not in reference links.
|
||||
This kind of difference is really hard for users to remember, so the
|
||||
spec allows single quotes in both contexts.
|
||||
|
||||
- The rule for HTML blocks differs, though in most real cases it
|
||||
shouldn't make a difference. (See the section on HTML Blocks
|
||||
for details.) The spec's proposal makes it easy to include Markdown
|
||||
inside HTML block-level tags, if you want to, but also allows you to
|
||||
exclude this. It is also makes parsing much easier, avoiding
|
||||
expensive backtracking.
|
||||
|
||||
- It does not collapse adjacent bird-track blocks into a single
|
||||
blockquote:
|
||||
|
||||
> this is two
|
||||
|
||||
> blockquotes
|
||||
|
||||
> this is a single
|
||||
>
|
||||
> blockquote with two paragraphs
|
||||
|
||||
- Rules for content in lists differ in a few respects, though (as with
|
||||
HTML blocks), most lists in existing documents should render as
|
||||
intended. There is some discussion of the choice points and
|
||||
differences in the subsection of List Items entitled Motivation.
|
||||
We think that the spec's proposal does better than any existing
|
||||
implementation in rendering lists the way a human writer or reader
|
||||
would intuitively understand them. (We could give numerous examples
|
||||
of perfectly natural looking lists that nearly every existing
|
||||
implementation flubs up.)
|
||||
|
||||
- The spec stipulates that two blank lines break out of all list
|
||||
contexts. This is an attempt to deal with issues that often come up
|
||||
when someone wants to have two adjacent lists, or a list followed by
|
||||
an indented code block.
|
||||
|
||||
- Changing bullet characters, or changing from bullets to numbers or
|
||||
vice versa, starts a new list. We think that is almost always going
|
||||
to be the writer's intent.
|
||||
|
||||
- The number that begins an ordered list item may be followed by
|
||||
either `.` or `)`. Changing the delimiter style starts a new
|
||||
list.
|
||||
|
||||
- The start number of an ordered list is significant.
|
||||
|
||||
- Fenced code blocks are supported, delimited by either
|
||||
backticks (```` ``` ```` or tildes (` ~~~ `).
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
There is a [forum for discussing
|
||||
CommonMark](http://talk.commonmark.org); you should use it instead of
|
||||
github issues for questions and possibly open-ended discussions.
|
||||
Use the [github issue tracker](http://github.com/jgm/CommonMark/issues)
|
||||
only for simple, clear, actionable issues.
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
The spec was written by John MacFarlane, drawing on
|
||||
|
||||
- his experience writing and maintaining Markdown implementations in several
|
||||
languages, including the first Markdown parser not based on regular
|
||||
expression substitutions ([pandoc](http://github.com/jgm/pandoc)) and
|
||||
the first markdown parsers based on PEG grammars
|
||||
([peg-markdown](http://github.com/jgm/peg-markdown),
|
||||
[lunamark](http://github.com/jgm/lunamark))
|
||||
- a detailed examination of the differences between existing Markdown
|
||||
implementations using [BabelMark 2](http://johnmacfarlane.net/babelmark2/),
|
||||
and
|
||||
- extensive discussions with David Greenspan, Jeff Atwood, Vicent
|
||||
Marti, Neil Williams, and Benjamin Dumke-von der Ehe.
|
||||
|
||||
John MacFarlane was also responsible for the original versions of the
|
||||
C and JavaScript implementations. The block parsing algorithm was
|
||||
worked out together with David Greenspan. Vicent Marti
|
||||
optimized the C implementation for performance, increasing its speed
|
||||
tenfold. Kārlis Gaņģis helped work out a better parsing algorithm
|
||||
for links and emphasis, eliminating several worst-case performance
|
||||
issues. Nick Wellnhofer contributed many improvements, including
|
||||
most of the C library's API and its test harness. Vitaly Puzrin
|
||||
has offered much good advice about the JavaScript implementation.
|
||||
|
||||
[cmake]: http://www.cmake.org/download/
|
||||
[pandoc]: http://johnmacfarlane.net/pandoc/
|
||||
[re2c]: http://re2c.org
|
||||
|
||||
16
test/benchmark/samples/block-bq-flat.md
Executable file
16
test/benchmark/samples/block-bq-flat.md
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
> the simple example of a blockquote
|
||||
> the simple example of a blockquote
|
||||
> the simple example of a blockquote
|
||||
> the simple example of a blockquote
|
||||
... continuation
|
||||
... continuation
|
||||
... continuation
|
||||
... continuation
|
||||
|
||||
empty blockquote:
|
||||
|
||||
>
|
||||
>
|
||||
>
|
||||
>
|
||||
|
||||
13
test/benchmark/samples/block-bq-nested.md
Executable file
13
test/benchmark/samples/block-bq-nested.md
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
>>>>>> deeply nested blockquote
|
||||
>>>>> deeply nested blockquote
|
||||
>>>> deeply nested blockquote
|
||||
>>> deeply nested blockquote
|
||||
>> deeply nested blockquote
|
||||
> deeply nested blockquote
|
||||
|
||||
> deeply nested blockquote
|
||||
>> deeply nested blockquote
|
||||
>>> deeply nested blockquote
|
||||
>>>> deeply nested blockquote
|
||||
>>>>> deeply nested blockquote
|
||||
>>>>>> deeply nested blockquote
|
||||
11
test/benchmark/samples/block-code.md
Executable file
11
test/benchmark/samples/block-code.md
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
an
|
||||
example
|
||||
|
||||
of
|
||||
|
||||
|
||||
|
||||
a code
|
||||
block
|
||||
|
||||
14
test/benchmark/samples/block-fences.md
Executable file
14
test/benchmark/samples/block-fences.md
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
``````````text
|
||||
an
|
||||
example
|
||||
```
|
||||
of
|
||||
|
||||
|
||||
a fenced
|
||||
```
|
||||
code
|
||||
block
|
||||
``````````
|
||||
|
||||
9
test/benchmark/samples/block-heading.md
Executable file
9
test/benchmark/samples/block-heading.md
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
# heading
|
||||
### heading
|
||||
##### heading
|
||||
|
||||
# heading #
|
||||
### heading ###
|
||||
##### heading \#\#\#\#\######
|
||||
|
||||
############ not a heading
|
||||
10
test/benchmark/samples/block-hr.md
Executable file
10
test/benchmark/samples/block-hr.md
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
* * * * *
|
||||
|
||||
- - - - -
|
||||
|
||||
________
|
||||
|
||||
|
||||
************************* text
|
||||
|
||||
32
test/benchmark/samples/block-html.md
Executable file
32
test/benchmark/samples/block-html.md
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
<div class="this is an html block">
|
||||
|
||||
blah blah
|
||||
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
**test**
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
|
||||
test
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
<![CDATA[
|
||||
[[[[[[[[[[[... *cdata section - this should not be parsed* ...]]]]]]]]]]]
|
||||
]]>
|
||||
|
||||
8
test/benchmark/samples/block-lheading.md
Executable file
8
test/benchmark/samples/block-lheading.md
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
heading
|
||||
---
|
||||
|
||||
heading
|
||||
===================================
|
||||
|
||||
not a heading
|
||||
----------------------------------- text
|
||||
67
test/benchmark/samples/block-list-flat.md
Executable file
67
test/benchmark/samples/block-list-flat.md
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
- tidy
|
||||
- bullet
|
||||
- list
|
||||
|
||||
|
||||
- loose
|
||||
|
||||
- bullet
|
||||
|
||||
- list
|
||||
|
||||
|
||||
0. ordered
|
||||
1. list
|
||||
2. example
|
||||
|
||||
|
||||
-
|
||||
-
|
||||
-
|
||||
-
|
||||
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
|
||||
- an example
|
||||
of a list item
|
||||
with a continuation
|
||||
|
||||
this part is inside the list
|
||||
|
||||
this part is just a paragraph
|
||||
|
||||
|
||||
1. test
|
||||
- test
|
||||
1. test
|
||||
- test
|
||||
|
||||
|
||||
111111111111111111111111111111111111111111. is this a valid bullet?
|
||||
|
||||
- _________________________
|
||||
|
||||
- this
|
||||
- is
|
||||
|
||||
a
|
||||
|
||||
long
|
||||
- loose
|
||||
- list
|
||||
|
||||
- with
|
||||
- some
|
||||
|
||||
tidy
|
||||
|
||||
- list
|
||||
- items
|
||||
- in
|
||||
|
||||
- between
|
||||
- _________________________
|
||||
36
test/benchmark/samples/block-list-nested.md
Executable file
36
test/benchmark/samples/block-list-nested.md
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
|
||||
- this
|
||||
- is
|
||||
- a
|
||||
- deeply
|
||||
- nested
|
||||
- bullet
|
||||
- list
|
||||
|
||||
|
||||
1. this
|
||||
2. is
|
||||
3. a
|
||||
4. deeply
|
||||
5. nested
|
||||
6. unordered
|
||||
7. list
|
||||
|
||||
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 6
|
||||
- 5
|
||||
- 4
|
||||
- 3
|
||||
- 2
|
||||
- 1
|
||||
|
||||
|
||||
- - - - - - - - - deeply-nested one-element item
|
||||
|
||||
15
test/benchmark/samples/block-ref-flat.md
Executable file
15
test/benchmark/samples/block-ref-flat.md
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
[1] [2] [3] [1] [2] [3]
|
||||
|
||||
[looooooooooooooooooooooooooooooooooooooooooooooooooong label]
|
||||
|
||||
[1]: <http://something.example.com/foo/bar>
|
||||
[2]: http://something.example.com/foo/bar 'test'
|
||||
[3]:
|
||||
http://foo/bar
|
||||
[ looooooooooooooooooooooooooooooooooooooooooooooooooong label ]:
|
||||
111
|
||||
'test'
|
||||
[[[[[[[[[[[[[[[[[[[[ this should not slow down anything ]]]]]]]]]]]]]]]]]]]]: q
|
||||
(as long as it is not referenced anywhere)
|
||||
|
||||
[[[[[[[[[[[[[[[[[[[[]: this is not a valid reference
|
||||
17
test/benchmark/samples/block-ref-nested.md
Executable file
17
test/benchmark/samples/block-ref-nested.md
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
[[[[[[[foo]]]]]]]
|
||||
|
||||
[[[[[[[foo]]]]]]]: bar
|
||||
[[[[[[foo]]]]]]: bar
|
||||
[[[[[foo]]]]]: bar
|
||||
[[[[foo]]]]: bar
|
||||
[[[foo]]]: bar
|
||||
[[foo]]: bar
|
||||
[foo]: bar
|
||||
|
||||
[*[*[*[*[foo]*]*]*]*]
|
||||
|
||||
[*[*[*[*[foo]*]*]*]*]: bar
|
||||
[*[*[*[foo]*]*]*]: bar
|
||||
[*[*[foo]*]*]: bar
|
||||
[*[foo]*]: bar
|
||||
[foo]: bar
|
||||
14
test/benchmark/samples/inline-autolink.md
Executable file
14
test/benchmark/samples/inline-autolink.md
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
closed (valid) autolinks:
|
||||
|
||||
<ftp://1.2.3.4:21/path/foo>
|
||||
<http://foo.bar.baz?q=hello&id=22&boolean>
|
||||
<http://veeeeeeeeeeeeeeeeeeery.loooooooooooooooooooooooooooooooong.autolink/>
|
||||
<teeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeest@gmail.com>
|
||||
|
||||
these are not autolinks:
|
||||
|
||||
<ftp://1.2.3.4:21/path/foo
|
||||
<http://foo.bar.baz?q=hello&id=22&boolean
|
||||
<http://veeeeeeeeeeeeeeeeeeery.loooooooooooooooooooooooooooooooong.autolink
|
||||
<teeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeest@gmail.com
|
||||
< http://foo.bar.baz?q=hello&id=22&boolean >
|
||||
3
test/benchmark/samples/inline-backticks.md
Executable file
3
test/benchmark/samples/inline-backticks.md
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
`lots`of`backticks`
|
||||
|
||||
``i``wonder``how``this``will``be``parsed``
|
||||
5
test/benchmark/samples/inline-em-flat.md
Executable file
5
test/benchmark/samples/inline-em-flat.md
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
*this* *is* *your* *basic* *boring* *emphasis*
|
||||
|
||||
_this_ _is_ _your_ _basic_ _boring_ _emphasis_
|
||||
|
||||
**this** **is** **your** **basic** **boring** **emphasis**
|
||||
5
test/benchmark/samples/inline-em-nested.md
Executable file
5
test/benchmark/samples/inline-em-nested.md
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
*this *is *a *bunch* of* nested* emphases*
|
||||
|
||||
__this __is __a __bunch__ of__ nested__ emphases__
|
||||
|
||||
***this ***is ***a ***bunch*** of*** nested*** emphases***
|
||||
5
test/benchmark/samples/inline-em-worst.md
Executable file
5
test/benchmark/samples/inline-em-worst.md
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
*this *is *a *worst *case *for *em *backtracking
|
||||
|
||||
__this __is __a __worst __case __for __em __backtracking
|
||||
|
||||
***this ***is ***a ***worst ***case ***for ***em ***backtracking
|
||||
11
test/benchmark/samples/inline-entity.md
Executable file
11
test/benchmark/samples/inline-entity.md
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
entities:
|
||||
|
||||
& © Æ Ď ¾ ℋ ⅆ ∲
|
||||
|
||||
# Ӓ Ϡ �
|
||||
|
||||
non-entities:
|
||||
|
||||
&18900987654321234567890; &1234567890098765432123456789009876543212345678987654;
|
||||
|
||||
&qwertyuioppoiuytrewqwer; &oiuytrewqwertyuioiuytrewqwertyuioytrewqwertyuiiuytri;
|
||||
15
test/benchmark/samples/inline-escape.md
Executable file
15
test/benchmark/samples/inline-escape.md
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
\t\e\s\t\i\n\g \e\s\c\a\p\e \s\e\q\u\e\n\c\e\s
|
||||
|
||||
\!\\\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?
|
||||
|
||||
\@ \[ \] \^ \_ \` \{ \| \} \~ \- \'
|
||||
|
||||
\
|
||||
\\
|
||||
\\\
|
||||
\\\\
|
||||
\\\\\
|
||||
|
||||
\<this\> \<is\> \<not\> \<html\>
|
||||
|
||||
44
test/benchmark/samples/inline-html.md
Executable file
44
test/benchmark/samples/inline-html.md
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
Taking commonmark tests from the spec for benchmarking here:
|
||||
|
||||
<a><bab><c2c>
|
||||
|
||||
<a/><b2/>
|
||||
|
||||
<a /><b2
|
||||
data="foo" >
|
||||
|
||||
<a foo="bar" bam = 'baz <em>"</em>'
|
||||
_boolean zoop:33=zoop:33 />
|
||||
|
||||
<33> <__>
|
||||
|
||||
<a h*#ref="hi">
|
||||
|
||||
<a href="hi'> <a href=hi'>
|
||||
|
||||
< a><
|
||||
foo><bar/ >
|
||||
|
||||
<a href='bar'title=title>
|
||||
|
||||
</a>
|
||||
</foo >
|
||||
|
||||
</a href="foo">
|
||||
|
||||
foo <!-- this is a
|
||||
comment - with hyphen -->
|
||||
|
||||
foo <!-- not a comment -- two hyphens -->
|
||||
|
||||
foo <?php echo $a; ?>
|
||||
|
||||
foo <!ELEMENT br EMPTY>
|
||||
|
||||
foo <![CDATA[>&<]]>
|
||||
|
||||
<a href="ö">
|
||||
|
||||
<a href="\*">
|
||||
|
||||
<a href="\"">
|
||||
23
test/benchmark/samples/inline-links-flat.md
Executable file
23
test/benchmark/samples/inline-links-flat.md
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
Valid links:
|
||||
|
||||
[this is a link]()
|
||||
[this is a link](<http://something.example.com/foo/bar>)
|
||||
[this is a link](http://something.example.com/foo/bar 'test')
|
||||
![this is an image]()
|
||||

|
||||

|
||||
|
||||
[escape test](<\>\>\>\>\>\>\>\>\>\>\>\>\>\>> '\'\'\'\'\'\'\'\'\'\'\'\'\'\'')
|
||||
[escape test \]\]\]\]\]\]\]\]\]\]\]\]\]\]\]\]](\)\)\)\)\)\)\)\)\)\)\)\)\)\))
|
||||
|
||||
Invalid links:
|
||||
|
||||
[this is not a link
|
||||
|
||||
[this is not a link](
|
||||
|
||||
[this is not a link](http://something.example.com/foo/bar 'test'
|
||||
|
||||
[this is not a link](((((((((((((((((((((((((((((((((((((((((((((((
|
||||
|
||||
[this is not a link]((((((((((()))))))))) (((((((((()))))))))))
|
||||
13
test/benchmark/samples/inline-links-nested.md
Executable file
13
test/benchmark/samples/inline-links-nested.md
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
Valid links:
|
||||
|
||||
[[[[[[[[](test)](test)](test)](test)](test)](test)](test)]
|
||||
|
||||
[ [[[[[[[[[[[[[[[[[[ [](test) ]]]]]]]]]]]]]]]]]] ](test)
|
||||
|
||||
Invalid links:
|
||||
|
||||
[[[[[[[[[
|
||||
|
||||
[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [
|
||||
|
||||
 lobortis, sapien arcu mattis erat, vel aliquet sem urna et risus. Ut feugiat sapien vitae mi elementum laoreet. Suspendisse potenti. Aliquam erat nisl, aliquam pretium libero aliquet, sagittis eleifend nunc. In hac habitasse platea dictumst. Integer turpis augue, tincidunt dignissim mauris id, rhoncus dapibus purus. Maecenas et enim odio. Nullam massa metus, varius quis vehicula sed, pharetra mollis erat. In quis viverra velit. Vivamus placerat, est nec hendrerit varius, enim dui hendrerit magna, ut pulvinar nibh lorem vel lacus. Mauris a orci iaculis, hendrerit eros sed, gravida leo. In dictum mauris vel augue varius, ac ullamcorper nisl ornare. In eu posuere velit, ac fermentum arcu. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam sed malesuada leo, at interdum elit.
|
||||
|
||||
Nullam ut tincidunt nunc. [Pellentesque][1] metus lacus, commodo eget justo ut, rutrum varius nunc. Sed non rhoncus risus. Morbi sodales gravida pulvinar. Duis malesuada, odio volutpat elementum vulputate, massa magna scelerisque ante, et accumsan tellus nunc in sem. Donec mattis arcu et velit aliquet, non sagittis justo vestibulum. Suspendisse volutpat felis lectus, nec consequat ipsum mattis id. Donec dapibus vehicula facilisis. In tincidunt mi nisi, nec faucibus tortor euismod nec. Suspendisse ante ligula, aliquet vitae libero eu, vulputate dapibus libero. Sed bibendum, sapien at posuere interdum, libero est sollicitudin magna, ac gravida tellus purus eu ipsum. Proin ut quam arcu.
|
||||
|
||||
Suspendisse potenti. Donec ante velit, ornare at augue quis, tristique laoreet sem. Etiam in ipsum elit. Nullam cursus dolor sit amet nulla feugiat tristique. Phasellus ac tellus tincidunt, imperdiet purus eget, ullamcorper ipsum. Cras eu tincidunt sem. Nullam sed dapibus magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In id venenatis tortor. In consectetur sollicitudin pharetra. Etiam convallis nisi nunc, et aliquam turpis viverra sit amet. Maecenas faucibus sodales tortor. Suspendisse lobortis mi eu leo viverra volutpat. Pellentesque velit ante, vehicula sodales congue ut, elementum a urna. Cras tempor, ipsum eget luctus rhoncus, arcu ligula fermentum urna, vulputate pharetra enim enim non libero.
|
||||
|
||||
Proin diam quam, elementum in eleifend id, elementum et metus. Cras in justo consequat justo semper ultrices. Sed dignissim lectus a ante mollis, nec vulputate ante molestie. Proin in porta nunc. Etiam pulvinar turpis sed velit porttitor, vel adipiscing velit fringilla. Cras ac tellus vitae purus pharetra tincidunt. Sed cursus aliquet aliquet. Cras eleifend commodo malesuada. In turpis turpis, ullamcorper ut tincidunt a, ullamcorper a nunc. Etiam luctus tellus ac dapibus gravida. Ut nec lacus laoreet neque ullamcorper volutpat.
|
||||
|
||||
Nunc et leo erat. Aenean mattis ultrices lorem, eget adipiscing dolor ultricies eu. In hac habitasse platea dictumst. Vivamus cursus feugiat sapien quis aliquam. Mauris quam libero, porta vel volutpat ut, blandit a purus. Vivamus vestibulum dui vel tortor molestie, sit amet feugiat sem commodo. Nulla facilisi. Sed molestie arcu eget tellus vestibulum tristique.
|
||||
|
||||
[1]: https://github.com/markdown-it
|
||||
18
test/benchmark/samples/rawtabs.md
Executable file
18
test/benchmark/samples/rawtabs.md
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
this is a test for tab expansion, be careful not to replace them with spaces
|
||||
|
||||
1 4444
|
||||
22 333
|
||||
333 22
|
||||
4444 1
|
||||
|
||||
|
||||
tab-indented line
|
||||
space-indented line
|
||||
tab-indented line
|
||||
|
||||
|
||||
a lot of spaces in between here
|
||||
|
||||
a lot of tabs in between here
|
||||
|
||||
9710
test/benchmark/samples/spec.md
Executable file
9710
test/benchmark/samples/spec.md
Executable file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue