Skip to content

feat: Adding in Performance Tick Data that stores data for external consumption (RFC #7)#491

Merged
mattwalsh-unity merged 11 commits intodevelopfrom
feature/performance_data_propagation
Feb 26, 2021
Merged

feat: Adding in Performance Tick Data that stores data for external consumption (RFC #7)#491
mattwalsh-unity merged 11 commits intodevelopfrom
feature/performance_data_propagation

Conversation

@kvassall-unity
Copy link
Copy Markdown
Contributor

@kvassall-unity kvassall-unity commented Feb 22, 2021

}
}

var profileTransport = NetworkConfig.NetworkTransport as ITransportProfilerData;
Copy link
Copy Markdown
Contributor

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();
Copy link
Copy Markdown
Contributor

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 ?

Comment thread com.unity.multiplayer.mlapi/Runtime/Profiling/ITransportProfilerData.cs Outdated

namespace MLAPI.Profiling
{
public static class PerformanceDataManager
Copy link
Copy Markdown
Contributor

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
Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor

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 structs as much as we can might help a little on GC pressure.

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor

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
Copy link
Copy Markdown
Contributor

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)

Copy link
Copy Markdown

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.

{
s_ProfilerData = new PerformanceTickData
{
tickID = s_TickID++,
Copy link
Copy Markdown

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
Copy link
Copy Markdown

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.

Comment thread com.unity.multiplayer.mlapi/Runtime/Core/NetworkedBehaviour.cs Outdated
m_Dictionary.Add(fieldName, value);
}

public void Increment(string fieldName, int count = 1)
Copy link
Copy Markdown
Contributor

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()

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Contributor

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

@kvassall-unity kvassall-unity changed the title feat: Performance Data Propagation (RFC #7) feat: Adding in Performance Tick Data that stores data for external consumption (RFC #7) Feb 24, 2021
@kvassall-unity kvassall-unity force-pushed the feature/performance_data_propagation branch from 83add46 to ed7b4a4 Compare February 24, 2021 20:21
Copy link
Copy Markdown
Contributor

@becksebenius-unity becksebenius-unity left a 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));
Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Contributor

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

Comment on lines +75 to +77
public delegate void PerformanceDataEventHandler(PerformanceTickData profilerData);

public event PerformanceDataEventHandler OnPerformanceDataEvent;
Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
Contributor Author

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.

@kvassall-unity kvassall-unity force-pushed the feature/performance_data_propagation branch from 957151b to 0529ef4 Compare February 26, 2021 16:36
long readStartPos = stream.Position;

networkedVarList[i].ReadDelta(stream, IsServer);
PerformanceDataManager.Increment(ProfilerConstants.NumberNetworkVarsReceived.ToString());
Copy link
Copy Markdown
Contributor

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 :)

Copy link
Copy Markdown
Contributor

@mattwalsh-unity mattwalsh-unity left a 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

@unity-cla-assistant
Copy link
Copy Markdown

unity-cla-assistant commented Feb 26, 2021

CLA assistant check
All committers have signed the CLA.

Copy link
Copy Markdown
Contributor

@mattwalsh-unity mattwalsh-unity left a 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

@mattwalsh-unity mattwalsh-unity merged commit 18c1a0a into develop Feb 26, 2021
@0xFA11 0xFA11 deleted the feature/performance_data_propagation branch March 5, 2021 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants