Skip to content

Commit

Permalink
Avoid Dictionary lookups in RendererBase.Write
Browse files Browse the repository at this point in the history
  • Loading branch information
MihaZupan committed Nov 26, 2023
1 parent dce5572 commit f3aa7e7
Showing 1 changed file with 65 additions and 37 deletions.
102 changes: 65 additions & 37 deletions src/Markdig/Renderers/RendererBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// See the license.txt file in the project root for more information.

using System.Runtime.CompilerServices;

using System.Runtime.InteropServices;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
Expand All @@ -16,31 +16,64 @@ namespace Markdig.Renderers;
/// <seealso cref="IMarkdownRenderer" />
public abstract class RendererBase : IMarkdownRenderer
{
private readonly Dictionary<KeyWrapper, IMarkdownObjectRenderer?> _renderersPerType = new();
private const int SubTableCount = 32;

private readonly struct RendererEntry
{
public readonly IntPtr Key;
public readonly IMarkdownObjectRenderer? Renderer;

public RendererEntry(IntPtr key, IMarkdownObjectRenderer? renderer)
{
Key = key;
Renderer = renderer;
}
}

private readonly RendererEntry[][] _renderersPerType;

internal int _childrenDepth = 0;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IntPtr GetKeyForType(MarkdownObject obj) => Type.GetTypeHandle(obj).Value;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int SubTableIndex(IntPtr key) => (int)((((ulong)key) / 64) & (SubTableCount - 1));

/// <summary>
/// Initializes a new instance of the <see cref="RendererBase"/> class.
/// </summary>
protected RendererBase() { }
protected RendererBase()
{
var entries = _renderersPerType = new RendererEntry[SubTableCount][];
for (int i = 0; i < entries.Length; i++)
{
entries[i] ??= [];
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
private IMarkdownObjectRenderer? GetRendererInstance(MarkdownObject obj)
{
KeyWrapper key = GetKeyForType(obj);
Type objectType = obj.GetType();
IMarkdownObjectRenderer? renderer = null;

for (int i = 0; i < ObjectRenderers.Count; i++)
foreach (var potentialRenderer in ObjectRenderers)
{
var renderer = ObjectRenderers[i];
if (renderer.Accept(this, objectType))
if (potentialRenderer.Accept(this, objectType))
{
_renderersPerType[key] = renderer;
return renderer;
renderer = potentialRenderer;
break;
}
}

_renderersPerType[key] = null;
return null;
IntPtr key = GetKeyForType(obj);

ref RendererEntry[] entries = ref _renderersPerType[SubTableIndex(key)];
Array.Resize(ref entries, entries.Length + 1);
entries[entries.Length - 1] = new RendererEntry(key, renderer);

return renderer;
}

public ObjectRendererCollection ObjectRenderers { get; } = new();
Expand Down Expand Up @@ -77,12 +110,11 @@ public void WriteChildren(ContainerBlock containerBlock)
bool saveIsFirstInContainer = IsFirstInContainer;
bool saveIsLastInContainer = IsLastInContainer;

var children = containerBlock;
for (int i = 0; i < children.Count; i++)
for (int i = 0; i < containerBlock.Count; i++)
{
IsFirstInContainer = i == 0;
IsLastInContainer = i + 1 == children.Count;
Write(children[i]);
IsLastInContainer = i + 1 == containerBlock.Count;
Write(containerBlock[i]);
}

IsFirstInContainer = saveIsFirstInContainer;
Expand Down Expand Up @@ -140,11 +172,27 @@ public void Write(MarkdownObject obj)
// Calls before writing an object
ObjectWriteBefore?.Invoke(this, obj);

if (!_renderersPerType.TryGetValue(GetKeyForType(obj), out IMarkdownObjectRenderer? renderer))
IMarkdownObjectRenderer? renderer = null;
IntPtr key = GetKeyForType(obj);

#if NETFRAMEWORK || NETSTANDARD
RendererEntry[] renderers = _renderersPerType[SubTableIndex(key)];
#else
RendererEntry[] renderers = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_renderersPerType), SubTableIndex(key));
#endif

foreach (RendererEntry entry in renderers)
{
renderer = GetRendererInstance(obj);
if (key == entry.Key)
{
renderer = entry.Renderer;
goto Render;
}
}

renderer = GetRendererInstance(obj);

Render:
if (renderer is not null)
{
renderer.Write(this, obj);
Expand All @@ -161,24 +209,4 @@ public void Write(MarkdownObject obj)
// Calls after writing an object
ObjectWriteAfter?.Invoke(this, obj);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static KeyWrapper GetKeyForType(MarkdownObject obj)
{
IntPtr typeHandle = Type.GetTypeHandle(obj).Value;
return new KeyWrapper(typeHandle);
}

private readonly struct KeyWrapper : IEquatable<KeyWrapper>
{
public readonly IntPtr Key;

public KeyWrapper(IntPtr key) => Key = key;

public bool Equals(KeyWrapper other) => Key == other.Key;

public override int GetHashCode() => Key.GetHashCode();

public override bool Equals(object? obj) => throw new NotImplementedException();
}
}

0 comments on commit f3aa7e7

Please sign in to comment.