Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize render tree building via RenderTreeFrameArrayBuilder #24484

Merged
merged 23 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7562cca
Remove RenderTreeFrame's "With..." methods, making it mutable
SteveSandersonMS Jul 31, 2020
1dd4176
Begin declaring RenderTreeArrayBuilder
SteveSandersonMS Jul 31, 2020
a8fcebd
Split Append into "attribute" and "nonattribute" cases so I can inlin…
SteveSandersonMS Jul 31, 2020
130a22c
Inline AppendAttribute
SteveSandersonMS Jul 31, 2020
8d905bb
Inline AppendNonAttribute
SteveSandersonMS Jul 31, 2020
f1e4aea
Begin implementing Append methods in RenderTreeArrayBuilder
SteveSandersonMS Jul 31, 2020
d425c4d
Define more Append methods in RenderTreeArrayBuilder
SteveSandersonMS Jul 31, 2020
afb7dfd
More Append methods
SteveSandersonMS Jul 31, 2020
d3f81af
Add todo note for later
SteveSandersonMS Jul 31, 2020
874cde6
Rename RenderTreeArrayBuilder->RenderTreeFrameArrayBuilder
SteveSandersonMS Jul 31, 2020
297119b
Add note about correctness issue to check
SteveSandersonMS Jul 31, 2020
a1b498d
Perform equivalent optimization with RenderTreeEditArrayBuilder
SteveSandersonMS Jul 31, 2020
6845cb5
Revert RenderTreeEditArrayBuilder, because it makes too little differ…
SteveSandersonMS Jul 31, 2020
c34d410
Inline GrowBuffer. Gains another 1%.
SteveSandersonMS Jul 31, 2020
2220e03
Add implementation notes
SteveSandersonMS Jul 31, 2020
81c342f
Switch to more risk-averse implementation. Seems to affect wallclock …
SteveSandersonMS Jul 31, 2020
98a23ac
Avoid removing methods
SteveSandersonMS Jul 31, 2020
3859d49
Simplify - revert some unnecessary changes
SteveSandersonMS Aug 3, 2020
fc7872f
Rename RenderTreeFrame fields for internal use
SteveSandersonMS Aug 3, 2020
cb57cc9
Avoid exposing RenderTreeFrame mutability publicly.
SteveSandersonMS Aug 3, 2020
0a7db66
Cleanup
SteveSandersonMS Aug 3, 2020
48f5c76
Cleanup
SteveSandersonMS Aug 3, 2020
ca77e54
Cleanup
SteveSandersonMS Aug 3, 2020
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
24 changes: 12 additions & 12 deletions src/Components/Components/src/ParameterView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ internal bool DefinitelyEquals(ParameterView oldParameters)

var oldIndex = oldParameters._ownerIndex;
var newIndex = _ownerIndex;
var oldEndIndexExcl = oldIndex + oldParameters._frames[oldIndex].ComponentSubtreeLength;
var newEndIndexExcl = newIndex + _frames[newIndex].ComponentSubtreeLength;
var oldEndIndexExcl = oldIndex + oldParameters._frames[oldIndex].ComponentSubtreeLengthField;
var newEndIndexExcl = newIndex + _frames[newIndex].ComponentSubtreeLengthField;
while (true)
{
// First, stop if we've reached the end of either subtree
Expand All @@ -162,21 +162,21 @@ internal bool DefinitelyEquals(ParameterView oldParameters)
ref var newFrame = ref _frames[newIndex];

// Stop if we've reached the end of either subtree's sequence of attributes
oldFinished = oldFrame.FrameType != RenderTreeFrameType.Attribute;
newFinished = newFrame.FrameType != RenderTreeFrameType.Attribute;
oldFinished = oldFrame.FrameTypeField != RenderTreeFrameType.Attribute;
newFinished = newFrame.FrameTypeField != RenderTreeFrameType.Attribute;
if (oldFinished || newFinished)
{
return oldFinished == newFinished; // Same only if we have same number of parameters
}
else
{
if (!string.Equals(oldFrame.AttributeName, newFrame.AttributeName, StringComparison.Ordinal))
if (!string.Equals(oldFrame.AttributeNameField, newFrame.AttributeNameField, StringComparison.Ordinal))
{
return false; // Different names
}

var oldValue = oldFrame.AttributeValue;
var newValue = newFrame.AttributeValue;
var oldValue = oldFrame.AttributeValueField;
var newValue = newFrame.AttributeValueField;
if (ChangeDetection.MayHaveChanged(oldValue, newValue))
{
return false;
Expand Down Expand Up @@ -216,8 +216,8 @@ internal void CaptureSnapshot(ArrayBuilder<RenderTreeFrame> builder)
public static ParameterView FromDictionary(IDictionary<string, object> parameters)
{
var frames = new RenderTreeFrame[parameters.Count + 1];
frames[0] = RenderTreeFrame.Element(0, GeneratedParameterViewElementName)
.WithElementSubtreeLength(frames.Length);
frames[0] = RenderTreeFrame.Element(0, GeneratedParameterViewElementName);
frames[0].ElementSubtreeLengthField = frames.Length;

var i = 0;
foreach (var kvp in parameters)
Expand Down Expand Up @@ -303,7 +303,7 @@ internal RenderTreeFrameParameterEnumerator(RenderTreeFrame[] frames, int ownerI
{
_frames = frames;
_ownerIndex = ownerIndex;
_ownerDescendantsEndIndexExcl = ownerIndex + _frames[ownerIndex].ElementSubtreeLength;
_ownerDescendantsEndIndexExcl = ownerIndex + _frames[ownerIndex].ElementSubtreeLengthField;
_currentIndex = ownerIndex;
_current = default;
}
Expand All @@ -321,15 +321,15 @@ public bool MoveNext()

// ... or if you get to its first non-attribute descendant (because attributes
// are always before any other type of descendant)
if (_frames[nextIndex].FrameType != RenderTreeFrameType.Attribute)
if (_frames[nextIndex].FrameTypeField != RenderTreeFrameType.Attribute)
{
return false;
}

_currentIndex = nextIndex;

ref var frame = ref _frames[_currentIndex];
_current = new ParameterValue(frame.AttributeName, frame.AttributeValue, false);
_current = new ParameterValue(frame.AttributeNameField, frame.AttributeValueField, false);

return true;
}
Expand Down
141 changes: 71 additions & 70 deletions src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs

Large diffs are not rendered by default.

176 changes: 103 additions & 73 deletions src/Components/Components/src/RenderTree/RenderTreeFrame.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.AspNetCore.Components.RenderTree
{
/// <summary>
/// A special subclass of <see cref="ArrayBuilder{T}"/> that contains methods optimized for appending <see cref="RenderTreeFrame"/> entries.
/// </summary>
internal class RenderTreeFrameArrayBuilder : ArrayBuilder<RenderTreeFrame>
{
// You may notice a repeated block at the top of each of these methods. This is intentionally inlined into each
// method because doing so improves intensive rendering scenarios by around 1% (based on the FastGrid benchmark).

public void AppendElement(int sequence, string elementName)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Element,
ElementNameField = elementName,
};
}

public void AppendText(int sequence, string textContent)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Text,
TextContentField = textContent,
};
}

public void AppendMarkup(int sequence, string markupContent)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Markup,
MarkupContentField = markupContent,
};
}

public void AppendAttribute(int sequence, string attributeName, object? attributeValue)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Attribute,
AttributeNameField = attributeName,
AttributeValueField = attributeValue,
};
}

public void AppendComponent(int sequence, Type componentType)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Component,
ComponentTypeField = componentType,
};
}

public void AppendElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.ElementReferenceCapture,
ElementReferenceCaptureActionField = elementReferenceCaptureAction,
};
}

public void AppendComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction, int parentFrameIndexValue)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.ComponentReferenceCapture,
ComponentReferenceCaptureActionField = componentReferenceCaptureAction,
ComponentReferenceCaptureParentFrameIndexField = parentFrameIndexValue,
};
}

public void AppendRegion(int sequence)
{
if (_itemsInUse == _items.Length)
{
GrowBuffer(_items.Length * 2);
}

_items[_itemsInUse++] = new RenderTreeFrame
{
SequenceField = sequence,
FrameTypeField = RenderTreeFrameType.Region,
};
}
}
}
15 changes: 8 additions & 7 deletions src/Components/Components/src/RenderTree/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,19 +292,20 @@ public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fiel

internal void InstantiateChildComponentOnFrame(ref RenderTreeFrame frame, int parentComponentId)
{
if (frame.FrameType != RenderTreeFrameType.Component)
if (frame.FrameTypeField != RenderTreeFrameType.Component)
{
throw new ArgumentException($"The frame's {nameof(RenderTreeFrame.FrameType)} property must equal {RenderTreeFrameType.Component}", nameof(frame));
}

if (frame.ComponentState != null)
if (frame.ComponentStateField != null)
{
throw new ArgumentException($"The frame already has a non-null component instance", nameof(frame));
}

var newComponent = InstantiateComponent(frame.ComponentType);
var newComponent = InstantiateComponent(frame.ComponentTypeField);
var newComponentState = AttachAndInitComponent(newComponent, parentComponentId);
frame = frame.WithComponent(newComponentState);
frame.ComponentStateField = newComponentState;
frame.ComponentIdField = newComponentState.ComponentId;
}

internal void AddToPendingTasks(Task task)
Expand Down Expand Up @@ -342,7 +343,7 @@ internal void AssignEventHandlerId(ref RenderTreeFrame frame)
{
var id = ++_lastEventHandlerId;

if (frame.AttributeValue is EventCallback callback)
if (frame.AttributeValueField is EventCallback callback)
{
// We hit this case when a EventCallback object is produced that needs an explicit receiver.
// Common cases for this are "chained bind" or "chained event handler" when a component
Expand All @@ -352,7 +353,7 @@ internal void AssignEventHandlerId(ref RenderTreeFrame frame)
// the receiver.
_eventBindings.Add(id, callback);
}
else if (frame.AttributeValue is MulticastDelegate @delegate)
else if (frame.AttributeValueField is MulticastDelegate @delegate)
{
// This is the common case for a delegate, where the receiver of the event
// is the same as delegate.Target. In this case since the receiver is implicit we can
Expand All @@ -364,7 +365,7 @@ internal void AssignEventHandlerId(ref RenderTreeFrame frame)
// NOTE: we do not to handle EventCallback<T> here. EventCallback<T> is only used when passing
// a callback to a component, and never when used to attaching a DOM event handler.

frame = frame.WithAttributeEventHandlerId(id);
frame.AttributeEventHandlerIdField = id;
}

/// <summary>
Expand Down
Loading