-
-
Save mandarinx/5721971 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// You can switch to a regular Update() loop | |
// if you comment out this line. (makes debugging easier) | |
#define COROUTINE | |
// | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Text; | |
using UnityEngine; | |
using Debug = UnityEngine.Debug; | |
// Simple code profiler class for Unity projects | |
// @robotduck 2011 | |
// | |
// usage: place on an empty gameobject in your scene | |
// then insert calls to CodeProfiler.Begin(id) and | |
// CodeProfiler.End(id) around the section you want to profile | |
// | |
// "id" should be string, unique to each code portion that you're timing | |
// for example, in your enemy update function, you might have: | |
// | |
// function Update { | |
// CodeProfiler.Begin("Enemy:Update"); | |
// <the rest of your enemy update code here> | |
// | |
// CodeProfiler.Begin("Enemy:Targeting"); | |
// <expensive inner code here> | |
// CodeProfiler.End(); | |
// | |
// CodeProfiler.End(); | |
// } | |
// | |
// the Begin id and the End id must match exactly. | |
public class CodeProfiler : MonoBehaviour { | |
// this is the ProfileRecording class which is simply included | |
// directly after the CodeProfiler class in the same file. | |
// The ProfileRecording class is basically for "internal use | |
// only" - you don't need to place it on a gameobject or interact | |
// with it in any way yourself, it's purely used by the | |
// CodeProfiler to do its job. | |
sealed class ProfilerRecording : Stopwatch { | |
// this class accumulates time for a single recording | |
public string name; | |
public int totalMilliseconds; | |
public int iterations; | |
public ProfilerRecording(string id) : base() { | |
name = id; | |
} | |
new public void Start() { | |
iterations++; | |
base.Start(); | |
} | |
new public void Stop() { | |
// If code is really fast ElapsedMilliseconds will be 0. | |
// Technically this is impossible, so we round up. | |
totalMilliseconds += Mathf.Max(1, (int) ElapsedMilliseconds); | |
base.Reset(); | |
} | |
new public void Reset() { | |
totalMilliseconds = 0; | |
iterations = 0; | |
base.Reset(); | |
} | |
} | |
// column width for text display | |
const int COL_WIDTH = 10; | |
const int MS_PER_SAMPLE = 5000; | |
readonly static string headerFormat = string.Format("{{0,-{0}}}{{1,-{0}}}{{2,-{0}}}{{3,-{0}}}{{4,-{0}}}\n", COL_WIDTH); | |
readonly static string recordFormat = string.Format("{{0,-{0}}}{{1,{0}:0.000%}}{{2,{0}:0.000ms}}{{3,{0}:0.000}}{{4,{0}:0.000ms}}\n", COL_WIDTH); | |
readonly Rect displayRect = new Rect(10, 10, 400, 300); | |
static StringBuilder displayText; | |
static Dictionary<string, ProfilerRecording> recordings; | |
static Stack<ProfilerRecording> stack; | |
static bool running; | |
static int frameCount; | |
static CodeProfiler instance; | |
static Stopwatch stopwatch; | |
static void CreateInstance() { | |
if (instance != null) { | |
return; | |
} | |
GameObject profiler = new GameObject("__Profiler__"); | |
SetInstance(profiler.AddComponent<CodeProfiler>()); | |
} | |
static bool SetInstance(CodeProfiler inst) { | |
if (instance != null && instance != inst) { | |
return false; | |
} else if (instance == inst) { | |
return true; | |
} | |
recordings = new Dictionary<string, ProfilerRecording>(100); | |
displayText = new StringBuilder("\n\nTaking initial readings..."); | |
stopwatch = new Stopwatch(); | |
stack = new Stack<ProfilerRecording>(100); | |
running = true; | |
instance = inst; | |
return true; | |
} | |
static void ClearInstance(CodeProfiler inst) { | |
if (instance != inst) { | |
return; | |
} | |
recordings = null; | |
displayText = null; | |
stopwatch = null; | |
instance = null; | |
running = false; | |
} | |
void OnEnable() { | |
if (!SetInstance(this)) { | |
DestroyImmediate(this); | |
return; | |
} | |
#if COROUTINE | |
StartCoroutine(UpdateCoroutine()); | |
#endif | |
} | |
void OnDisable() { | |
#if COROUTINE | |
StopAllCoroutines(); | |
#endif | |
ClearInstance(this); | |
} | |
void OnGUI() { | |
GUI.Box(displayRect, "Code Profiler"); | |
GUI.Label(displayRect, string.Format("\n\n{0}", displayText)); | |
} | |
public static void Begin(string id) { | |
if (!running) { | |
CreateInstance(); | |
} | |
if (string.IsNullOrEmpty(id)) { | |
Debug.LogWarning("Empty profiler id!"); | |
id = "<null>"; | |
} | |
ProfilerRecording record = null; | |
// create a new recording if not present in the list | |
if (!recordings.TryGetValue(id, out record)) { | |
record = new ProfilerRecording(id); | |
recordings[id] = record; | |
} | |
record.Start(); | |
stack.Push(record); | |
} | |
public static void End() { | |
stack.Pop().Stop(); | |
} | |
// just here for backwards compatibility | |
public static void End(string id) { | |
End(); | |
} | |
#if COROUTINE | |
static IEnumerator UpdateCoroutine() { | |
#else | |
void Update() { | |
#endif | |
#if COROUTINE | |
while (running) { | |
#endif | |
if (!stopwatch.IsRunning) { | |
stopwatch.Start(); | |
} | |
while (stopwatch.ElapsedMilliseconds < MS_PER_SAMPLE) { | |
if (stack.Count != 0) { | |
Debug.LogWarning(string.Format("Unmatched Begin() and End()? stack count={0}", stack.Count)); | |
} | |
frameCount++; | |
#if COROUTINE | |
yield return null; | |
#else | |
return; | |
#endif | |
} | |
stopwatch.Stop(); | |
// time to display the results | |
// the overall frame time and frames per second: | |
int totalMS = (int) stopwatch.ElapsedMilliseconds; | |
double avgMS = (double) totalMS / frameCount; | |
double fps = 1000.0 / avgMS; | |
displayText.Length = 0; | |
displayText.AppendFormat("Avg frame time: {0:0.#}ms, {1:0.#}fps\n", avgMS, fps); | |
// the column titles for the individual recordings: | |
displayText.AppendFormat(headerFormat, "Label", "Total", "MS/frame", "Calls/fra", "MS/call"); | |
int recordedMS; | |
double percent; | |
double msPerFrame; | |
double msPerCall; | |
double timesPerFrame; | |
// now we loop through each individual recording | |
foreach (var recording in recordings.Values) { | |
// calculate the statistics for this recording: | |
recordedMS = recording.totalMilliseconds; | |
msPerFrame = (double) recordedMS / frameCount; | |
percent = (double) recordedMS / totalMS; | |
msPerCall = (double) recordedMS / recording.iterations; | |
timesPerFrame = (double) recording.iterations / frameCount; | |
displayText.AppendFormat(recordFormat, recording.name, percent, msPerFrame, timesPerFrame, msPerCall); | |
// and reset the recording | |
recording.Reset(); | |
} | |
Debug.Log(displayText.ToString()); | |
// reset & schedule the next time to display results: | |
frameCount = 0; | |
stopwatch.Reset(); | |
#if COROUTINE | |
} | |
#endif | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment