diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj index 7565534a51d108b7140c7538f844309da992cf55..1e7f881ed501a40a664ca7ef5fc2a82dd59170b2 100644 --- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj +++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj @@ -10,7 +10,6 @@ <ItemGroup> <Compile Include="$(ComponentsSharedSourceRoot)src\ArrayBuilder.cs" LinkBase="RenderTree" /> - <Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" /> </ItemGroup> <ItemGroup> diff --git a/src/Components/Components/src/Profiling/ComponentsProfiling.cs b/src/Components/Components/src/Profiling/ComponentsProfiling.cs deleted file mode 100644 index f47a0c917c8e325fa48698d86c0c0d8417734424..0000000000000000000000000000000000000000 --- a/src/Components/Components/src/Profiling/ComponentsProfiling.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Components.Profiling -{ - internal abstract class ComponentsProfiling - { - // For now, this is only intended for use on Blazor WebAssembly, and will have no effect - // when running on Blazor Server. The reason for having the ComponentsProfiling abstraction - // is so that if we later have two different implementations (one for WebAssembly, one for - // Server), the execution characteristics of calling Start/End will be unchanged and historical - // perf data will still be comparable to newer data. - public static readonly ComponentsProfiling Instance = PlatformInfo.IsWebAssembly - ? new WebAssemblyComponentsProfiling() - : (ComponentsProfiling)new NoOpComponentsProfiling(); - - public abstract void Start([CallerMemberName] string? name = null); - public abstract void End([CallerMemberName] string? name = null); - } -} diff --git a/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs b/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs deleted file mode 100644 index 74037e7de7446ae6e3c07934e63d0ffaf557dd3e..0000000000000000000000000000000000000000 --- a/src/Components/Components/src/Profiling/NoOpComponentsProfiling.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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. - -namespace Microsoft.AspNetCore.Components.Profiling -{ - internal class NoOpComponentsProfiling : ComponentsProfiling - { - public override void Start(string? name) - { - } - - public override void End(string? name) - { - } - } -} diff --git a/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs b/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs deleted file mode 100644 index 4e0b6dbd745473f5245010409f2172c7c9093834..0000000000000000000000000000000000000000 --- a/src/Components/Components/src/Profiling/WebAssemblyComponentsProfiling.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 WebAssembly.JSInterop; - -namespace Microsoft.AspNetCore.Components.Profiling -{ - // Later on, we will likely want to move this into the WebAssembly package. However it needs to - // be inlined into the Components package directly until we're ready to make the underlying - // ComponentsProfile abstraction into a public API. It's possible that this API will never become - // public, or that it will be replaced by something more standard for .NET, if it's possible to - // make that work performantly on WebAssembly. - - internal class WebAssemblyComponentsProfiling : ComponentsProfiling - { - static bool IsCapturing = false; - - public static void SetCapturing(bool isCapturing) - { - IsCapturing = isCapturing; - } - - public override void Start(string? name) - { - if (IsCapturing) - { - InternalCalls.InvokeJSUnmarshalled<string, object, object, object>( - out _, "_blazorProfileStart", name, null, null); - } - } - - public override void End(string? name) - { - if (IsCapturing) - { - InternalCalls.InvokeJSUnmarshalled<string, object, object, object>( - out _, "_blazorProfileEnd", name, null, null); - } - } - } -} diff --git a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs index a7892991f45f0d35e95d0924b916c62cf76ff135..7429cf7d7418a1d0735b8653b11e9d36e83b76c5 100644 --- a/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs +++ b/src/Components/Components/src/RenderTree/RenderTreeDiffBuilder.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.Rendering; namespace Microsoft.AspNetCore.Components.RenderTree @@ -28,7 +27,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree ArrayRange<RenderTreeFrame> oldTree, ArrayRange<RenderTreeFrame> newTree) { - ComponentsProfiling.Instance.Start(); var editsBuffer = batchBuilder.EditsBuffer; var editsBufferStartLength = editsBuffer.Count; @@ -37,7 +35,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree var editsSegment = editsBuffer.ToSegment(editsBufferStartLength, editsBuffer.Count); var result = new RenderTreeDiff(componentId, editsSegment); - ComponentsProfiling.Instance.End(); return result; } @@ -49,7 +46,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { - ProfilingStart(); // This is deliberately a very large method. Parts of it could be factored out // into other private methods, but doing so comes at a consequential perf cost, // because it involves so much parameter passing. You can think of the code here @@ -300,12 +296,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree diffContext.KeyedItemInfoDictionaryPool.Return(keyedItemInfos); } } - ProfilingEnd(); } private static Dictionary<object, KeyedItemInfo> BuildKeyToInfoLookup(DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { - ProfilingStart(); var result = diffContext.KeyedItemInfoDictionaryPool.Get(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; @@ -351,7 +345,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree newStartIndex = NextSiblingIndex(frame, newStartIndex); } - ProfilingEnd(); return result; } @@ -384,7 +377,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { - ProfilingStart(); // The overhead of the dictionary used by AppendAttributeDiffEntriesForRangeSlow is // significant, so we want to try and do a merge-join if possible, but fall back to // a hash-join if not. We'll do a merge join until we hit a case we can't handle and @@ -433,7 +425,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree ref diffContext, oldStartIndex, oldEndIndexExcl, newStartIndex, newEndIndexExcl); - ProfilingEnd(); return; } @@ -459,12 +450,9 @@ namespace Microsoft.AspNetCore.Components.RenderTree ref diffContext, oldStartIndex, oldEndIndexExcl, newStartIndex, newEndIndexExcl); - ProfilingEnd(); return; } } - - ProfilingEnd(); } private static void AppendAttributeDiffEntriesForRangeSlow( @@ -472,7 +460,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) { - ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; @@ -511,7 +498,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree // We should have processed any additions at this point. Reset for the next batch. diffContext.AttributeDiffSet.Clear(); - ProfilingEnd(); } private static void UpdateRetainedChildComponent( @@ -519,7 +505,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldComponentIndex, int newComponentIndex) { - ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; ref var oldComponentFrame = ref oldTree[oldComponentIndex]; @@ -546,8 +531,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree { componentState.SetDirectParameters(newParameters); } - - ProfilingEnd(); } private static int NextSiblingIndex(in RenderTreeFrame frame, int frameIndex) @@ -570,7 +553,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldFrameIndex, int newFrameIndex) { - ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; ref var oldFrame = ref oldTree[oldFrameIndex]; @@ -583,7 +565,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree { InsertNewFrame(ref diffContext, newFrameIndex); RemoveOldFrame(ref diffContext, oldFrameIndex); - ProfilingEnd(); return; } @@ -709,8 +690,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree default: throw new NotImplementedException($"Encountered unsupported frame type during diffing: {newTree[newFrameIndex].FrameType}"); } - - ProfilingEnd(); } // This should only be called for attributes that have the same name. This is an @@ -720,7 +699,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree int oldFrameIndex, int newFrameIndex) { - ProfilingStart(); var oldTree = diffContext.OldTree; var newTree = diffContext.NewTree; ref var oldFrame = ref oldTree[oldFrameIndex]; @@ -749,13 +727,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree // since it was unchanged. newFrame = oldFrame; } - - ProfilingEnd(); } private static void InsertNewFrame(ref DiffContext diffContext, int newFrameIndex) { - ProfilingStart(); var newTree = diffContext.NewTree; ref var newFrame = ref newTree[newFrameIndex]; switch (newFrame.FrameType) @@ -808,12 +783,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree default: throw new NotImplementedException($"Unexpected frame type during {nameof(InsertNewFrame)}: {newFrame.FrameType}"); } - ProfilingEnd(); } private static void RemoveOldFrame(ref DiffContext diffContext, int oldFrameIndex) { - ProfilingStart(); var oldTree = diffContext.OldTree; ref var oldFrame = ref oldTree[oldFrameIndex]; switch (oldFrame.FrameType) @@ -855,7 +828,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree default: throw new NotImplementedException($"Unexpected frame type during {nameof(RemoveOldFrame)}: {oldFrame.FrameType}"); } - ProfilingEnd(); } private static int GetAttributesEndIndexExclusive(RenderTreeFrame[] tree, int rootIndex) @@ -889,7 +861,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree private static void InitializeNewSubtree(ref DiffContext diffContext, int frameIndex) { - ProfilingStart(); var frames = diffContext.NewTree; var endIndexExcl = frameIndex + frames[frameIndex].ElementSubtreeLength; for (var i = frameIndex; i < endIndexExcl; i++) @@ -911,12 +882,10 @@ namespace Microsoft.AspNetCore.Components.RenderTree break; } } - ProfilingEnd(); } private static void InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex) { - ProfilingStart(); var frames = diffContext.NewTree; ref var frame = ref frames[frameIndex]; @@ -933,7 +902,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree var initialParametersLifetime = new ParameterViewLifetime(diffContext.BatchBuilder); var initialParameters = new ParameterView(initialParametersLifetime, frames, frameIndex); childComponentState.SetDirectParameters(initialParameters); - ProfilingEnd(); } private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame) @@ -978,7 +946,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree private static void DisposeFramesInRange(RenderBatchBuilder batchBuilder, RenderTreeFrame[] frames, int startIndex, int endIndexExcl) { - ProfilingStart(); for (var i = startIndex; i < endIndexExcl; i++) { ref var frame = ref frames[i]; @@ -991,7 +958,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree batchBuilder.DisposedEventHandlerIds.Append(frame.AttributeEventHandlerId); } } - ProfilingEnd(); } /// <summary> @@ -1033,18 +999,5 @@ namespace Microsoft.AspNetCore.Components.RenderTree SiblingIndex = 0; } } - - // Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact - // even when capturing is disabled. So, to enable detailed profiling for this class, define the - // Profile_RenderTreeDiffBuilder compiler symbol, otherwise the calls are compiled out entirely. - // Enabling detailed profiling adds about 5% to rendering benchmark times. - - [Conditional("Profile_RenderTreeDiffBuilder")] - private static void ProfilingStart([CallerMemberName] string? name = null) - => ComponentsProfiling.Instance.Start(name); - - [Conditional("Profile_RenderTreeDiffBuilder")] - private static void ProfilingEnd([CallerMemberName] string? name = null) - => ComponentsProfiling.Instance.End(name); } } diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs index 446243201ff987c075bddb484f4aaa24bd540c3f..2f1e74a95cdfff068c8e8d57379cfc1fef6ec386 100644 --- a/src/Components/Components/src/RenderTree/Renderer.cs +++ b/src/Components/Components/src/RenderTree/Renderer.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -247,7 +246,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree /// </returns> public virtual Task DispatchEventAsync(ulong eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs) { - ComponentsProfiling.Instance.Start(); Dispatcher.AssertAccess(); if (!_eventBindings.TryGetValue(eventHandlerId, out var callback)) @@ -275,7 +273,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree catch (Exception e) { HandleException(e); - ComponentsProfiling.Instance.End(); return Task.CompletedTask; } finally @@ -290,7 +287,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree // Task completed synchronously or is still running. We already processed all of the rendering // work that was queued so let our error handler deal with it. var result = GetErrorHandledTask(task); - ComponentsProfiling.Instance.End(); return result; } @@ -441,7 +437,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree private void ProcessRenderQueue() { - ComponentsProfiling.Instance.Start(); Dispatcher.AssertAccess(); if (_isBatchInProgress) @@ -456,7 +451,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree { if (_batchBuilder.ComponentRenderQueue.Count == 0) { - ComponentsProfiling.Instance.End(); return; } @@ -468,9 +462,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree } var batch = _batchBuilder.ToBatch(); - ComponentsProfiling.Instance.Start(nameof(UpdateDisplayAsync)); updateDisplayTask = UpdateDisplayAsync(batch); - ComponentsProfiling.Instance.End(nameof(UpdateDisplayAsync)); // Fire off the execution of OnAfterRenderAsync, but don't wait for it // if there is async work to be done. @@ -480,7 +472,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree { // Ensure we catch errors while running the render functions of the components. HandleException(e); - ComponentsProfiling.Instance.End(); return; } finally @@ -498,7 +489,6 @@ namespace Microsoft.AspNetCore.Components.RenderTree { ProcessRenderQueue(); } - ComponentsProfiling.Instance.End(); } private Task InvokeRenderCompletedCalls(ArrayRange<RenderTreeDiff> updatedComponents, Task updateDisplayTask) diff --git a/src/Components/Components/src/Rendering/ComponentState.cs b/src/Components/Components/src/Rendering/ComponentState.cs index 7b755efd5dfc30fc0dcf482039a9d78f89426766..3655d493350b5a3aa23ca95b6d9636384134801f 100644 --- a/src/Components/Components/src/Rendering/ComponentState.cs +++ b/src/Components/Components/src/Rendering/ComponentState.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.RenderTree; namespace Microsoft.AspNetCore.Components.Rendering @@ -57,7 +56,6 @@ namespace Microsoft.AspNetCore.Components.Rendering public void RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment) { - ComponentsProfiling.Instance.Start(); // A component might be in the render queue already before getting disposed by an // earlier entry in the render queue. In that case, rendering is a no-op. if (_componentWasDisposed) @@ -69,9 +67,7 @@ namespace Microsoft.AspNetCore.Components.Rendering (CurrentRenderTree, _renderTreeBuilderPrevious) = (_renderTreeBuilderPrevious, CurrentRenderTree); CurrentRenderTree.Clear(); - ComponentsProfiling.Instance.Start("BuildRenderTree"); renderFragment(CurrentRenderTree); - ComponentsProfiling.Instance.End("BuildRenderTree"); var diff = RenderTreeDiffBuilder.ComputeDiff( _renderer, @@ -81,7 +77,6 @@ namespace Microsoft.AspNetCore.Components.Rendering CurrentRenderTree.GetFrames()); batchBuilder.UpdatedComponentDiffs.Append(diff); batchBuilder.InvalidateParameterViews(); - ComponentsProfiling.Instance.End(); } public bool TryDisposeInBatch(RenderBatchBuilder batchBuilder, [NotNullWhen(false)] out Exception? exception) diff --git a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs index 4e9280c70f4b9654bc4d18961a8e538c4cdb1d83..566265c98880d96deb3e2d6b701e9e63c80e641d 100644 --- a/src/Components/Components/src/Rendering/RenderTreeBuilder.cs +++ b/src/Components/Components/src/Rendering/RenderTreeBuilder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; -using Microsoft.AspNetCore.Components.Profiling; using Microsoft.AspNetCore.Components.RenderTree; namespace Microsoft.AspNetCore.Components.Rendering @@ -45,7 +44,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="elementName">A value representing the type of the element.</param> public void OpenElement(int sequence, string elementName) { - ProfilingStart(); // We are entering a new scope, since we track the "duplicate attributes" per // element/component we might need to clean them up now. if (_hasSeenAddMultipleAttributes) @@ -56,7 +54,6 @@ namespace Microsoft.AspNetCore.Components.Rendering _openElementIndices.Push(_entries.Count); Append(RenderTreeFrame.Element(sequence, elementName)); - ProfilingEnd(); } /// <summary> @@ -65,7 +62,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// </summary> public void CloseElement() { - ProfilingStart(); var indexOfEntryBeingClosed = _openElementIndices.Pop(); // We might be closing an element with only attributes, run the duplicate cleanup pass @@ -77,7 +73,6 @@ namespace Microsoft.AspNetCore.Components.Rendering ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; entry = entry.WithElementSubtreeLength(_entries.Count - indexOfEntryBeingClosed); - ProfilingEnd(); } /// <summary> @@ -87,9 +82,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="markupContent">Content for the new markup frame.</param> public void AddMarkupContent(int sequence, string? markupContent) { - ProfilingStart(); Append(RenderTreeFrame.Markup(sequence, markupContent ?? string.Empty)); - ProfilingEnd(); } /// <summary> @@ -99,9 +92,7 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="textContent">Content for the new text frame.</param> public void AddContent(int sequence, string? textContent) { - ProfilingStart(); Append(RenderTreeFrame.Text(sequence, textContent ?? string.Empty)); - ProfilingEnd(); } /// <summary> @@ -111,7 +102,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="fragment">Content to append.</param> public void AddContent(int sequence, RenderFragment? fragment) { - ProfilingStart(); if (fragment != null) { // We surround the fragment with a region delimiter to indicate that the @@ -122,7 +112,6 @@ namespace Microsoft.AspNetCore.Components.Rendering fragment(this); CloseRegion(); } - ProfilingEnd(); } /// <summary> @@ -133,12 +122,10 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="value">The value used by <paramref name="fragment"/>.</param> public void AddContent<TValue>(int sequence, RenderFragment<TValue>? fragment, TValue value) { - ProfilingStart(); if (fragment != null) { AddContent(sequence, fragment(value)); } - ProfilingEnd(); } /// <summary> @@ -169,15 +156,12 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="name">The name of the attribute.</param> public void AddAttribute(int sequence, string name) { - ProfilingStart(); - if (_lastNonAttributeFrameType != RenderTreeFrameType.Element) { throw new InvalidOperationException($"Valueless attributes may only be added immediately after frames of type {RenderTreeFrameType.Element}"); } Append(RenderTreeFrame.Attribute(sequence, name, BoxedTrue)); - ProfilingEnd(); } /// <summary> @@ -194,7 +178,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="value">The value of the attribute.</param> public void AddAttribute(int sequence, string name, bool value) { - ProfilingStart(); AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -210,7 +193,6 @@ namespace Microsoft.AspNetCore.Components.Rendering { TrackAttributeName(name); } - ProfilingEnd(); } /// <summary> @@ -227,7 +209,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="value">The value of the attribute.</param> public void AddAttribute(int sequence, string name, string? value) { - ProfilingStart(); AssertCanAddAttribute(); if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -237,7 +218,6 @@ namespace Microsoft.AspNetCore.Components.Rendering { TrackAttributeName(name); } - ProfilingEnd(); } /// <summary> @@ -254,7 +234,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="value">The value of the attribute.</param> public void AddAttribute(int sequence, string name, MulticastDelegate? value) { - ProfilingStart(); AssertCanAddAttribute(); if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -264,7 +243,6 @@ namespace Microsoft.AspNetCore.Components.Rendering { TrackAttributeName(name); } - ProfilingEnd(); } /// <summary> @@ -285,7 +263,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// </remarks> public void AddAttribute(int sequence, string name, EventCallback value) { - ProfilingStart(); AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -310,7 +287,6 @@ namespace Microsoft.AspNetCore.Components.Rendering // Track the attribute name if needed since we elided the frame. TrackAttributeName(name); } - ProfilingEnd(); } /// <summary> @@ -331,7 +307,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// </remarks> public void AddAttribute<TArgument>(int sequence, string name, EventCallback<TArgument> value) { - ProfilingStart(); AssertCanAddAttribute(); if (_lastNonAttributeFrameType == RenderTreeFrameType.Component) { @@ -356,7 +331,6 @@ namespace Microsoft.AspNetCore.Components.Rendering // Track the attribute name if needed since we elided the frame. TrackAttributeName(name); } - ProfilingEnd(); } /// <summary> @@ -370,7 +344,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="value">The value of the attribute.</param> public void AddAttribute(int sequence, string name, object? value) { - ProfilingStart(); // This looks a bit daunting because we need to handle the boxed/object version of all of the // types that AddAttribute special cases. if (_lastNonAttributeFrameType == RenderTreeFrameType.Element) @@ -423,7 +396,6 @@ namespace Microsoft.AspNetCore.Components.Rendering // This is going to throw. Calling it just to get a consistent exception message. AssertCanAddAttribute(); } - ProfilingEnd(); } /// <summary> @@ -438,7 +410,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="frame">A <see cref="RenderTreeFrame"/> holding the name and value of the attribute.</param> public void AddAttribute(int sequence, in RenderTreeFrame frame) { - ProfilingStart(); if (frame.FrameType != RenderTreeFrameType.Attribute) { throw new ArgumentException($"The {nameof(frame.FrameType)} must be {RenderTreeFrameType.Attribute}."); @@ -446,7 +417,6 @@ namespace Microsoft.AspNetCore.Components.Rendering AssertCanAddAttribute(); Append(frame.WithAttributeSequence(sequence)); - ProfilingEnd(); } /// <summary> @@ -456,7 +426,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="attributes">A collection of key-value pairs representing attributes.</param> public void AddMultipleAttributes(int sequence, IEnumerable<KeyValuePair<string, object>>? attributes) { - ProfilingStart(); // Calling this up-front just to make sure we validate before mutating anything. AssertCanAddAttribute(); @@ -473,7 +442,6 @@ namespace Microsoft.AspNetCore.Components.Rendering AddAttribute(sequence, attribute.Key, attribute.Value); } } - ProfilingEnd(); } /// <summary> @@ -490,7 +458,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="updatesAttributeName">The name of another attribute whose value can be updated when the event handler is executed.</param> public void SetUpdatesAttributeName(string updatesAttributeName) { - ProfilingStart(); if (_entries.Count == 0) { throw new InvalidOperationException("No preceding attribute frame exists."); @@ -503,7 +470,6 @@ namespace Microsoft.AspNetCore.Components.Rendering } prevFrame = prevFrame.WithAttributeEventUpdatesAttributeName(updatesAttributeName); - ProfilingEnd(); } /// <summary> @@ -535,12 +501,10 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="value">The value for the key.</param> public void SetKey(object? value) { - ProfilingStart(); if (value == null) { // Null is equivalent to not having set a key, which is valuable because Razor syntax doesn't have an // easy way to have conditional directive attributes - ProfilingEnd(); return; } @@ -563,12 +527,10 @@ namespace Microsoft.AspNetCore.Components.Rendering default: throw new InvalidOperationException($"Cannot set a key on a frame of type {parentFrame.FrameType}."); } - ProfilingEnd(); } private void OpenComponentUnchecked(int sequence, Type componentType) { - ProfilingStart(); // We are entering a new scope, since we track the "duplicate attributes" per // element/component we might need to clean them up now. if (_hasSeenAddMultipleAttributes) @@ -579,7 +541,6 @@ namespace Microsoft.AspNetCore.Components.Rendering _openElementIndices.Push(_entries.Count); Append(RenderTreeFrame.ChildComponent(sequence, componentType)); - ProfilingEnd(); } /// <summary> @@ -588,7 +549,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// </summary> public void CloseComponent() { - ProfilingStart(); var indexOfEntryBeingClosed = _openElementIndices.Pop(); // We might be closing a component with only attributes. Run the attribute cleanup pass @@ -600,7 +560,6 @@ namespace Microsoft.AspNetCore.Components.Rendering ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; entry = entry.WithComponentSubtreeLength(_entries.Count - indexOfEntryBeingClosed); - ProfilingEnd(); } /// <summary> @@ -610,14 +569,12 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="elementReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param> public void AddElementReferenceCapture(int sequence, Action<ElementReference> elementReferenceCaptureAction) { - ProfilingStart(); if (GetCurrentParentFrameType() != RenderTreeFrameType.Element) { throw new InvalidOperationException($"Element reference captures may only be added as children of frames of type {RenderTreeFrameType.Element}"); } Append(RenderTreeFrame.ElementReferenceCapture(sequence, elementReferenceCaptureAction)); - ProfilingEnd(); } /// <summary> @@ -627,7 +584,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="componentReferenceCaptureAction">An action to be invoked whenever the reference value changes.</param> public void AddComponentReferenceCapture(int sequence, Action<object?> componentReferenceCaptureAction) { - ProfilingStart(); var parentFrameIndex = GetCurrentParentFrameIndex(); if (!parentFrameIndex.HasValue) { @@ -641,7 +597,6 @@ namespace Microsoft.AspNetCore.Components.Rendering } Append(RenderTreeFrame.ComponentReferenceCapture(sequence, componentReferenceCaptureAction, parentFrameIndexValue)); - ProfilingEnd(); } /// <summary> @@ -650,7 +605,6 @@ namespace Microsoft.AspNetCore.Components.Rendering /// <param name="sequence">An integer that represents the position of the instruction in the source code.</param> public void OpenRegion(int sequence) { - ProfilingStart(); // We are entering a new scope, since we track the "duplicate attributes" per // element/component we might need to clean them up now. if (_hasSeenAddMultipleAttributes) @@ -661,7 +615,6 @@ namespace Microsoft.AspNetCore.Components.Rendering _openElementIndices.Push(_entries.Count); Append(RenderTreeFrame.Region(sequence)); - ProfilingEnd(); } /// <summary> @@ -670,11 +623,9 @@ namespace Microsoft.AspNetCore.Components.Rendering /// </summary> public void CloseRegion() { - ProfilingStart(); var indexOfEntryBeingClosed = _openElementIndices.Pop(); ref var entry = ref _entries.Buffer[indexOfEntryBeingClosed]; entry = entry.WithRegionSubtreeLength(_entries.Count - indexOfEntryBeingClosed); - ProfilingEnd(); } private void AssertCanAddAttribute() @@ -702,29 +653,24 @@ namespace Microsoft.AspNetCore.Components.Rendering /// </summary> public void Clear() { - ProfilingStart(); _entries.Clear(); _openElementIndices.Clear(); _lastNonAttributeFrameType = null; _hasSeenAddMultipleAttributes = false; _seenAttributeNames?.Clear(); - ProfilingEnd(); } // internal because this should only be used during the post-event tree patching logic // It's expensive because it involves copying all the subsequent memory in the array internal void InsertAttributeExpensive(int insertAtIndex, int sequence, string attributeName, object? attributeValue) { - ProfilingStart(); // Replicate the same attribute omission logic as used elsewhere if ((attributeValue == null) || (attributeValue is bool boolValue && !boolValue)) { - ProfilingEnd(); return; } _entries.InsertExpensive(insertAtIndex, RenderTreeFrame.Attribute(sequence, attributeName, attributeValue)); - ProfilingEnd(); } /// <summary> @@ -748,7 +694,6 @@ namespace Microsoft.AspNetCore.Components.Rendering // Internal for testing internal void ProcessDuplicateAttributes(int first) { - ProfilingStart(); Debug.Assert(_hasSeenAddMultipleAttributes); // When AddMultipleAttributes method has been called, we need to postprocess attributes while closing @@ -830,7 +775,6 @@ namespace Microsoft.AspNetCore.Components.Rendering seenAttributeNames.Clear(); _hasSeenAddMultipleAttributes = false; - ProfilingEnd(); } // Internal for testing @@ -847,22 +791,7 @@ namespace Microsoft.AspNetCore.Components.Rendering void IDisposable.Dispose() { - ProfilingStart(); _entries.Dispose(); - ProfilingEnd(); } - - // Having too many calls to ComponentsProfiling.Instance.Start/End has a measurable perf impact - // even when capturing is disabled. So, to enable detailed profiling for this class, define the - // Profile_RenderTreeBuilder compiler symbol, otherwise the calls are compiled out entirely. - // Enabling detailed profiling adds about 5% to rendering benchmark times. - - [Conditional("Profile_RenderTreeBuilder")] - private static void ProfilingStart([CallerMemberName] string? name = null) - => ComponentsProfiling.Instance.Start(name); - - [Conditional("Profile_RenderTreeBuilder")] - private static void ProfilingEnd([CallerMemberName] string? name = null) - => ComponentsProfiling.Instance.End(name); } } diff --git a/src/Components/Web.JS/dist/Release/blazor.server.js b/src/Components/Web.JS/dist/Release/blazor.server.js index 32c6455b2002826fbd11c96111043d917ecb96ab..97da9d5ed9498ef67c205008e9f2b51dbdecc0e9 100644 Binary files a/src/Components/Web.JS/dist/Release/blazor.server.js and b/src/Components/Web.JS/dist/Release/blazor.server.js differ diff --git a/src/Components/Web.JS/dist/Release/blazor.webassembly.js b/src/Components/Web.JS/dist/Release/blazor.webassembly.js index cd99a3089a22c368fc35f4e72cab4227fa267207..c335f6c5649a4aa07b6637179fcc2199e5aa1049 100644 Binary files a/src/Components/Web.JS/dist/Release/blazor.webassembly.js and b/src/Components/Web.JS/dist/Release/blazor.webassembly.js differ diff --git a/src/Components/Web.JS/src/Boot.Server.ts b/src/Components/Web.JS/src/Boot.Server.ts index c781d52aacc8e300ea0bfd2fd40617a3b95a0e9f..ed5647dd128eecff705e58278bf058f957239944 100644 --- a/src/Components/Web.JS/src/Boot.Server.ts +++ b/src/Components/Web.JS/src/Boot.Server.ts @@ -12,7 +12,6 @@ import { setEventDispatcher } from './Rendering/RendererEventDispatcher'; import { resolveOptions, CircuitStartOptions } from './Platform/Circuits/CircuitStartOptions'; import { DefaultReconnectionHandler } from './Platform/Circuits/DefaultReconnectionHandler'; import { attachRootComponentToLogicalElement } from './Rendering/Renderer'; -import { initializeProfiling } from './Platform/Profiling'; let renderingFailed = false; let started = false; @@ -22,7 +21,6 @@ async function boot(userOptions?: Partial<CircuitStartOptions>): Promise<void> { throw new Error('Blazor has already started.'); } started = true; - initializeProfiling(null); // Establish options to be used const options = resolveOptions(userOptions); diff --git a/src/Components/Web.JS/src/Boot.WebAssembly.ts b/src/Components/Web.JS/src/Boot.WebAssembly.ts index 99b117ad78d50305a8571622f80946aca3f81409..b0a08fe2b4b6820331b5fe0e422a354783c76f86 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.ts @@ -11,7 +11,6 @@ import { WebAssemblyConfigLoader } from './Platform/WebAssemblyConfigLoader'; import { BootConfigResult } from './Platform/BootConfig'; import { Pointer } from './Platform/Platform'; import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions'; -import { profileStart, profileEnd } from './Platform/Profiling'; let started = false; @@ -34,8 +33,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> { const platform = Environment.setPlatform(monoPlatform); window['Blazor'].platform = platform; window['Blazor']._internal.renderBatch = (browserRendererId: number, batchAddress: Pointer) => { - profileStart('renderBatch'); - // We're going to read directly from the .NET memory heap, so indicate to the platform // that we don't want anything to modify the memory contents during this time. Currently this // is only guaranteed by the fact that .NET code doesn't run during this time, but in the @@ -47,8 +44,6 @@ async function boot(options?: Partial<WebAssemblyStartOptions>): Promise<void> { } finally { heapLock.release(); } - - profileEnd('renderBatch'); }; // Configure navigation via JS Interop diff --git a/src/Components/Web.JS/src/GlobalExports.ts b/src/Components/Web.JS/src/GlobalExports.ts index 08f7557ba510f8e3f11d516b1b5b398ff8f5030d..df7f9b18a8425af1fab0281c842d67f1b5c573be 100644 --- a/src/Components/Web.JS/src/GlobalExports.ts +++ b/src/Components/Web.JS/src/GlobalExports.ts @@ -1,7 +1,6 @@ import { navigateTo, internalFunctions as navigationManagerInternalFunctions } from './Services/NavigationManager'; import { attachRootComponentToElement } from './Rendering/Renderer'; import { domFunctions } from './DomWrapper'; -import { setProfilingEnabled } from './Platform/Profiling'; // Make the following APIs available in global scope for invocation from JS window['Blazor'] = { @@ -11,6 +10,5 @@ window['Blazor'] = { attachRootComponentToElement, navigationManager: navigationManagerInternalFunctions, domWrapper: domFunctions, - setProfilingEnabled: setProfilingEnabled, }, }; diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index d2abd2a70edf03bccae334316b6bc7c23694b87e..5a5eda94224352a31bb8b659033fca19d448484e 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -5,7 +5,6 @@ import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResour import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock } from '../Platform'; import { loadTimezoneData } from './TimezoneDataFile'; import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions'; -import { initializeProfiling } from '../Profiling'; let mono_wasm_add_assembly: (name: string, heapAddress: number, length: number) => void; const appBinDirName = 'appBinDir'; @@ -36,10 +35,6 @@ export const monoPlatform: Platform = { start: function start(resourceLoader: WebAssemblyResourceLoader) { return new Promise<void>((resolve, reject) => { attachDebuggerHotkey(resourceLoader); - initializeProfiling(isCapturing => { - const setCapturingMethod = bindStaticMethod('Microsoft.AspNetCore.Components', 'Microsoft.AspNetCore.Components.Profiling.WebAssemblyComponentsProfiling', 'SetCapturing'); - setCapturingMethod(isCapturing); - }); // dotnet.js assumes the existence of this window['Browser'] = { diff --git a/src/Components/Web.JS/src/Platform/Profiling.ts b/src/Components/Web.JS/src/Platform/Profiling.ts deleted file mode 100644 index 9c4f0f220b445ad39deed852ea93d43e255c9cfb..0000000000000000000000000000000000000000 --- a/src/Components/Web.JS/src/Platform/Profiling.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { System_String } from './Platform'; - -interface TimingEntry { - // To minimize overhead, don't even decode the strings that arrive from .NET. Assume they are compile-time constants - // and hence the memory address will be fixed, so we can just store the pointer value. - name: string | System_String; - type: 'start' | 'end'; - timestamp: number; -} - -interface TraceEvent { - // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview - name: string; - cat: string; // Category - ph: 'B' | 'E'; // Phase - ts: number; // Timestamp in microseconds - pid: number; // Process ID - tid: number; // Thread ID -} - -let updateCapturingStateInHost: (isCapturing: boolean) => void; -let captureStartTime = 0; -const blazorProfilingEnabledKey = 'blazorProfilingEnabled'; -const profilingEnabled = !!localStorage[blazorProfilingEnabledKey]; -const entryLog: TimingEntry[] = []; -const openRegionsStack: (string | System_String)[] = []; - -export function setProfilingEnabled(enabled: boolean) { - // We only wire up the hotkeys etc. if the following localStorage entry is present during startup - // This is to ensure we're not interfering with any other hotkeys that developers might want to - // use for different purposes, plus it gives us a single point where we can notify .NET code during - // startup about whether profiling should be enabled. - localStorage[blazorProfilingEnabledKey] = (enabled !== false); - location.reload(); -} - -export function initializeProfiling(setCapturingCallback: ((isCapturing: boolean) => void) | null) { - if (!profilingEnabled) { - return; - } - - updateCapturingStateInHost = setCapturingCallback || (() => {}); - - // Attach hotkey (alt/cmd)+shift+p - const altKeyName = navigator.platform.match(/^Mac/i) ? 'Cmd' : 'Alt'; - console.info(`Profiling hotkey: Shift+${altKeyName}+P (when application has focus)`); - document.addEventListener('keydown', evt => { - if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyP') { - toggleCaptureEnabled(); - } - }); -} - -export function profileStart(name: System_String | string) { - if (!captureStartTime) { - return; - } - - const startTime = performance.now(); - openRegionsStack.push(name); - entryLog.push({ name: name, type: 'start', timestamp: startTime }); -} - -export function profileEnd(name: System_String | string) { - if (!captureStartTime) { - return; - } - - const endTime = performance.now(); - const poppedRegionName = openRegionsStack.pop(); - if (!poppedRegionName) { - throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the stack was empty.`); - } else if (poppedRegionName !== name) { - throw new Error(`Profiling mismatch: tried to end profiling for '${readJsString(name)}', but the top stack item was '${readJsString(poppedRegionName)}'.`); - } - - entryLog.push({ name: name, type: 'end', timestamp: endTime }); -} - -function profileReset() { - openRegionsStack.length = 0; - entryLog.length = 0; - captureStartTime = 0; - updateCapturingStateInHost(false); -} - -function profileExport() { - const traceEvents: TraceEvent[] = entryLog.map(entry => ({ - name: readJsString(entry.name)!, - cat: 'PERF', - ph: entry.type === 'start' ? 'B': 'E', - ts: (entry.timestamp - captureStartTime) * 1000, - pid: 0, - tid: 0, - })); - const traceEventsJson = JSON.stringify(traceEvents); - const traceEventsBuffer = new TextEncoder().encode(traceEventsJson); - const anchorElement = document.createElement('a'); - anchorElement.href = URL.createObjectURL(new Blob([traceEventsBuffer])); - anchorElement.setAttribute('download', 'trace.json'); - anchorElement.click(); - URL.revokeObjectURL(anchorElement.href); -} - -function toggleCaptureEnabled() { - if (!captureStartTime) { - displayOverlayMessage('Started capturing performance profile...'); - updateCapturingStateInHost(true); - captureStartTime = performance.now(); - } else { - displayOverlayMessage('Finished capturing performance profile'); - profileExport(); - profileReset(); - } -} - -function displayOverlayMessage(message: string) { - const elem = document.createElement('div'); - elem.textContent = message; - elem.setAttribute('style', 'position: absolute; z-index: 99999; font-family: \'Sans Serif\'; top: 0; left: 0; padding: 4px; font-size: 12px; background-color: purple; color: white;'); - document.body.appendChild(elem); - setTimeout(() => document.body.removeChild(elem), 3000); -} - -function readJsString(str: string | System_String) { - // This is expensive, so don't do it while capturing timings. Only do it as part of the export process. - return typeof str === 'string' ? str : BINDING.conv_string(str); -} - -// These globals deliberately differ from our normal conventions for attaching functions inside Blazor.* -// because the intention is to minimize overhead in all reasonable ways. Having any dot-separators in the -// name would cause extra string allocations on every invocation. -window['_blazorProfileStart'] = profileStart; -window['_blazorProfileEnd'] = profileEnd; diff --git a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts index 0c9c3ea2b96adb82869d52af84a5e1144211dc91..14faf703291c9dd6eeca511f6870f09cc602f17d 100644 --- a/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts +++ b/src/Components/Web.JS/src/Rendering/BrowserRenderer.ts @@ -6,7 +6,6 @@ import { applyCaptureIdToElement } from './ElementReferenceCapture'; import { EventFieldInfo } from './EventFieldInfo'; import { dispatchEvent } from './RendererEventDispatcher'; import { attachToEventDelegator as attachNavigationManagerToEventDelegator } from '../Services/NavigationManager'; -import { profileEnd, profileStart } from '../Platform/Profiling'; const selectValuePropname = '_blazorSelectValue'; const sharedTemplateElemForParsing = document.createElement('template'); const sharedSvgElemForParsing = document.createElementNS('http://www.w3.org/2000/svg', 'g'); @@ -41,8 +40,6 @@ export class BrowserRenderer { } public updateComponent(batch: RenderBatch, componentId: number, edits: ArrayBuilderSegment<RenderTreeEdit>, referenceFrames: ArrayValues<RenderTreeFrame>): void { - profileStart('updateComponent'); - const element = this.childComponentLocations[componentId]; if (!element) { throw new Error(`No element is currently associated with component ${componentId}`); @@ -70,8 +67,6 @@ export class BrowserRenderer { if ((activeElementBefore instanceof HTMLElement) && ownerDocument && ownerDocument.activeElement !== activeElementBefore) { activeElementBefore.focus(); } - - profileEnd('updateComponent'); } public disposeComponent(componentId: number) { diff --git a/src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs b/src/Components/WebAssembly/JSInterop/src/InternalCalls.cs similarity index 100% rename from src/Components/Shared/src/WebAssemblyJSInteropInternalCalls.cs rename to src/Components/WebAssembly/JSInterop/src/InternalCalls.cs diff --git a/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj index 64d0f52e10829e1a042792eafffd77288bbdc5d4..8b58eb02dd684e0421c5ed823004d5eab13d6518 100644 --- a/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj +++ b/src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj @@ -9,7 +9,6 @@ </PropertyGroup> <ItemGroup> - <Compile Include="$(ComponentsSharedSourceRoot)src\WebAssemblyJSInteropInternalCalls.cs" /> <Reference Include="Microsoft.JSInterop" /> </ItemGroup>