A common misunderstanding about FixedUpdate is that it runs with a regular rhythm in real time / wall time. ie. that Unity is watching the clock like a hawk, and when the real-time moment for the next FixedUpdate comes around, it puts everything on hold and immediately runs FixedUpdate on that exact moment, then waits for the next moment again.
This is not what this method promises.
Unity runs FixedUpdate with a regular rhythm in game time. At the start of each frame, Unity measures how much game time should have elapsed since last frame, and runs FixedUpdate and the physics step back-to-back some number of times to catch up, advancing the game time in fixed increments with each step.
So, from the perspective of game time, FixedUpdate occurs with perfect regularity. That's how it lets us create consistent game simulations.
From the perspective of the player, who sees the end result after all the catch-up has been done for the frame, the result is a pretty good illusion of consistent frequency. They might observe a judder due to "loose change" time (a bit of extra time has elapsed since the last FixedUpdate, but not enough to run the next FixedUpdate), but this is usually fixable with interpolation. They can also observe slowdown/lag, if the hardware is overloaded and not able to run the updates fast enough for game time to keep pace with real time. This affects the play experience, but not the underlying consistency of the simulation, which still maintains its exact regular frequency in game time.
But you're counting your FixedUpdates with respect to wall time. In wall time, FixedUpdates occur in bursts, generally after their "scheduled time", as the engine plays catch-up at the start of each frame to reach where the simulation should be for the next frame. Especially if your frames are running long because you're asking Unity to do hundreds of physics steps per second, the exact time when those steps are computed will have less and less correlation with the matching wall clock time.
If only the simulation log output matters, then you don't care about when those calculations happened in real time. You just need to know when it occurred within the simulation timeline. So you should be measuring Time.time
instead of DateTime.Utc.Now
. Or, if 32-bit floating point precision isn't enough for you, you can make your own timer variable that you increment by your fixed timestep in FixedUpdate
, to measure your simulation time at any precision you like.