2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UnityAdvent Calendar 2020

Day 19

Unityプロファイラーウィンドウで負荷の高いフレームを抽出する

Posted at

はじめに

Unityのプロファイラーウィンドウ上で負荷の高い箇所を抽出し、選択してくれる機能を作成してみました。
HeavyFrames.gif

環境

  • 確認バージョン
    • Unity2019.4.13f1
    • Unity2020.1.10f1
  • Windows

ウィンドウ

Refreshボタンを押すと開いているプロファイラーの情報から重いフレームを抽出して <>ボタンで
重いフレームを選択できるようになっています。
2020-12-19_16h39_41.png

負荷が高いところの抽出

プロファイラーウィンドウでスクリプトから Time msの高いところを抽出します。

     var property = new ProfilerProperty();
        var firstIndex = ProfilerDriver.firstFrameIndex;
        var lastIndex = ProfilerDriver.lastFrameIndex;
        for (var index = firstIndex; index < lastIndex; index++)
        {
            property.SetRoot(index, HierarchyFrameDataView.columnSelfTime, 0);
            var value = property.GetColumnAsSingle(HierarchyFrameDataView.columnTotalTime);
            if (value > thresholdField.value)
            {
                 // 負荷が高い
            }
        }

負荷が高いかどうかの判断については PlayerLoopのTime(ms)がしきい値(Threshold(ms))を超えている箇所としています。
ProfilerPropertyを上記のように使うことで負荷がとれることができます。

コード全体

コード全体になります。UIElementsを使用しています。
Gistにもアップしましたので使っていただければと思います。

ProfilerHeavyFrameCaptureWindow.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.Profiling;
using UnityEditor.UIElements;
using UnityEditorInternal;

/// <summary>
/// Profilerの情報を取得するウィンドウ
/// </summary>
public class ProfilerHeavyFrameCaptureWindow : EditorWindow
{
    class HeavyFrame
    {
        public int frameIndex;
        public string label;
    }
    List<HeavyFrame> heavyFrames = new List<HeavyFrame>();
    int heavyIndex;
    FloatField thresholdField;
    Label countLabel;

    [MenuItem("Window/ProfilerHeavyFrameCapture")]
    static void ShowWindow()
    {
        GetWindow<ProfilerHeavyFrameCaptureWindow>();
    }

    void OnEnable()
    {
        var toolbar = new Toolbar();
        toolbar.style.minHeight = 20F;
        rootVisualElement.Add(toolbar);

        var prevButton = new ToolbarButton(() => Prev());
        prevButton.text = "<";
        prevButton.style.minWidth = 100F;
        toolbar.Add(prevButton);

        var nextButton = new ToolbarButton(() => Next());
        nextButton.text = ">";
        nextButton.style.minWidth = 100F;
        toolbar.Add(nextButton);

        countLabel = new Label();
        countLabel.text = "";
        toolbar.Add(countLabel);

        var refreshButton = new ToolbarButton(() => Refresh());
        refreshButton.text = "Refresh";
        toolbar.Add(refreshButton);

        thresholdField = new FloatField("Threshold(ms)");
        thresholdField.viewDataKey = "threshold";
        thresholdField.value = 20F;
        rootVisualElement.Add(thresholdField);
    }

    void SetCurrentFrame()
    {
        var frameIndex = heavyFrames[heavyIndex].frameIndex;
        SetCurrentFrameOfProfilerWindow(frameIndex);
        UpdateCountLabel();
    }

    void UpdateCountLabel()
    {
        countLabel.text = $"{heavyIndex + 1}/{heavyFrames.Count}";
    }

    void Prev()
    {
        heavyIndex--;
        heavyIndex = Math.Max(heavyIndex, 0);
        SetCurrentFrame();
    }

    void Next()
    {
        heavyIndex++;
        heavyIndex = Math.Min(heavyIndex, heavyFrames.Count - 1);
        SetCurrentFrame();
    }

    void Refresh()
    {
        CaptureHeavyFrames();
        UpdateCountLabel();
    }

    void CaptureHeavyFrames()
    {
        heavyFrames.Clear();
        var property = new ProfilerProperty();
        var firstIndex = ProfilerDriver.firstFrameIndex;
        var lastIndex = ProfilerDriver.lastFrameIndex;
        for (var index = firstIndex; index < lastIndex; index++)
        {
            property.SetRoot(index, HierarchyFrameDataView.columnSelfTime, 0);
            var value = property.GetColumnAsSingle(HierarchyFrameDataView.columnTotalTime);
            if (value > thresholdField.value)
            {

                heavyFrames.Add(new HeavyFrame { frameIndex = index, label = index.ToString() });
            }
        }
    }

    /// <summary>
    /// プロファイラーウィンドウを指定のframeIndexが選択状態にします。
    /// </summary>
    void SetCurrentFrameOfProfilerWindow(int frameIndex)
    {
        var profiler = Resources.FindObjectsOfTypeAll<EditorWindow>()
        .FirstOrDefault(x => x.GetType().Name == "ProfilerWindow");
        var setCurrentFrame = profiler.GetType().GetMethod("SetCurrentFrame", BindingFlags.NonPublic | BindingFlags.Instance);
        setCurrentFrame.Invoke(profiler, new object[] { frameIndex });
        profiler.Repaint();
    }
}

最後に

負荷の高い箇所を楽に選択できればいいなとおもい作成したエディタ拡張ではありましたがいかがでしょうか。
プロファイリングのタスクで重宝すればと思います。

参考資料

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?