Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions OfficeIMO.Markdown.Benchmarks/MarkdownBenchmarkCorpus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
namespace OfficeIMO.Markdown.Benchmarks;

internal static class MarkdownBenchmarkCorpus {
private static readonly IReadOnlyDictionary<string, string> Corpora = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
["PortableReadme"] = BuildPortableReadme(),
["Transcript"] = BuildTranscript(),
["TechnicalDoc"] = BuildTechnicalDoc()
};

public static IEnumerable<string> Names => Corpora.Keys;

public static string Get(string name) => Corpora[name];

private static string BuildPortableReadme() {
var section = """
# OfficeIMO Markdown Overview

OfficeIMO.Markdown can build, parse, inspect, and render Markdown for chat, docs, and reports.

## Features

- Fluent builders for headings, paragraphs, tables, lists, and fenced code
- Typed document queries for headings, list items, top-level blocks, and descendants
- HTML rendering with anchor links and table-of-contents support

## Sample Table

| Area | Status | Notes |
| --- | --- | --- |
| Reader | Active | Typed document traversal |
| Renderer | Active | Portable HTML output |
| Docs | Improving | More examples and contract docs |

## Example

```csharp
var doc = MarkdownDoc.Create();
doc.H1("Status");
doc.P("Everything is working.");
```

Additional notes:

1. Keep README output readable.
2. Keep parser behavior predictable.
3. Keep portability in mind.

> This quote keeps the corpus close to a normal README.

""";

return string.Concat(Enumerable.Repeat(section + Environment.NewLine, 12));
}

private static string BuildTranscript() {
var section = """
## User

Please summarize the deployment status and list any blockers.

## Assistant

Deployment summary:

1. API rollout completed in staging.
2. Windows smoke tests are still running.
3. Package-mode validation passed locally.

### Notes

- Environment: `staging`
- Region: `westeurope`
- Follow-up: confirm package publication plan

```json
{
"rollout": "staging",
"result": "pending-final-check"
}
```

""";

return string.Concat(Enumerable.Repeat(section + Environment.NewLine, 20));
}

private static string BuildTechnicalDoc() {
var section = """
# Rendering Contract

## Normalization

The pipeline first normalizes transcript boundaries, then applies runtime-specific rendering options.

### Constraints

- Preserve literal fenced code blocks
- Preserve angle-bracket links
- Keep nested list structure stable

## Reference

| Component | Responsibility |
| --- | --- |
| Preparation | App-side transcript cleanup |
| Contract | Shared normalization across render/export |
| Runtime | Renderer and DOCX capability probing |

> [!NOTE]
> OfficeIMO-specific callouts are part of the default profile.

### Example

```markdown
> [!TIP]
> Keep package-mode validation in CI.
```

Trailing paragraph with **bold**, _emphasis_, `code`, and [a link](https://example.com).

""";

return string.Concat(Enumerable.Repeat(section + Environment.NewLine, 16));
}
}
61 changes: 61 additions & 0 deletions OfficeIMO.Markdown.Benchmarks/MarkdownBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Markdig;

namespace OfficeIMO.Markdown.Benchmarks;

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class MarkdownParseBenchmarks {
private MarkdownReaderOptions _portableOptions = null!;
private string _markdown = string.Empty;

[ParamsSource(nameof(CorpusNames))]
public string CorpusName { get; set; } = string.Empty;

public IEnumerable<string> CorpusNames() => MarkdownBenchmarkCorpus.Names;

[GlobalSetup]
public void Setup() {
_portableOptions = MarkdownReaderOptions.CreatePortableProfile();
_markdown = MarkdownBenchmarkCorpus.Get(CorpusName);
}

[Benchmark(Baseline = true)]
public MarkdownDoc OfficeIMO_Parse_Default() => MarkdownReader.Parse(_markdown);

[Benchmark]
public MarkdownDoc OfficeIMO_Parse_Portable() => MarkdownReader.Parse(_markdown, _portableOptions);

[Benchmark]
public Markdig.Syntax.MarkdownDocument Markdig_Parse() => Markdig.Markdown.Parse(_markdown);
}

[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class MarkdownHtmlBenchmarks {
private static readonly MarkdownPipeline MarkdigPipeline = new MarkdownPipelineBuilder().Build();

private MarkdownReaderOptions _portableOptions = null!;
private string _markdown = string.Empty;

[ParamsSource(nameof(CorpusNames))]
public string CorpusName { get; set; } = string.Empty;

public IEnumerable<string> CorpusNames() => MarkdownBenchmarkCorpus.Names;

[GlobalSetup]
public void Setup() {
_portableOptions = MarkdownReaderOptions.CreatePortableProfile();
_markdown = MarkdownBenchmarkCorpus.Get(CorpusName);
}

[Benchmark(Baseline = true)]
public string OfficeIMO_ToHtml_Default() => MarkdownReader.Parse(_markdown).ToHtml();

[Benchmark]
public string OfficeIMO_ToHtml_Portable() => MarkdownReader.Parse(_markdown, _portableOptions).ToHtml();

[Benchmark]
public string Markdig_ToHtml() => Markdig.Markdown.ToHtml(_markdown, MarkdigPipeline);
}
21 changes: 21 additions & 0 deletions OfficeIMO.Markdown.Benchmarks/OfficeIMO.Markdown.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>Latest</LangVersion>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="Markdig" Version="1.1.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\OfficeIMO.Markdown\OfficeIMO.Markdown.csproj" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions OfficeIMO.Markdown.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
21 changes: 21 additions & 0 deletions OfficeIMO.Markdown.Benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# OfficeIMO.Markdown.Benchmarks

Internal benchmark harness for `OfficeIMO.Markdown`.

It measures representative parse and HTML-render workloads for:

- OfficeIMO default reader behavior
- OfficeIMO portable reader profile
- the internal comparison baseline used in parity work

Run with:

```powershell
dotnet run -c Release --project .\OfficeIMO.Markdown.Benchmarks\OfficeIMO.Markdown.Benchmarks.csproj
```

Filter a specific benchmark class with:

```powershell
dotnet run -c Release --project .\OfficeIMO.Markdown.Benchmarks\OfficeIMO.Markdown.Benchmarks.csproj -- --filter *MarkdownParseBenchmarks*
```
14 changes: 14 additions & 0 deletions OfficeIMO.sln
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OfficeIMO.Reader.Json", "Of
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OfficeIMO.Reader.Xml", "OfficeIMO.Reader.Xml\OfficeIMO.Reader.Xml.csproj", "{DAAF4EE3-4254-40CA-A7B2-A0CFD692025A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OfficeIMO.Markdown.Benchmarks", "OfficeIMO.Markdown.Benchmarks\OfficeIMO.Markdown.Benchmarks.csproj", "{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -480,6 +482,18 @@ Global
{DAAF4EE3-4254-40CA-A7B2-A0CFD692025A}.Release|x64.Build.0 = Release|Any CPU
{DAAF4EE3-4254-40CA-A7B2-A0CFD692025A}.Release|x86.ActiveCfg = Release|Any CPU
{DAAF4EE3-4254-40CA-A7B2-A0CFD692025A}.Release|x86.Build.0 = Release|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Debug|x64.ActiveCfg = Debug|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Debug|x64.Build.0 = Debug|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Debug|x86.ActiveCfg = Debug|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Debug|x86.Build.0 = Debug|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Release|Any CPU.Build.0 = Release|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Release|x64.ActiveCfg = Release|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Release|x64.Build.0 = Release|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Release|x86.ActiveCfg = Release|Any CPU
{9EEFC83D-8CBF-461A-95A8-5DD685310D0C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading