Skip to content

Commit

Permalink
Performance improvements, Add BlockParser.Trigger
Browse files Browse the repository at this point in the history
  • Loading branch information
yuin committed Aug 30, 2019
1 parent 667a292 commit 187643a
Show file tree
Hide file tree
Showing 18 changed files with 386 additions and 51 deletions.
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,25 @@ if err := goldmark.Convert(source, &buf); err != nil {
}
```

With options
------------------------------

```go
var buf bytes.Buffer
if err := goldmark.Convert(source, &buf, parser.WithWorkers(16)); err != nil {
panic(err)
}
```

| Functional option | Type | Description |
| ----------------- | ---- | ----------- |
| `parser.WithContext` | A parser.Context | Context for the parsing phase. |
| parser.WithWorkers | int | Number of goroutines that execute concurrent inline element parsing. |

`parser.WithWorkers` may make performance better a little if markdown text
is relatively large. Otherwise, `parser.Workers` may cause performance degradation due to
goroutine overheads.

Custom parser and renderer
--------------------------
```go
Expand Down Expand Up @@ -236,10 +255,16 @@ blackfriday v2 can not simply be compared with other Commonmark compliant librar
Though goldmark builds clean extensible AST structure and get full compliance with
Commonmark, it is resonably fast and less memory consumption.

This benchmark parses a relatively large markdown text. In such text, concurrent parsing
makes performance better a little.

```
BenchmarkGoldMark-4 200 6388385 ns/op 2085552 B/op 13856 allocs/op
BenchmarkGolangCommonMark-4 200 7056577 ns/op 2974119 B/op 18828 allocs/op
BenchmarkBlackFriday-4 300 5635122 ns/op 3341668 B/op 20057 allocs/op
BenchmarkMarkdown/Blackfriday-v2-4 300 5316935 ns/op 3321072 B/op 20050 allocs/op
BenchmarkMarkdown/GoldMark(workers=16)-4 300 5506219 ns/op 2702358 B/op 14494 allocs/op
BenchmarkMarkdown/GoldMark-4 200 5903779 ns/op 2594304 B/op 13861 allocs/op
BenchmarkMarkdown/CommonMark-4 200 7147659 ns/op 2752977 B/op 18827 allocs/op
BenchmarkMarkdown/Lute-4 200 5930621 ns/op 2839712 B/op 21165 allocs/op
BenchmarkMarkdown/GoMarkdown-4 10 120953070 ns/op 2192278 B/op 22174 allocs/op
```

### against cmark(A CommonMark reference implementation written in c)
Expand All @@ -248,12 +273,15 @@ BenchmarkBlackFriday-4 300 5635122 ns/op 3341668
----------- cmark -----------
file: _data.md
iteration: 50
average: 0.0050112160 sec
go run ./goldmark_benchmark.go
average: 0.0047014618 sec
------- goldmark -------
file: _data.md
iteration: 50
average: 0.0064833820 sec
average: 0.0052624750 sec
------- goldmark(workers=16) -------
file: _data.md
iteration: 50
average: 0.0044918780 sec
```

As you can see, goldmark performs pretty much equally to the cmark.
Expand Down
15 changes: 15 additions & 0 deletions _benchmark/cmark/goldmark_benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
)

Expand Down Expand Up @@ -42,4 +43,18 @@ func main() {
fmt.Printf("file: %s\n", file)
fmt.Printf("iteration: %d\n", n)
fmt.Printf("average: %.10f sec\n", float64((int64(sum)/int64(n)))/1000000000.0)

sum = time.Duration(0)
for i := 0; i < n; i++ {
start := time.Now()
out.Reset()
if err := markdown.Convert(source, &out, parser.WithWorkers(16)); err != nil {
panic(err)
}
sum += time.Since(start)
}
fmt.Printf("------- goldmark(workers=16) -------\n")
fmt.Printf("file: %s\n", file)
fmt.Printf("iteration: %d\n", n)
fmt.Printf("average: %.10f sec\n", float64((int64(sum)/int64(n)))/1000000000.0)
}
33 changes: 32 additions & 1 deletion _benchmark/go/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import (

gomarkdown "github.com/gomarkdown/markdown"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"
"gitlab.com/golang-commonmark/markdown"

bf1 "github.com/russross/blackfriday"
bf2 "gopkg.in/russross/blackfriday.v2"

"github.com/b3log/lute"
)

func BenchmarkMarkdown(b *testing.B) {
Expand All @@ -31,13 +35,25 @@ func BenchmarkMarkdown(b *testing.B) {
doBenchmark(b, r)
})

b.Run("GoldMark(workers=16)", func(b *testing.B) {
markdown := goldmark.New(
goldmark.WithRendererOptions(html.WithXHTML(), html.WithUnsafe()),
)
r := func(src []byte) ([]byte, error) {
var out bytes.Buffer
err := markdown.Convert(src, &out, parser.WithWorkers(16))
return out.Bytes(), err
}
doBenchmark(b, r)
})

b.Run("GoldMark", func(b *testing.B) {
markdown := goldmark.New(
goldmark.WithRendererOptions(html.WithXHTML(), html.WithUnsafe()),
)
r := func(src []byte) ([]byte, error) {
var out bytes.Buffer
err := markdown.Convert(src, &out)
err := markdown.Convert(src, &out, parser.WithWorkers(0))
return out.Bytes(), err
}
doBenchmark(b, r)
Expand All @@ -53,13 +69,28 @@ func BenchmarkMarkdown(b *testing.B) {
doBenchmark(b, r)
})

b.Run("Lute", func(b *testing.B) {
luteEngine := lute.New(
lute.GFM(false),
lute.CodeSyntaxHighlight(false),
lute.SoftBreak2HardBreak(false),
lute.AutoSpace(false),
lute.FixTermTypo(false))
r := func(src []byte) ([]byte, error) {
out, err := luteEngine.FormatStr("Benchmark", util.BytesToReadOnlyString(src))

This comment has been minimized.

Copy link
@88250

88250 Aug 30, 2019

Contributor

Should use MarkdownStr instead of FormatStr, thanks.

This comment has been minimized.

Copy link
@yuin

yuin Aug 30, 2019

Author Owner

@88250 Oops, I've fixed this now.

return util.StringToReadOnlyBytes(out), err
}
doBenchmark(b, r)
})

b.Run("GoMarkdown", func(b *testing.B) {
r := func(src []byte) ([]byte, error) {
out := gomarkdown.ToHTML(src, nil, nil)
return out, nil
}
doBenchmark(b, r)
})

}

// The different frameworks have different APIs. Create an adapter that
Expand Down
8 changes: 8 additions & 0 deletions extension/definition_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func NewDefinitionListParser() parser.BlockParser {
return defaultDefinitionListParser
}

func (b *definitionListParser) Trigger() []byte {
return []byte{':'}
}

func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
if _, ok := parent.(*ast.DefinitionList); ok {
return nil, parser.NoChildren
Expand Down Expand Up @@ -105,6 +109,10 @@ func NewDefinitionDescriptionParser() parser.BlockParser {
return defaultDefinitionDescriptionParser
}

func (b *definitionDescriptionParser) Trigger() []byte {
return []byte{':'}
}

func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
line, _ := reader.PeekLine()
pos := pc.BlockOffset()
Expand Down
6 changes: 5 additions & 1 deletion extension/footnote.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func NewFootnoteBlockParser() parser.BlockParser {
return defaultFootnoteBlockParser
}

func (b *footnoteBlockParser) Trigger() []byte {
return []byte{'['}
}

func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
line, segment := reader.PeekLine()
pos := pc.BlockOffset()
Expand Down Expand Up @@ -136,7 +140,7 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
block.Advance(closes + 1)

var list *ast.FootnoteList
if tlist := pc.Get(footnoteListKey); tlist != nil {
if tlist := pc.Root().Get(footnoteListKey); tlist != nil {
list = tlist.(*ast.FootnoteList)
}
if list == nil {
Expand Down
4 changes: 4 additions & 0 deletions parser/atx_heading.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ func NewATXHeadingParser(opts ...HeadingOption) BlockParser {
return p
}

func (b *atxHeadingParser) Trigger() []byte {
return []byte{'#'}
}

func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
line, segment := reader.PeekLine()
pos := pc.BlockOffset()
Expand Down
4 changes: 4 additions & 0 deletions parser/blockquote.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func (b *blockquoteParser) process(reader text.Reader) bool {
return true
}

func (b *blockquoteParser) Trigger() []byte {
return []byte{'>'}
}

func (b *blockquoteParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
if b.process(reader) {
return ast.NewBlockquote(), HasChildren
Expand Down
4 changes: 4 additions & 0 deletions parser/code_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ func NewCodeBlockParser() BlockParser {
return defaultCodeBlockParser
}

func (b *codeBlockParser) Trigger() []byte {
return nil
}

func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
line, segment := reader.PeekLine()
pos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
Expand Down
4 changes: 4 additions & 0 deletions parser/fcode_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ type fenceData struct {

var fencedCodeBlockInfoKey = NewContextKey()

func (b *fencedCodeBlockParser) Trigger() []byte {
return []byte{'~', '`'}
}

func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
line, segment := reader.PeekLine()
pos := pc.BlockOffset()
Expand Down
4 changes: 4 additions & 0 deletions parser/html_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ func NewHTMLBlockParser() BlockParser {
return defaultHtmlBlockParser
}

func (b *htmlBlockParser) Trigger() []byte {
return []byte{'<'}
}

func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
var node *ast.HTMLBlock
line, segment := reader.PeekLine()
Expand Down
4 changes: 2 additions & 2 deletions parser/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
block.SetPosition(l, pos)
ssegment := text.NewSegment(last.Segment.Stop, segment.Start)
maybeReference := block.Value(ssegment)
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
ref, ok := pc.Root().Reference(util.ToLinkReference(maybeReference))
if !ok {
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
return nil
Expand Down Expand Up @@ -243,7 +243,7 @@ func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, b
maybeReference = block.Value(ssegment)
}

ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
ref, ok := pc.Root().Reference(util.ToLinkReference(maybeReference))
if !ok {
return nil, true
}
Expand Down
4 changes: 4 additions & 0 deletions parser/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ func NewListParser() BlockParser {
return defaultListParser
}

func (b *listParser) Trigger() []byte {
return []byte{'-', '+', '*', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
}

func (b *listParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
last := pc.LastOpenedBlock().Node
if _, lok := last.(*ast.List); lok || pc.Get(skipListParser) != nil {
Expand Down
4 changes: 4 additions & 0 deletions parser/list_item.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ func NewListItemParser() BlockParser {
var skipListParser = NewContextKey()
var skipListParserValue interface{} = true

func (b *listItemParser) Trigger() []byte {
return []byte{'-', '+', '*', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
}

func (b *listItemParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
list, lok := parent.(*ast.List)
if !lok { // list item must be a child of a list
Expand Down
4 changes: 4 additions & 0 deletions parser/paragraph.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ func NewParagraphParser() BlockParser {
return defaultParagraphParser
}

func (b *paragraphParser) Trigger() []byte {
return nil
}

func (b *paragraphParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
_, segment := reader.PeekLine()
segment = segment.TrimLeftSpace(reader.Source())
Expand Down
Loading

0 comments on commit 187643a

Please sign in to comment.