Hi
I’m trying to profile cumulative cost of Instantiate
and Destroy
in our codebase to get a baseline before retrofitting object pooling everywhere that they are currently (ab)used.
I’ve got this code
public static class ProfileGCMarker
{
public const string MARKER_NAME = "GameInstantiate";
private static readonly ProfilerMarker s_marker = new ProfilerMarker( ProfilerCategory.Memory, MARKER_NAME );
public static void Begin( UnityEngine.Object context ) => s_marker.Begin( context );
public static void End() => s_marker.End();
}
wrapping a ProfileMarker
Everywhere we’re calling Instantiate
I have something like this, so that the context object passed to ProfileMarker.Begin()
is the object being instantiated.
ProfileGCMarker.Begin( m_projectilePrefab );
GameObject instance = Instantiate( m_projectilePrefab, origin.position, origin.rotation );
ProfileGCMarker.End();
I am using UnityEditorInternal.ProfilerDriver.NewProfilerFrameRecorded
to get a callback for each profiler frame which is working fine.
My callback function looks like this:
private void ProfilerDriverFrameIteratorCallback( int connectionId, int frameIndex )
{
using
(
var frameData = UnityEditorInternal.ProfilerDriver.GetRawFrameDataView
(
frameIndex: frameIndex,
threadIndex: UNITY_MAINTHREAD_INDEX
)
)
{
int markerId = frameData.GetMarkerId( ProfileGCMarker.MARKER_NAME );
if( UnityEditor.Profiling.RawFrameDataView.invalidMarkerId == markerId )
{
return;
}
int maxSamples = frameData.sampleCount;
for( int i = 0; i < maxSamples; ++i )
{
if( frameData.GetSampleMarkerId( i ) != markerId )
{
continue;
}
//
// I get here whenever ProfileGCMarker.Begin() is called but:
// the sample has no metadata, no callstack info, and I have no idea how to get at the context object
// which was passed to ProfileGCMarker.Begin()
//
Debug.Log( $"found a {ProfileGCMarker.MARKER_NAME} {frameData.GetMarkerFlags( i )} [{frameData.GetSampleMetadataCount( i )} metadata][{frameData.GetSampleTimeMs( i )} ms ({frameData.GetSampleTimeNs( i )} ns)][callstack size: {m_sampleCallstack.Count}]" );
}
}
}
In the log line I get valid sample times, but no metadata, no special flags.
I realise this is an internal API but I couldn’t find any other way to get at this data in a way which will allow me to aggregate the profiler sample data over a meaningful play session of our game (i.e. 30 seconds to a minute at least).
Any and all help appreciated!
Alex
In the profiler window I can see the context object associated with the ProfilerMarker just fine:
ok. Gonna answer my own question here…
Since it was giving me no useful info, I gave up trying to use UnityEditor.Profiling.RawFrameDataView
.
I’m now using UnityEditor.Profiling.HierarchyFrameDataView
which - if you’re trying not to allocate a buttload of temporary lists - is non-trivial to work with but seems to have all the information I wanted.
I hope this helps someone because all I could find on this stuff was the Unity manual pages:
The callback for UnityEditorInternal.ProfilerDriver.NewProfilerFrameRecorded
now looks as below…
NOTE: I wrote a helper class to handle allocation-free stack based iteration of UnityEditor.Profiling.HierarchyFrameDataView
which is not included in the snippet, and which I will leave as an exercise for the reader.
private void ProfilerDriverFrameIteratorCallback( int connectionId, int frameIndex )
{
using
(
var frameData = UnityEditorInternal.ProfilerDriver.GetHierarchyFrameDataView
(
frameIndex: frameIndex,
threadIndex: UNITY_MAINTHREAD_INDEX, // note: this is 0
viewMode: UnityEditor.Profiling.HierarchyFrameDataView.ViewModes.HideEditorOnlySamples,
sortColumn: UnityEditor.Profiling.HierarchyFrameDataView.columnTotalTime,
sortAscending: false
)
)
{
//
// m_stackIterator is the custom stack-based iterator I wrote...
// it holds data to manage recursive iteration of UnityEditor.Profiling.HierarchyFrameDataView
// essentially a stack of lists of childItemIds and their parent itemId
//
m_stackIterator.Initialise( frameData );
// local function to simplify iterating the hierarchy
// note: m_stackIterator is a stack of IterationStackFrame
int GetChildOfItemWithName( IterationStackFrame frame, string childNameToFind )
{
foreach( int childItemId in frame._childIdList )
{
var childItemName = frameData.GetItemName( childItemId );
if( childItemName == childNameToFind )
{
return childItemId;
}
}
return Constants.k_InvalidIndex;
}
//
// the first few chunks of code find the part of the hierarchy where I know the info I want is
// i.e. under PlayerLoop => Update.ScriptRunBehaviourUpdate => BehaviourUpdate...
//
int markerId = frameData.GetMarkerId( ProfileGCMarker.MARKER_NAME );
if( UnityEditor.Profiling.HierarchyFrameDataView.invalidMarkerId == markerId )
{
return;
}
// --------------------------------
{
int playerLoopId = GetChildOfItemWithName( m_stackIterator.Current(), "PlayerLoop" );
if( Constants.k_InvalidIndex == playerLoopId )
{
return;
}
m_stackIterator.Push( playerLoopId );
}
// --------------------------------
{
int scriptBehaviourUpdateId = GetChildOfItemWithName( m_stackIterator.Current(), "Update.ScriptRunBehaviourUpdate" );
if( Constants.k_InvalidIndex == scriptBehaviourUpdateId )
{
return;
}
m_stackIterator.Push( scriptBehaviourUpdateId );
}
// --------------------------------
{
int behaviourUpdateId = GetChildOfItemWithName( m_stackIterator.Current(), "BehaviourUpdate" );
if( Constants.k_InvalidIndex == behaviourUpdateId )
{
return;
}
m_stackIterator.Push( behaviourUpdateId );
}
//
// now we know we're under PlayerLoop => Update.ScriptRunBehaviourUpdate => BehaviourUpdate
// I use this recursive local function to iterate the remaining hierarchy tree to find the marker(s) I'm looking for
//
void RecursiveIterateFrameDataToFindChildItemWithMarkerId( FrameDataStackIterator stackIterator, int targetMarkerId, Action< FrameDataStackIterator, int > callbackWhenFindItemWithMarkerId )
{
var currentChildren = stackIterator.Current()._childIdList;
foreach( var childIdOfCurrent in currentChildren )
{
if( stackIterator.FrameData.GetItemMarkerID( childIdOfCurrent ) == targetMarkerId )
{
callbackWhenFindItemWithMarkerId(stackIterator, childIdOfCurrent );
}
if( stackIterator.FrameData.HasItemChildren( childIdOfCurrent ) )
{
stackIterator.Push( childIdOfCurrent );
RecursiveIterateFrameDataToFindChildItemWithMarkerId( stackIterator, targetMarkerId, callbackWhenFindItemWithMarkerId );
stackIterator.Pop();
}
}
}
RecursiveIterateFrameDataToFindChildItemWithMarkerId
(
m_stackIterator,
markerId,
( stackIterator, foundItemId ) =>
{
Debug.Log( $"found {ProfileGCMarker.MARKER_NAME} "
+ $"instantiating: {stackIterator.FrameData.GetItemColumnData( foundItemId, UnityEditor.Profiling.HierarchyFrameDataView.columnObjectName )} "
+ $"time: {stackIterator.FrameData.GetItemColumnData( foundItemId, UnityEditor.Profiling.HierarchyFrameDataView.columnTotalTime )} ms "
+ $"allocated: {stackIterator.FrameData.GetItemColumnData( foundItemId, UnityEditor.Profiling.HierarchyFrameDataView.columnGcMemory )}" );
}
);
}