diff --git a/Nerfed.Editor/Systems/EditorProfilerWindow.cs b/Nerfed.Editor/Systems/EditorProfilerWindow.cs index 55af743..0d8a6c2 100644 --- a/Nerfed.Editor/Systems/EditorProfilerWindow.cs +++ b/Nerfed.Editor/Systems/EditorProfilerWindow.cs @@ -13,7 +13,7 @@ namespace Nerfed.Editor.Systems private int selectedFrame = 0; private int previousSelectedFrame = -1; - private IOrderedEnumerable> orderedCombinedData = null; + private IOrderedEnumerable> orderedCombinedData = null; private IOrderedEnumerable> orderedThreadRollingData = null; private readonly ProfilerVisualizer.TimelineState timelineState = new ProfilerVisualizer.TimelineState(); private readonly List frameSnapshot = new List(256); @@ -143,14 +143,16 @@ namespace Nerfed.Editor.Systems ImGui.BeginChild("Hierachy", new Vector2(150, 0), ImGuiChildFlags.ResizeX); - if (ImGui.BeginTable("ProfilerData", 6, tableFlags, new Vector2(0, 0))) + if (ImGui.BeginTable("ProfilerData", 8, tableFlags, new Vector2(0, 0))) { - ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.40f, 0); - ImGui.TableSetupColumn("category", ImGuiTableColumnFlags.WidthStretch, 0.15f, 1); - ImGui.TableSetupColumn("tags", ImGuiTableColumnFlags.WidthStretch, 0.10f, 2); - ImGui.TableSetupColumn("thread", ImGuiTableColumnFlags.WidthStretch, 0.10f, 3); + ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.30f, 0); + ImGui.TableSetupColumn("category", ImGuiTableColumnFlags.WidthStretch, 0.12f, 1); + ImGui.TableSetupColumn("tags", ImGuiTableColumnFlags.WidthStretch, 0.08f, 2); + ImGui.TableSetupColumn("thread", ImGuiTableColumnFlags.WidthStretch, 0.08f, 3); ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.10f, 4); ImGui.TableSetupColumn("self", ImGuiTableColumnFlags.WidthStretch, 0.10f, 5); + ImGui.TableSetupColumn("alloc(B)", ImGuiTableColumnFlags.WidthStretch, 0.11f, 6); + ImGui.TableSetupColumn("self alloc", ImGuiTableColumnFlags.WidthStretch, 0.11f, 7); ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible ImGui.TableHeadersRow(); @@ -191,6 +193,10 @@ namespace Nerfed.Editor.Systems ImGui.Text($"{node.ElapsedMilliseconds():0.000}"); ImGui.TableNextColumn(); ImGui.Text($"{node.SelfMilliseconds():0.000}"); + ImGui.TableNextColumn(); + ImGui.Text($"{node.AllocatedBytes}"); + ImGui.TableNextColumn(); + ImGui.Text($"{node.SelfAllocatedBytes()}"); if (isOpen) { @@ -202,7 +208,7 @@ namespace Nerfed.Editor.Systems } } - private static void DrawCombined(in IOrderedEnumerable> orderedCombinedData) + private static void DrawCombined(in IOrderedEnumerable> orderedCombinedData) { if(orderedCombinedData == null) { @@ -211,18 +217,20 @@ namespace Nerfed.Editor.Systems ImGui.BeginChild("Combined", new Vector2(0, 0)); - if (ImGui.BeginTable("ProfilerCombinedData", 6, tableFlags, new Vector2(0, 0))) + if (ImGui.BeginTable("ProfilerCombinedData", 8, tableFlags, new Vector2(0, 0))) { - ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.45f, 0); - ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.14f, 1); - ImGui.TableSetupColumn("self", ImGuiTableColumnFlags.WidthStretch, 0.14f, 2); - ImGui.TableSetupColumn("calls", ImGuiTableColumnFlags.WidthStretch, 0.10f, 3); - ImGui.TableSetupColumn("avg", ImGuiTableColumnFlags.WidthStretch, 0.10f, 4); - ImGui.TableSetupColumn("p95", ImGuiTableColumnFlags.WidthStretch, 0.10f, 5); + ImGui.TableSetupColumn("name", ImGuiTableColumnFlags.WidthStretch, 0.32f, 0); + ImGui.TableSetupColumn("ms", ImGuiTableColumnFlags.WidthStretch, 0.11f, 1); + ImGui.TableSetupColumn("self", ImGuiTableColumnFlags.WidthStretch, 0.11f, 2); + ImGui.TableSetupColumn("calls", ImGuiTableColumnFlags.WidthStretch, 0.08f, 3); + ImGui.TableSetupColumn("avg", ImGuiTableColumnFlags.WidthStretch, 0.09f, 4); + ImGui.TableSetupColumn("p95", ImGuiTableColumnFlags.WidthStretch, 0.09f, 5); + ImGui.TableSetupColumn("alloc(B)", ImGuiTableColumnFlags.WidthStretch, 0.10f, 6); + ImGui.TableSetupColumn("self alloc", ImGuiTableColumnFlags.WidthStretch, 0.10f, 7); ImGui.TableSetupScrollFreeze(0, 1); // Make row always visible ImGui.TableHeadersRow(); - foreach (KeyValuePair combinedData in orderedCombinedData) + foreach (KeyValuePair combinedData in orderedCombinedData) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -237,6 +245,10 @@ namespace Nerfed.Editor.Systems ImGui.Text($"{combinedData.Value.avgMs:0.000}"); ImGui.TableNextColumn(); ImGui.Text($"{combinedData.Value.p95Ms:0.000}"); + ImGui.TableNextColumn(); + ImGui.Text($"{combinedData.Value.allocBytes}"); + ImGui.TableNextColumn(); + ImGui.Text($"{combinedData.Value.selfAllocBytes}"); } ImGui.EndTable(); @@ -245,10 +257,10 @@ namespace Nerfed.Editor.Systems ImGui.EndChild(); } - private static IOrderedEnumerable> CalculateCombinedData(Profiler.Frame frame) + private static IOrderedEnumerable> CalculateCombinedData(Profiler.Frame frame) { IReadOnlyDictionary rollingData = Profiler.GetRollingLabelMetricsSnapshot(); - Dictionary combinedRecordData = new Dictionary(128); + Dictionary combinedRecordData = new Dictionary(128); foreach (KeyValuePair metric in frame.LabelMetrics) { Profiler.RollingLabelMetrics rolling = default; @@ -257,7 +269,7 @@ namespace Nerfed.Editor.Systems rolling = found; } - combinedRecordData[metric.Key] = (metric.Value.InclusiveMs, metric.Value.SelfMs, metric.Value.Calls, rolling.AverageMs, rolling.P95Ms); + combinedRecordData[metric.Key] = (metric.Value.InclusiveMs, metric.Value.SelfMs, metric.Value.Calls, rolling.AverageMs, rolling.P95Ms, metric.Value.AllocatedBytes, metric.Value.SelfAllocatedBytes); } return combinedRecordData.OrderByDescending(x => x.Value.ms); } diff --git a/Nerfed.Runtime/Profiler.cs b/Nerfed.Runtime/Profiler.cs index 319d2bd..2511a95 100644 --- a/Nerfed.Runtime/Profiler.cs +++ b/Nerfed.Runtime/Profiler.cs @@ -39,13 +39,15 @@ public static class Profiler public readonly struct LabelMetrics { - public LabelMetrics(double inclusiveMs, double selfMs, uint calls, double minInclusiveMs, double maxInclusiveMs) + public LabelMetrics(double inclusiveMs, double selfMs, uint calls, double minInclusiveMs, double maxInclusiveMs, long allocatedBytes, long selfAllocatedBytes) { InclusiveMs = inclusiveMs; SelfMs = selfMs; Calls = calls; MinInclusiveMs = minInclusiveMs; MaxInclusiveMs = maxInclusiveMs; + AllocatedBytes = allocatedBytes; + SelfAllocatedBytes = selfAllocatedBytes; } public double InclusiveMs { get; } @@ -53,6 +55,8 @@ public static class Profiler public uint Calls { get; } public double MinInclusiveMs { get; } public double MaxInclusiveMs { get; } + public long AllocatedBytes { get; } + public long SelfAllocatedBytes { get; } } public readonly struct RollingLabelMetrics @@ -340,6 +344,8 @@ public static class Profiler { double inclusiveMs = node.ElapsedMilliseconds(); double selfMs = node.SelfMilliseconds(); + long allocBytes = node.AllocatedBytes; + long selfAllocBytes = node.SelfAllocatedBytes(); if (labelMetrics.TryGetValue(node.Label, out LabelMetrics current)) { @@ -348,11 +354,13 @@ public static class Profiler current.SelfMs + selfMs, current.Calls + 1, Math.Min(current.MinInclusiveMs, inclusiveMs), - Math.Max(current.MaxInclusiveMs, inclusiveMs)); + Math.Max(current.MaxInclusiveMs, inclusiveMs), + current.AllocatedBytes + allocBytes, + current.SelfAllocatedBytes + selfAllocBytes); } else { - labelMetrics[node.Label] = new LabelMetrics(inclusiveMs, selfMs, 1, inclusiveMs, inclusiveMs); + labelMetrics[node.Label] = new LabelMetrics(inclusiveMs, selfMs, 1, inclusiveMs, inclusiveMs, allocBytes, selfAllocBytes); } if (categoryMetrics.TryGetValue(node.Category, out LabelMetrics categoryCurrent)) @@ -362,11 +370,13 @@ public static class Profiler categoryCurrent.SelfMs + selfMs, categoryCurrent.Calls + 1, Math.Min(categoryCurrent.MinInclusiveMs, inclusiveMs), - Math.Max(categoryCurrent.MaxInclusiveMs, inclusiveMs)); + Math.Max(categoryCurrent.MaxInclusiveMs, inclusiveMs), + categoryCurrent.AllocatedBytes + allocBytes, + categoryCurrent.SelfAllocatedBytes + selfAllocBytes); } else { - categoryMetrics[node.Category] = new LabelMetrics(inclusiveMs, selfMs, 1, inclusiveMs, inclusiveMs); + categoryMetrics[node.Category] = new LabelMetrics(inclusiveMs, selfMs, 1, inclusiveMs, inclusiveMs, allocBytes, selfAllocBytes); } for (int i = 0; i < node.Children.Count; i++) @@ -423,8 +433,11 @@ public static class Profiler public long EndTime { get; private set; } public int ManagedThreadId { get; private set; } public List Children { get; } = new List(); + public long AllocatedBytes { get; private set; } internal ScopeNode Parent { get; private set; } internal long ChildrenDurationTicks { get; private set; } + internal long ChildrenAllocatedBytes { get; private set; } + private long allocatedBytesAtStart; internal void Reset(string label, string category, ulong tagMask, int managedThreadId, ScopeNode parent) { @@ -436,7 +449,10 @@ public static class Profiler StartTime = Stopwatch.GetTimestamp(); EndTime = 0; ChildrenDurationTicks = 0; + ChildrenAllocatedBytes = 0; + AllocatedBytes = 0; Children.Clear(); + allocatedBytesAtStart = GC.GetAllocatedBytesForCurrentThread(); } internal void End() @@ -447,9 +463,11 @@ public static class Profiler } EndTime = Stopwatch.GetTimestamp(); + AllocatedBytes = Math.Max(0, GC.GetAllocatedBytesForCurrentThread() - allocatedBytesAtStart); if (Parent != null) { Parent.ChildrenDurationTicks += Math.Max(0, EndTime - StartTime); + Parent.ChildrenAllocatedBytes += AllocatedBytes; } } @@ -465,6 +483,11 @@ public static class Profiler return ((double)selfTicks) * 1000 / Stopwatch.Frequency; } + public long SelfAllocatedBytes() + { + return Math.Max(0, AllocatedBytes - ChildrenAllocatedBytes); + } + internal ScopeNode AddChild(string label, string category, ulong tagMask) { ScopeNode child = RentNode(label, category, tagMask, ManagedThreadId, this);