-
Notifications
You must be signed in to change notification settings - Fork 450
perf!: reduce linq, cleanup connectedclients #1196
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
Conversation
… some xmldoc comments though)
…(aside from INetworkSerializable, which isn't ready to be tested yet due to lack of BufferSerializer)
- Streamlined the implementations of bit-packing uints and ulongs.
…lacing with byte* allocated directly through UnsafeUtility.Malloc() (same function used under the hood by NativeArray). This is required in order to be able to store pointers to FastBufferReader and FastBufferWriter in order to be able to wrap them with BufferSerializer - NativeArray contains a managed variable in it that disallows taking a pointer to it. And since FBW and FBR are structs, pointers are the only way to wrap them "by reference" in another struct - ref fields aren't allowed even inside ref structs.
…g values in other ref structs in a capture-by-reference style, updated BitReader and BitWriter to use that. Also aggressively inlining properties in FBW and FBR.
…. it's a little slower, but apparently some platforms won't support unaligned memory access (e.g., WEBGL, ARM processors) and there's no compile time way to detect ARM processors since the bytecode is not processor-dependent... the cost of the runtime detection would be more expensive than the cost of just doing the memcpy.
…afeUtility.MemCpy for small values, while still supporting unaligned access.
… restructuring of some of the other tests.
…ork correctly (requesting beyond MaxCapacity and requesting more than double current capacity) -Added support for FastBufferReader to be used in a mode that doesn't copy the input buffer
- Fixed incorrect text in a warning in FastBufferReader
# Conflicts: # com.unity.netcode.gameobjects/Runtime/com.unity.netcode.runtime.asmdef # com.unity.netcode.gameobjects/Tests/Editor/com.unity.netcode.editortests.asmdef
…ConnectionApprovedMessage. A couple of tests are known to be failing... this is to let the work start being divided up.
Several tests are failing... fixing them will be the next checkin.
@@ -1,8 +1,8 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; |
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 is still here because of profiler stuff (which is really broken and needs a separate pass). It's now at the right position at least 🙃
@@ -112,7 +112,7 @@ public unsafe void SendClientRpc(ref FastBufferWriter writer, uint rpcMethodId, | |||
if (sendParams.Send.TargetClientIds != null) | |||
{ | |||
// Copy into a localArray because SendMessage doesn't take IEnumerable, only IReadOnlyList | |||
ulong* localArray = stackalloc ulong[sendParams.Send.TargetClientIds.Count()]; |
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.
RPC params did allocate by using LINQ .Count on IEnumerable. Using IReadonlyList now. This was problematic in the first point because of multiple enumeration.
{ | ||
get | ||
{ | ||
Assert.IsTrue(IsServer, $"{nameof(ConnectedClients)} should only be accessed on server."); |
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 don't think Assert should be used here - IIRC @JesseOlmer mentioned that Assert gets compiled out in production builds.
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.
Yeah my goal was to get them compiled out for perf. But now thinking about it I don't think we should care in this case. Will change to NotServerException.
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.
To elaborate, the problem with using exceptions inside properties is that it kills inlining for them.
@@ -25,7 +25,7 @@ public struct ClientRpcSendParams | |||
/// Note: Even if you provide a value such as NativeArray, enumerating it will cause boxing. | |||
/// If you want to avoid boxing, use TargetClientIdsNativeArray | |||
/// </summary> | |||
public IEnumerable<ulong> TargetClientIds; | |||
public IReadOnlyList<ulong> TargetClientIds; |
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 this becomes IReadOnlyList, then NetworkBehaviour.SendClientRpc() can be adjusted to not have to make a copy of it and just pass it directly to SendMessage() :)
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.
Yes you are right, simplified the code to use the SendMessage overload with IReadOnlyList.
…factor/remove-linq # Conflicts: # com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
@@ -463,12 +461,21 @@ public override void DisconnectRemoteClient(ulong clientId) | |||
} | |||
|
|||
// we need to cleanup any SendQueues for this connectionID; | |||
var keys = m_SendQueue.Keys.Where(k => k.ClientId == clientId).ToList(); | |||
var keys = new NativeList<SendTarget>(16, Allocator.Temp); // use nativelist and manual foreach to avoid allocations |
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.
@Cosmin-B what do you think should we do this or keep linq 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.
Yes. Also, is worth noting that this call would not happen frequently at all... so the performance benefit is super tiny.
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.
lgtm
Would sure love to see some test cases around this (lists & properties being cleared at appropriate times, NotServerException being thrown when expected & not thrown when not expected, etc.)
/// <summary> | ||
/// Gets the local <see cref="NetworkClient"/> for this client. | ||
/// </summary> | ||
public NetworkClient LocalClient { get; internal set; } |
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 see this getting set in connection approval, but I don't ever see it unset?
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.
Yes this was already an issue before where ConnectedClients was not cleared until the next initialize. I added a line which clears LocalClient in initialize. So this will behave differently as before which is not ideal behavior but I think we decided to touch the initialization/shutdown flow it should be in a separate PR.
# Conflicts: # com.unity.netcode.adapter.utp/Tests/Runtime/Helpers/RuntimeTestsHelpers.cs # com.unity.netcode.gameobjects/Components/Interpolator.meta # com.unity.netcode.gameobjects/Components/Interpolator/BufferedLinearInterpolator.cs.meta # com.unity.netcode.gameobjects/Components/NetworkAnimator.cs # com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs.meta # com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs.meta # com.unity.netcode.gameobjects/Components/NetworkTransform.cs # com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs # com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs # com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs # com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs # com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs # com.unity.netcode.gameobjects/Runtime/Core/SnapshotSystem.cs # com.unity.netcode.gameobjects/Runtime/Messaging/BatchHeader.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs # com.unity.netcode.gameobjects/Runtime/Messaging/IMessageSender.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/INetworkHooks.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/InternalCommandContext.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/InternalMessageHandler.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/MessageBatcher.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/MessageQueue/MessageFrameItem.cs.meta # com.unity.netcode.gameobjects/Runtime/Messaging/Messages.meta # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/DestroyObjectMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ServerLogMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SnapshotDataMessage.cs # com.unity.netcode.gameobjects/Runtime/Messaging/MessagingSystem.cs # com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs # com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs # com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs # com.unity.netcode.gameobjects/Runtime/Serialization/INetworkSerializable.cs # com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs # com.unity.netcode.gameobjects/Tests/Editor/Profiling.meta # com.unity.netcode.gameobjects/Tests/Runtime/Messaging/NamedMessageTests.cs # com.unity.netcode.gameobjects/Tests/Runtime/Messaging/UnnamedMessageTests.cs # com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariableTests.cs # testproject/Assets/Tests/Manual/Scripts/StatsInfoContainer.cs # testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
@@ -463,12 +461,21 @@ public override void DisconnectRemoteClient(ulong clientId) | |||
} | |||
|
|||
// we need to cleanup any SendQueues for this connectionID; | |||
var keys = m_SendQueue.Keys.Where(k => k.ClientId == clientId).ToList(); | |||
var keys = new NativeList<SendTarget>(16, Allocator.Temp); // use nativelist and manual foreach to avoid allocations |
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.
Yes. Also, is worth noting that this call would not happen frequently at all... so the performance benefit is super tiny.
This is really two separate things: refactoring how the NetworkManager,ConnectedClients stuff works reducing linq / gc allocs in the hotpath The reason why they are in the same PR is because it would have been really hard to make one change without the other. Refactoring ConnectedClients Before whenever ConnectedClientIds from NetworkManager was called we would allocate a new list and use a linq .where call to fill it :this_is_fine: (this was really problematic for instance sending a ClientRpc uses this property causing 2 allocs 🔥 ) The new code keeps an additinal id list cached. In addition on the client side ConnectedClients is no longer partially broken by just containing the local client. The new system works like the following: NetworkManager.ConnectedClients / ConnectedClientsList / ConnectedClientsIds can only be accessed by the server. If a non-server tries to access them an exception is thrown. These lists are now exposed as properties and they are IReadOnlyLists because they really should not be modifiable. There is a new NetworkManager.LocalClient property which can be used to access the local client's NetworkClient. This works on clients/host.
MTT-574 addressing linq perf problems.
This is really two separate things:
The reason why they are in the same PR is because it would have been really hard to make one change without the other.
Refactoring ConnectedClients
Before whenever ConnectedClientIds from
NetworkManager
was called we would allocate a new list and use a linq .where call to fill it :this_is_fine: (this was really problematic for instance sending a ClientRpc uses this property causing 2 allocs 🔥 )The new code keeps an additinal id list cached.
In addition on the client side
ConnectedClients
is no longer partially broken by just containing the local client. The new system works like the following:IReadOnlyLists
because they really should not be modifiable.There are still some heavy allocs left around profiling which will be addressed in a separate PR. But this PR addresses all hot path usages of linq.