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
60 changes: 60 additions & 0 deletions src/ReverseMarkdown.Benchmark/Benchmark.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Benchmark Results for ReverseMarkdown

**Legends**

```
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
Gen0 : GC Generation 0 collects per 1000 operations
Gen1 : GC Generation 1 collects per 1000 operations
Gen2 : GC Generation 2 collects per 1000 operations
Allocated : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
Ratio : Mean of the method divided by the mean of the baseline
1 s : 1 Second (1 sec)
1 ms : 1 Millisecond (1 ms)
```

---

## Comparing ReverseMarkdown v4.7.1 vs TextWriter approach

**Hardware: AMD Ryzen 9 3900X 3.80GHz, 24 cores, 12 physical cores**

```
BenchmarkDotNet v0.15.6, Windows 11 (10.0.26200.6899)
AMD Ryzen 9 3900X 3.80GHz, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.306
[Host] : .NET 9.0.10 (9.0.10, 9.0.1025.47515), X64 RyuJIT x86-64-v3
.NET 9.0 : .NET 9.0.10 (9.0.10, 9.0.1025.47515), X64 RyuJIT x86-64-v3
```

Job=.NET 9.0 Runtime=.NET 9.0

### Running with `Files/1000-paragraphs.html` file (size: 442KB) and `1000` paragraphs.

| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | Ratio |
|--------------------------- |---------:|---------:|---------:|------------:|-----------:|-----------:|----------:|------:|
| ReverseMarkdown TextWriter | 25.96 ms | 0.519 ms | 0.744 ms | 3500.0000 | 1593.7500 | 1218.7500 | 24.95 MB | 1 |
| ReverseMarkdown v4.7.1 | 147.7 ms | 2.95 ms | 8.37 ms | 100500.0000 | 97500.0000 | 97500.0000 | 896.35 MB | 5.689 |

Outliers:
- ReverseMarkdown v4.7.1: .NET 9.0 -> 2 outliers were removed (172.08 ms, 174.42 ms)

### Running with `Files/10k-paragraphs.html` file (size: 3.7MB) and `10k` paragraphs.

| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | Ratio |
|--------------------------- |---------:|--------:|--------:|------------:|------------:|------------:|----------:|------:|
| ReverseMarkdown TextWriter | 0.232 s | 0.005 s | 0.009 s | 20000.0000 | 5000.0000 | 1000.0000 | 210.15 MB | 1 |
| ReverseMarkdown v4.7.1 | 14.08 s | 0.280 s | 0.747 s | 624000.0000 | 605000.0000 | 603000.0000 | 75.27 GB | 60.69 |

Outliers:
- ReverseMarkdown TextWriter: .NET 9.0 -> 1 outlier was removed (265.53 ms)
- ReverseMarkdown v4.7.1: .NET 9.0 -> 2 outliers were removed (17.04 s, 17.23 s)

### Running with `Files/huge.html` file (size: 16MB) and `41312` paragraphs.

| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | Ratio |
|--------------------------- |----------:|---------:|---------:|-------------:|-------------:|-------------:|-------------:|-------:|
| ReverseMarkdown TextWriter | 0.944 s | 0.0175 s | 0.0172 s | 86000.0000 | 20000.0000 | 3000.0000 | 955.34 MB | 1 |
| ReverseMarkdown v4.7.1 | 191.611 s | 3.7686 s | 4.4863 s | 2735000.0000 | 2666000.0000 | 2659000.0000 | 640544.94 MB | 202.97 |
28 changes: 28 additions & 0 deletions src/ReverseMarkdown.Benchmark/CompareBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;


namespace ReverseMarkdown.Benchmark;

[SimpleJob(RuntimeMoniker.Net90)]
[RPlotExporter]
[MemoryDiagnoser]
public class CompareBenchmark {
private string _html = null!;
private Converter _converter = null!;

[GlobalSetup]
public void Setup()
{
//_html = FileHelper.ReadFile("Files/1000-paragraphs.html");
_html = FileHelper.ReadFile("Files/10k-paragraphs.html");
_converter = new Converter(new Config());
}

[Benchmark]
public string ReverseMarkdown()
{
var result = _converter.Convert(_html);
return result;
}
}
8 changes: 8 additions & 0 deletions src/ReverseMarkdown.Benchmark/FileHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace ReverseMarkdown.Benchmark {
internal sealed class FileHelper {
public static string ReadFile(string path)
{
return File.ReadAllText(Path.Combine(AppContext.BaseDirectory, path));
}
}
}
1,000 changes: 1,000 additions & 0 deletions src/ReverseMarkdown.Benchmark/Files/1000-paragraphs.html

Large diffs are not rendered by default.

10,000 changes: 10,000 additions & 0 deletions src/ReverseMarkdown.Benchmark/Files/10k-paragraphs.html

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/ReverseMarkdown.Benchmark/Files/huge.html

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/ReverseMarkdown.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using BenchmarkDotNet.Running;
using ReverseMarkdown.Benchmark;


var summary = BenchmarkRunner.Run<CompareBenchmark>();
Console.ReadLine();
32 changes: 32 additions & 0 deletions src/ReverseMarkdown.Benchmark/ReverseMarkdown.Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Optimize>true</Optimize>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.6" />
<!--<PackageReference Include="ReverseMarkdown" Version="4.7.1" />-->
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReverseMarkdown\ReverseMarkdown.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Files\1000-paragraphs.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\10k-paragraphs.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Files\huge.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
14 changes: 4 additions & 10 deletions src/ReverseMarkdown.Test/ChildConverterTests.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
using ReverseMarkdown.Converters;
using ReverseMarkdown.Test.Children;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

using Xunit;

namespace ReverseMarkdown.Test
{
public class ChildConverterTests
{

namespace ReverseMarkdown.Test {
public class ChildConverterTests {
[Fact]
public void WhenConverter_A_IsReplacedByConverter_IgnoreAWhenHasClass()
{
Expand All @@ -28,7 +22,7 @@ public void WhenConverter_A_IsReplacedByConverter_IgnoreAWhenHasClass()

Assert.NotNull(propValRaw);

var propVal = (IDictionary<string, IConverter>)propValRaw;
var propVal = (IDictionary<string, IConverter>) propValRaw;

Assert.NotNull(propVal);

Expand Down
26 changes: 8 additions & 18 deletions src/ReverseMarkdown.Test/Children/IgnoreAWhenHasClass.cs
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
using HtmlAgilityPack;

using ReverseMarkdown.Converters;
using System.IO;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ReverseMarkdown.Test.Children
{
internal class IgnoreAWhenHasClass : A
{
private readonly string _ignore = "ignore";

public IgnoreAWhenHasClass(Converter converter) : base(converter)
{ }
namespace ReverseMarkdown.Test.Children {
internal class IgnoreAWhenHasClass(Converter converter) : A(converter) {
private const string Ignore = "ignore";

public override string Convert(HtmlNode node)
public override void Convert(TextWriter writer, HtmlNode node)
{
if (node.HasClass(_ignore))
return "";
if (node.HasClass(Ignore))
return;

return base.Convert(node);
base.Convert(writer, node);
}
}
}
22 changes: 11 additions & 11 deletions src/ReverseMarkdown.Test/ConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public Task WhenThereIsHtmlLinkNotWhitelisted_ThenBypass()
@"Leave <a href=""http://example.com"">http</a>, <a href=""https://example.com"">https</a>, <a href=""ftp://example.com"">ftp</a>, <a href=""ftps://example.com"">ftps</a>, <a href=""file://example.com"">file</a>. Remove <a href=""data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678"">data</a>, <a href=""tel://example.com"">tel</a> and <a href=""whatever://example.com"">whatever</a>";
return CheckConversion(html, new Config
{
WhitelistUriSchemes = new[] {"http", "https", "ftp", "ftps", "file"}
WhitelistUriSchemes = {"http", "https", "ftp", "ftps", "file"}
});
}

Expand All @@ -66,7 +66,7 @@ public Task WhenThereHtmlWithHrefAndNoSchema_WhitelistedEmptyString_ThenConvertT
html: @"<a href=""example.com"">yeah</a>",
config: new Config
{
WhitelistUriSchemes = new[] {""}
WhitelistUriSchemes = {""}
}
);
}
Expand All @@ -78,7 +78,7 @@ public Task WhenThereHtmlWithHrefAndNoSchema_NotWhitelisted_ThenConvertToPlain()
html: @"<a href=""example.com"">yeah</a>",
config: new Config
{
WhitelistUriSchemes = new[] {"whatever"}
WhitelistUriSchemes = {"whatever"}
}
);
}
Expand Down Expand Up @@ -430,7 +430,7 @@ public Task WhenThereIsImgTag_SchemeNotWhitelisted_ThenEmptyOutput()
html: @"<img src=""data:image/gif;base64,R0lGODlhEAAQ...""/>",
config: new Config
{
WhitelistUriSchemes = new[] {"http"}
WhitelistUriSchemes = {"http"}
}
);
}
Expand All @@ -442,7 +442,7 @@ public Task WhenThereIsImgTag_SchemeIsWhitelisted_ThenConvertToMarkdown()
html: @"<img src=""data:image/gif;base64,R0lGODlhEAAQ...""/>",
config: new Config()
{
WhitelistUriSchemes = new[] {"data"}
WhitelistUriSchemes = {"data"}
}
);
}
Expand All @@ -454,7 +454,7 @@ public Task WhenThereIsImgTagAndSrcWithNoSchema_WhitelistedEmptyString_ThenConve
html: @"<img src=""example.com""/>",
config: new Config()
{
WhitelistUriSchemes = new[] {""}
WhitelistUriSchemes = {""}
}
);
}
Expand All @@ -466,7 +466,7 @@ public Task WhenThereIsImgTagAndSrcWithNoSchema_NotWhitelisted_ThenConvertToPlai
html: @"<img src=""data:image/gif;base64,R0lGODlhEAAQ...""/>",
config: new Config()
{
WhitelistUriSchemes = new[] {"whatever"}
WhitelistUriSchemes = {"whatever"}
}
);
}
Expand All @@ -478,7 +478,7 @@ public Task WhenThereIsImgTagWithRelativeUrl_NotWhitelisted_ThenConvertToMarkdow
html: @"<img src=""/example.gif""/>",
config: new Config()
{
WhitelistUriSchemes = new[] {"data"}
WhitelistUriSchemes = {"data"}
}
);
}
Expand All @@ -490,7 +490,7 @@ public Task WhenThereIsImgTagWithUnixUrl_ConfigHasWhitelist_ThenConvertToMarkdow
html: @"<img src=""/example.gif""/>",
config: new Config()
{
WhitelistUriSchemes = new[] {"file"}
WhitelistUriSchemes = {"file"}
}
);
}
Expand All @@ -501,7 +501,7 @@ public Task WhenThereIsImgTagWithHttpProtocolRelativeUrl_ConfigHasWhitelist_Then
var html = @"<img src=""//example.gif""/>";
var config = new Config
{
WhitelistUriSchemes = new[] {"http"}
WhitelistUriSchemes = {"http"}
};
return CheckConversion(html, config);
}
Expand Down Expand Up @@ -1152,7 +1152,7 @@ public Task When_Tag_In_PassThoughTags_List_Then_Use_PassThroughConverter()
@"This text has image <img alt=""alt"" src=""http://test.com/images/test.png"">. Next line of text";
var config = new Config
{
PassThroughTags = new[] {"img"}
PassThroughTags = ["img"]
};
return CheckConversion(html, config);
}
Expand Down
28 changes: 16 additions & 12 deletions src/ReverseMarkdown.Test/ReverseMarkdown.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<TargetFrameworks>net472;net6.0;net9.0</TargetFrameworks>
<IsTestProject>true</IsTestProject>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\ReverseMarkdown\ReverseMarkdown.csproj" />
<PackageReference Include="MarkdownSnippets.MsBuild" Version="27.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Verify.Xunit" Version="30.1.0" />
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="Verify.Xunit" Version="31.4.3" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReverseMarkdown\ReverseMarkdown.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="ConverterTests.When_Span_with_newline_Should_Convert_Properly.verified.md">
<DependentUpon>ConverterTests.cs</DependentUpon>
Expand Down
13 changes: 11 additions & 2 deletions src/ReverseMarkdown.sln
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.9
# Visual Studio Version 17
VisualStudioVersion = 17.14.36616.10 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReverseMarkdown", "ReverseMarkdown\ReverseMarkdown.csproj", "{518A6D79-4596-4086-BA5D-DD353BCEC9A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReverseMarkdown.Test", "ReverseMarkdown.Test\ReverseMarkdown.Test.csproj", "{1756FE48-2619-459C-A0D0-2D2A1D163A03}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReverseMarkdown.Benchmark", "ReverseMarkdown.Benchmark\ReverseMarkdown.Benchmark.csproj", "{B0521F32-EA69-4ABF-8F60-89AF704ABFD4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,8 +23,15 @@ Global
{1756FE48-2619-459C-A0D0-2D2A1D163A03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1756FE48-2619-459C-A0D0-2D2A1D163A03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1756FE48-2619-459C-A0D0-2D2A1D163A03}.Release|Any CPU.Build.0 = Release|Any CPU
{B0521F32-EA69-4ABF-8F60-89AF704ABFD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0521F32-EA69-4ABF-8F60-89AF704ABFD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0521F32-EA69-4ABF-8F60-89AF704ABFD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0521F32-EA69-4ABF-8F60-89AF704ABFD4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {478AE4EC-DF7B-46D4-9350-862EA335D02F}
EndGlobalSection
EndGlobal
Loading