I know and love the built-in Unity performance profiling (the one where you set ENABLE_INTERNAL_PROFILER to 0), but I wanted to make sure I wasn’t missing some awesome per-method profiler as well… I’m thinking something that shows you which methods are occupying the most CPU time on average, etc.
I already wrote my own tiny version of such a thing, but I wanted to make sure there wasn’t some existing grandiose option.
Can’t remember reading about plans to upgrade the Mono on the iPhone editions and I also don’t see any direct benefit of such a move as mono 2 has nothing that mono 1 hasn’t in relation to what you can use on the iphone at all (.NET 1.1 only).
I’m not sure if debugging and profiling would / could be possible at all within the constraints of the iPhone and the requirements that have to be met.
Thanks for the comments. Looks like we’re on our own for the time being… that said, here is what I came up with (I will throw it on the wiki, too). It has already helped me nail a lot of bad, bad methods.
Usage:
1.) Add the script “Benchmark” to an object in your scene.
2.) Wrap a Begin() and an End() call around the code you want to profile.
Example:
public void MyMethod() {
Benchmark.Begin("MyClass.MyMethod");
for (int i = 0; i < 1000000; i++)
// Awesome code
Benchmark.End("MyClass.MyMethod");
}
Output:
You should see this hotness output to your console (whether in the editor or in XCode) every [calculationInterval] seconds:
Naturally, adding additional code will slow your methods down, but nevertheless, the benchmark results will be largely unaffected, especially with more complex methods (which is where you should be profiling anyway).
I didn’t notice much of a performance hit using strings as keys, but if you’re anal you can always use enums.
The Code:
using UnityEngine;
using System.Collections;
/*
Author: Mike Laurence
[url]http://mikelaurence.com[/url] [url]http://punkbot.com[/url]
*/
public class Benchmark : MonoBehaviour {
// Keep a reference to the instantiated MonoBehaviour so we can check if it is enabled or not
public static Benchmark main;
private static Hashtable timers = new Hashtable();
private static Hashtable results = new Hashtable();
public float calculationInterval = 2f;
private float nextCalculation = 2f;
public int outputDecimalPlaces = 2;
private float precisionMultiplier;
public void Awake() {
main = this;
precisionMultiplier = Mathf.Pow(10f, outputDecimalPlaces);
}
/*
Begins a benchmark with the specified key
*/
public static void Begin(object key) {
if (main.enabled)
timers[key] = Time.realtimeSinceStartup;
}
/*
Ends a benchmark with the specified key.
The benchmark result (time duration since benchmark Begin() for this same key) is aggregated
into a list for later profiling
*/
public static void End(string key) {
if (main.enabled) {
ArrayList arr = (ArrayList) results[key];
if (arr == null) {
arr = new ArrayList(20);
results[key] = arr;
}
object startTime = timers[key];
if (startTime != null)
arr.Add(Time.realtimeSinceStartup - (float) startTime);
}
}
public void LateUpdate() {
if (Time.realtimeSinceStartup > nextCalculation) {
float outputStartTime = Time.realtimeSinceStartup;
Debug.Log("=== Benchmark Output ===");
foreach (string key in results.Keys) {
ArrayList arr = (ArrayList) results[key];
if (arr.Count > 0) {
float min = Mathf.Infinity, max = 0, total = 0;
foreach (float result in arr) {
total += result;
if (result < min) min = result;
if (result > max) max = result;
}
Debug.Log("=== " + key + "> min: " + FormatFloat(min * 1000) + " max: " + FormatFloat(max * 1000) + " avg: " + FormatFloat(total / arr.Count * 1000));
arr.Clear();
}
}
Debug.Log("=== Output Time: " + FormatFloat((Time.realtimeSinceStartup - outputStartTime) * 1000));
Debug.Log("=== === === === ===");
nextCalculation += calculationInterval;
}
}
private float FormatFloat(float num) {
return Mathf.Round(num * precisionMultiplier) / (precisionMultiplier);
}
}