-
Notifications
You must be signed in to change notification settings - Fork 450
feat: Adding in Performance Tick Data that stores data for external consumption (RFC #7) #491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Adding in Performance Tick Data that stores data for external consumption (RFC #7) #491
Conversation
@@ -853,6 +860,15 @@ private void NetworkUpdate() | |||
currentNetworkTimeOffset += Mathf.Clamp(networkTimeOffset - currentNetworkTimeOffset, -maxDelta, maxDelta); | |||
} | |||
} | |||
|
|||
var profileTransport = NetworkConfig.NetworkTransport as ITransportProfilerData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is totally fine but I always like to share newer c# syntax, this could also be
if(NetworkConfig.NetworkTransport is ITransportProfilerData profileTransport)
(tbh I'm still not 100% sold on the concept of inline variable declarations, it's been growing on me)
{ | ||
public interface ITransportProfilerData | ||
{ | ||
Dictionary<string, int> GetTransportGetData(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confused by this method name. Should it be something like GetTransportProfilerData
?
com.unity.multiplayer.mlapi/Runtime/Profiling/ITransportProfilerData.cs
Outdated
Show resolved
Hide resolved
|
||
namespace MLAPI.Profiling | ||
{ | ||
public static class PerformanceDataManager |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this class be internal? Do we ever expect external (non-MLAPI) code to call these APIs?
If we do, I think we should at least make BeginNewTick
, AddTransportData
, and GetData
to be internal at the member level
|
||
public static void BeginNewTick() | ||
{ | ||
s_ProfilerData = new PerformanceTickData |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how to solve, but this is going to allocate on the heap for every network tick. I could see re-using problematic since we shared this data out and who knows whether consumers hold a reference to it / process async. But I'm curious if you or any other reviewers have any thoughts on ways to avoid an alloc here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we absolutely have to do an allocation a pool might help
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to Matt's point and also turning things into struct
s as much as we can might help a little on GC pressure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also hashing and caching strings might help a little more on string allocation but I'm not sure how applicable these options are here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we went with the generic approach, it needs to exist on the heap (dictionary<,>) since it's an indeterminate size. Although we should definitely make the containers for the heap objects structs when possible to reduce it
@@ -0,0 +1,21 @@ | |||
namespace MLAPI.Profiling | |||
{ | |||
public enum ProfilerConstants |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not convinced about using an enum here instead of string constants - why bother using a Dictionary, if we're restricting the set of values anyways? I think we should go one way or the other, either make it an explicit data set, or make it extensible. Right now we're taking all the cost of a Dictionary (loose abstraction, heap alloc) and none of the benefits (extensibility)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do agree with what Beck says here. However, this can offer a clear distinction between standard and custom data to profile. Not sure if we're aiming for such support right now.
17f3358
to
2bf5b3e
Compare
{ | ||
s_ProfilerData = new PerformanceTickData | ||
{ | ||
tickID = s_TickID++, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know the odds are very low, but there's a risk of overflowing. We should at least use an unsigned. Probably unsigned long? No sense in having a negative tick id.
@@ -0,0 +1,21 @@ | |||
namespace MLAPI.Profiling | |||
{ | |||
public enum ProfilerConstants |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do agree with what Beck says here. However, this can offer a clear distinction between standard and custom data to profile. Not sure if we're aiming for such support right now.
m_Dictionary.Add(fieldName, value); | ||
} | ||
|
||
public void Increment(string fieldName, int count = 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just make the type ProfilerConstants
and avoid the ToString()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IT cant be typed. Each transport will have its own types of strings that it wants to propagate up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oddly enough, we had the same thing in de-string-ifying the channels in MLAPI. They too have per-transport customization, so we still established a set of enums in the SDK with a 'LAST_ENTRY' enum, but then if your transport wants more than that you just do LAST_ENTRY + 1, etc. That let us have our type safety / low overhead but also be extensible. We do this in Transport.cs:8 and then like in TransportTest.cs:42
83add46
to
ed7b4a4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Close to approving but we need to address the enum.ToString() issue because as Matt calls out this will cause a lot of runtime allocations. I'd suggest changing them to string constants instead of using an enum.
|
||
public void AddNonDuplicateData(IReadOnlyDictionary<string, int> transportProfilerData) | ||
{ | ||
IEnumerable<KeyValuePair<string, int>> nonDuplicates = transportProfilerData.Where(entry => !m_TickData.HasData(entry.Key)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this linq query is unnecessary here. you could do the HasData check inside the loop and avoid the linq cost
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://stackoverflow.com/questions/4044400/linq-performance-faq
The allocations and performance AFAIK are the same. nonDuplicates
isn't an allocation and we probably save speed by iterating over less entries.
That is to say, the line in question doesn't get evaluated until the bottom for loop
lazily. And I feel that this reads better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost all Linq queries, including .Where
, allocate an IEnumerable on the heap to wrap the iteration of the collection. this line also allocates an anonymous function - it can't be static since it captures the member field m_TickData
I won't hold approval for this since it's not a high frequency alloc but I do think it could be optimized
public delegate void PerformanceDataEventHandler(PerformanceTickData profilerData); | ||
|
||
public event PerformanceDataEventHandler OnPerformanceDataEvent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#if UNITY_EDITOR || DEVELOPMENT_BUILD
maybe? I wonder how are we planning to strip-out performance profiling stuff for non-Editor, non-Development targets, maybe I'm missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For performance reporting, I don't think we want to. The profiler itself should be stripped out in non-profiler but the data output itself is potentially relevant for end-user tools at runtime (for example, the net stats overlay). That said, users may still want to strip it out for performance reasons, but I think that should be a runtime check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm doing 1-to-1 parity right now. so since these weren't previously in an #if then i'm not doing that in this PR.
Ideally we could ifdef all instances of this class if we want.
Move transport constants to the appropriate location
957151b
to
0529ef4
Compare
@@ -769,6 +769,7 @@ internal static void HandleNetworkedVarDeltas(List<INetworkedVar> networkedVarLi | |||
long readStartPos = stream.Position; | |||
|
|||
networkedVarList[i].ReadDelta(stream, IsServer); | |||
PerformanceDataManager.Increment(ProfilerConstants.NumberNetworkVarsReceived.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ToString() is being called on a string :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mainly there are a bunch of cases where we're calling ToString on the string in the .increment calls
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I went ahead and removed the extra toString calls
Implements RFC #7: Performance Data Propagation (tracked by Track RFC #7: Performance Data Propagation #484)
Adds in Performance Tick Data that stores data for external consumption
Add an interface
ITransportProfilerData
for transports to relay transport specific information