Avoid allocating CodeInline.Content substrings

This commit is contained in:
Miha Zupan
2022-04-08 17:22:38 +02:00
parent e86d1ffce5
commit 3201699053
6 changed files with 83 additions and 17 deletions

View File

@@ -0,0 +1,45 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
using System;
using System.Diagnostics;
namespace Markdig.Helpers
{
internal struct LazySubstring
{
public string Text;
public int Offset;
public int Length;
public LazySubstring(string text)
{
Text = text;
Offset = 0;
Length = text.Length;
}
public LazySubstring(string text, int offset, int length)
{
Debug.Assert((ulong)offset + (ulong)length < (ulong)text.Length, $"{offset}-{length} in {text}");
Text = text;
Offset = offset;
Length = length;
}
public ReadOnlySpan<char> AsSpan() => Text.AsSpan(Offset, Length);
public override string ToString()
{
if (Offset != 0 || Length != Text.Length)
{
Text = Text.Substring(Offset, Length);
Offset = 0;
}
return Text;
}
}
}

View File

@@ -6,6 +6,7 @@ using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System;
using System.Diagnostics;
namespace Markdig.Parsers.Inlines
{
@@ -54,6 +55,7 @@ namespace Markdig.Parsers.Inlines
// whitespace from the opening or closing backtick strings.
bool allSpace = true;
bool containsNewLine = false;
var contentEnd = -1;
while (c != '\0')
@@ -61,10 +63,12 @@ namespace Markdig.Parsers.Inlines
// Transform '\n' into a single space
if (c == '\n')
{
containsNewLine = true;
c = ' ';
}
else if (c == '\r')
{
containsNewLine = true;
slice.SkipChar();
c = slice.CurrentChar;
continue;
@@ -100,14 +104,19 @@ namespace Markdig.Parsers.Inlines
{
ReadOnlySpan<char> contentSpan = builder.AsSpan();
var content = containsNewLine
? new LazySubstring(contentSpan.ToString())
: new LazySubstring(slice.Text, contentStart, contentSpan.Length);
Debug.Assert(contentSpan.SequenceEqual(content.AsSpan()));
// Remove one space from front and back if the string is not all spaces
if (!allSpace && contentSpan.Length > 2 && contentSpan[0] == ' ' && contentSpan[contentSpan.Length - 1] == ' ')
{
contentSpan = contentSpan.Slice(1, contentSpan.Length - 2);
content.Offset++;
content.Length -= 2;
}
string content = contentSpan.ToString();
int delimiterCount = Math.Min(openSticks, closeSticks);
var spanStart = processor.GetSourcePosition(startPosition, out int line, out int column);
var spanEnd = processor.GetSourcePosition(slice.Start - 1);

View File

@@ -22,11 +22,11 @@ namespace Markdig.Renderers.Html.Inlines
}
if (renderer.EnableHtmlEscape)
{
renderer.WriteEscape(obj.Content);
renderer.WriteEscape(obj.ContentSpan);
}
else
{
renderer.Write(obj.Content);
renderer.Write(obj.ContentSpan);
}
if (renderer.EnableHtmlForInline)
{

View File

@@ -15,15 +15,16 @@ namespace Markdig.Renderers.Normalize.Inlines
protected override void Write(NormalizeRenderer renderer, CodeInline obj)
{
var delimiterCount = 0;
for (var i = 0; i < obj.Content!.Length; i++)
string content = obj.Content;
for (var i = 0; i < content.Length; i++)
{
var index = obj.Content.IndexOf(obj.Delimiter, i);
var index = content.IndexOf(obj.Delimiter, i);
if (index == -1) break;
var count = 1;
for (i = index + 1; i < obj.Content.Length; i++)
for (i = index + 1; i < content.Length; i++)
{
if (obj.Content[i] == obj.Delimiter) count++;
if (content[i] == obj.Delimiter) count++;
else break;
}
@@ -32,14 +33,14 @@ namespace Markdig.Renderers.Normalize.Inlines
}
var delimiterRun = new string(obj.Delimiter, delimiterCount + 1);
renderer.Write(delimiterRun);
if (obj.Content.Length != 0)
if (content.Length != 0)
{
if (obj.Content[0] == obj.Delimiter)
if (content[0] == obj.Delimiter)
{
renderer.Write(' ');
}
renderer.Write(obj.Content);
if (obj.Content[obj.Content.Length - 1] == obj.Delimiter)
renderer.Write(content);
if (content[content.Length - 1] == obj.Delimiter)
{
renderer.Write(' ');
}

View File

@@ -16,7 +16,7 @@ namespace Markdig.Renderers.Roundtrip.Inlines
{
var delimiterRun = new string(obj.Delimiter, obj.DelimiterCount);
renderer.Write(delimiterRun);
if (obj.Content is { Length: > 0 })
if (!obj.ContentSpan.IsEmpty)
{
renderer.Write(obj.ContentWithTrivia);
}

View File

@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using Markdig.Helpers;
using System;
using System.Diagnostics;
namespace Markdig.Syntax.Inlines
@@ -17,9 +18,13 @@ namespace Markdig.Syntax.Inlines
private TriviaProperties? _trivia => GetTrivia<TriviaProperties>();
private TriviaProperties Trivia => GetOrSetTrivia<TriviaProperties>();
public CodeInline(string content)
private LazySubstring _content;
public CodeInline(string content) : this(new LazySubstring(content)) { }
internal CodeInline(LazySubstring content)
{
Content = content;
_content = content;
}
/// <summary>
@@ -35,7 +40,13 @@ namespace Markdig.Syntax.Inlines
/// <summary>
/// Gets or sets the content of the span.
/// </summary>
public string Content { get; set; }
public string Content
{
get => _content.ToString();
set => _content = new LazySubstring(value ?? string.Empty);
}
public ReadOnlySpan<char> ContentSpan => _content.AsSpan();
/// <summary>
/// Gets or sets the content with trivia and whitespace.