Baseline Lag on local server

Hi all

I am busy refining my networking and what I’ve found is that the engine seems to have a “baseline” lag. I am running locally with a dedicated server. I have tried this both in-editor and with shipping builds for both client and server.
It seems the engine has a minimum lag of around 8ms.
I have measured the lag both via the playercontroller ExactPing and also manually sending packets back and forth measuring the time. The results are consistent.
I have tested this on an empty map with no actor replication other than the player pawn.

Does anyone know if this is inherent in the engine or if there’s something I’m missing? 8ms is significant considering my lag to a cloud server in my town is only 5ms. I feel that on a local machine this number should be 0.

Thanks a mil

Not sure how the ExactPing is calculated but packets going through the NetDriver may be queued/delayed before actually being sent via UNetConnection::LowLevelSend.

I guess i’m going to have to debug the engine code. Nobody seems to have an answer on this. What concerns me most is that not one person I have discussed this with has ever even been aware of the ping they are getting when simulating locally. I would have thought this was quite important :slight_smile:

I ran into the same thing and managed to narrow it down.

In my test, a dedicated server and client were running on two PCs in the same LAN.

A simple UDP responder gave me:

PONG RTT: 2.8–3.3 ms

But a request/response going through Unreal’s networking layer consistently gave me:

RPC RTT: ~32 ms

And APlayerState::GetPingInMilliseconds() was also around:

28–35 ms

So the ~30 ms isn’t simply the physical network RTT. It appears to be introduced by the Unreal networking path / packet processing / timing.

For testing this, I used a minimal Server RPC + Client RPC round trip:

// PlayerController.h

UFUNCTION(Server, Unreliable)
void ServerPingTest(double ClientSendTime);

UFUNCTION(Client, Unreliable)
void ClientPingTestResult(double ClientSendTime);
// PlayerController.cpp

void AMyPlayerController::ServerPingTest_Implementation(double ClientSendTime)
{
    // Respond immediately.
    ClientPingTestResult(ClientSendTime);
}

void AMyPlayerController::ClientPingTestResult_Implementation(double ClientSendTime)
{
    const double Now = FPlatformTime::Seconds();

    const double RTTSeconds = Now - ClientSendTime;
    const double RTTMs = RTTSeconds * 1000.0;

    UE_LOG(
        LogTemp,
        Warning,
        TEXT("UE RPC RTT: %.3f ms"),
        RTTMs
    );
}

void AMyPlayerController::TestNetworkPing()
{
    const double StartTime = FPlatformTime::Seconds();

    ServerPingTest(StartTime);
}

Then compare it with a raw UDP ping to the same machine.

In my case:

Raw UDP RTT:     ~3 ms
UE RPC RTT:     ~32 ms
GetPing...:     ~32 ms

So if you’re trying to measure the latency that actually matters for gameplay, I would not use a separate UDP socket as the displayed “ping”. That measures the network path to the host, not necessarily the latency through Unreal’s networking stack.

A separate UDP ping is still useful for diagnostics, because it lets you distinguish:

network RTT
    vs.
Unreal networking RTT

But where the goal is to show players an estimate of actual gameplay responsiveness, I’d measure through the same Unreal networking path used by the game.

// In IpNetDriver.cpp
TAutoConsoleVariable<int32> CVarNetIpNetDriverUseReceiveThread(
    TEXT("net.IpNetDriverUseReceiveThread"),
    0,  // ← Disabled by default
    TEXT("If true, the IpNetDriver will call the socket's RecvFrom function on a separate thread (not the game thread)")
);

TAutoConsoleVariable<int32> CVarNetIpNetDriverReceiveThreadPollTimeMS(
    TEXT("net.IpNetDriverReceiveThreadPollTimeMS"),
    250,  // ← 250 ms polling interval
    TEXT("If net.IpNetDriverUseReceiveThread is true, the number of milliseconds to use as the timeout value for FSocket::Wait on the receive thread.")
);

The important part is net.IpNetDriverReceiveThreadPollTimeMS = 250.

With net.IpNetDriverUseReceiveThread enabled, the receive thread calls FSocket::Wait() with this timeout. This means the thread can potentially wait up to 250 ms before checking for incoming packets, depending on the socket/platform behavior.

Also

# Config/DefaultEngine.ini
[/Script/OnlineSubsystemUtils.IpNetDriver]
NetServerMaxTickRate=256
MinNetTicks=0
MaxClientRate=100000

[PacketSimulationSettings]
PktLag=0
PktLagVariance=0