- Home /
Accumulating deltaTime oddity
I've come across an oddity with Time.deltaTime, specifically with accumulating time using deltaTime. For instance if I want to keep my own timer object to keep track of how long an object has been alive or in a certain state, I can simply do something like the following:
void Update ()
{
timeAlive += UnityEngine.Time.deltaTime;
}
The strange part comes in if you do this type of calc and let it run for a long time, specifically for me if I run it for longer than 68 minutes, I begin to see some odd changes in how fast timeAlive increases. After this period of time, deltaTime becomes larger than it should be.
To test, I wrote the following component:
using UnityEngine;
using System.Collections;
public class TestUnityTime : MonoBehaviour
{
void Start ()
{
Debug.Log("Clock is high res? "
+ System.Diagnostics.Stopwatch.IsHighResolution);
Debug.Log("StopWatchTime, Time, RealTimeSinceStartup, "
+ "UpdateAccumulation, FixedUpdateAccumulation");
stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();
accumulatedDelta = 0;
accumulatedDeltaFixed = 0;
StartCoroutine(Print());
}
void Update ()
{
accumulatedDelta += UnityEngine.Time.deltaTime;
}
void FixedUpdate()
{
accumulatedDeltaFixed += UnityEngine.Time.fixedDeltaTime;
}
IEnumerator Print()
{
while (true)
{
double swSeconds = stopWatch.Elapsed.TotalSeconds;
Debug.Log(swSeconds
+ ", " + (UnityEngine.Time.time - swSeconds)
+ ", " + (UnityEngine.Time.realtimeSinceStartup - swSeconds)
+ ", " + (accumulatedDelta - swSeconds)
+ ", " + (accumulatedDeltaFixed - swSeconds));
yield return new WaitForSeconds(printInterval);
}
}
public float printInterval = 60; // interval for sampling and printing deltas
private System.Diagnostics.Stopwatch stopWatch;
private float accumulatedDelta;
private float accumulatedDeltaFixed;
}
So essentially this accumulates the time that the app has been running using independent methods: deltaTime, fixedDeltaTime, and a StopWatch. It then periodically logs the difference from the stop watch to each of these other independent accumulations, since we can presume that the stop watch is fairly accurate. Assuming that deltaTime is truly frame independent, while accumulated time might be off from the stop watch slightly, that delta should remain fairly constant over time. Unfortunately, this does not seem to be the case, and I'm not sure why.
Here are the results of running this for just a little over 8 hours:
Figure 1:
And here is the raw output
I also ran a similar test calculating my own time delta based on Time.time, purely to ensure that the issue was not caused by floating point accuracy, or rounding errors within the script. To do this, inside of update I added the following:
customDelta = Time.time - timeLastFrame;
accumulatedCustomDelta += customDelta;
timeLastFrame = Time.time;
This custom delta is shown by the blue line in Figure 2.
Figure 2:
The interesting thing is that somewhere between 4080-4140 seconds, deltaTime becomes much larger than it should be, causing the accumulated time to become increasingly faster than the stop watch. This also changes between 8161-8221 seconds, and 16322-16442 seconds. Sometimes slowing down, sometimes speeding up.
fixedDeltaTime also seems to become smaller than it should around the 8161-8221 second mark. It perhaps may shift and become larger if run for longer than the 8 hours, not sure.
Time.time and realTimeSinceStartup are both solid. They're slightly off from stop watch, which is to be expected simply due to the timing of when these components start, but they stay fairly constant.
So my questions:
Why does the rate of change of this accumulated deltaTime change so drastically over time?
Why doesn't the accumulation of Time.deltaTime come anywhere close to Time.time since Time.time should itself be an accumulation of deltaTime?
I'm using Unity 4.3.1f1, but I've also tested on 3.5.6f4 with similar results.
btw: the changes most likely happens at 4096, 8192, 16384, 32768 which are all power of two numbers. Since floating point numbers, like almost all numbers, are represented in the binary system at every power of two number you need one additional bit to represent the number. However floating point numbers work a little bit different than usual integer numbers, but it's components are still binary numbers and bound to it's limitations.
So to ensure this wasn't an accumulation of error, I also calculated my own delta time based on Time.time. So inside of update I now have the following:
customDelta = Time.time - timeLastFrame;
accumulatedCustomDelta += customDelta;
timeLastFrame = Time.time;
So this ignores Time.deltaTime and calculates it's own based on Time.time delta since the script was last executed.
If this were an accumulation of error or a floating point precision problem with the variables in my script, I would expect it to exhibit the same behavior as accumulating deltaTime. But if you look at the new graph, there is almost no drift from the stop watch over time.
For clarification, I'm not saying that this isn't a floating point precision problem. The thing that I'm trying to deter$$anonymous$$e is am I causing the problem or is it a problem inherent in simply using Time.deltaTime.
I've edited my original post to include this.
Also, I'm still new to the community here. Is this type of question better served at this point on the forums ins$$anonymous$$d of answers?
Answer by Bunny83 · Jan 08, 2014 at 11:12 PM
This is not a bug, but just the well known floating point accuracy problems. There's not much you can do about this.
That was my initial reaction as well, but I would have expected a fairly linear drift over time due to accumulated error of these accuracy problems, not something changing direction such as this. I'm going to do some additional testing with a constant accumulation and some others and post my results.
Also, assu$$anonymous$$g this problem is with the way I'm accumulating these floats and not with how Unity calculates deltaTime, if this was a floating point accuracy problem wouldn't I see a similar pattern in the FixedUpdateAccumulation as I do with UpdateAccumulation?
to mitigate the floating point accuracy issue, you could use an int, or long to store the completed seconds. This frees the float to only keep track of the decimal part.
int seconds = 0;
float accumulatedDelta = 0f;
void Update ()
{
accumulatedDelta += UnityEngine.Time.deltaTime;
while(accumulatedDelta >= 1f){
seconds++;
accumulatedDelta -= 1f;
}
}
$$anonymous$$aybe it will have an effect, maybe not. Who knows.
As to why your graph looks like it does, my money is on float rounding errors as well. At some point your float runs out of enough space to have enough decimals and it rounds the value systematically either too small or too big because the framerate is relatively stable. I think the float already rounds to 0f at 17k stopwatch time because of the linear growth.
It's definitely a floating point problem. The part that was throwing me off was why it was happening with one accumulator and not another, but that wasn't comparing apples to oranges.
To fix the problem, I ended up using the $$anonymous$$ahan summation algorithm:
float y = UnityEngine.Time.deltaTime - accumulatedDeltaCompensation;
float t = accumulatedDelta + y;
accumulatedDeltaCompensation = (t - accumulatedDelta) - y;
accumulatedDelta = t;
I had originally tried something similar to Jamora's solution, except using truncate ins$$anonymous$$d of a while loop to separate it into number of seconds and fractions of a second. The primary problem with this is you always have to reconstruct the total seconds by adding the two floats, which in some cases would happen several times per frame, resulting in far more floating point arithmetic than the solution above.
The problem probably is already in the calculation of deltaTime. Unity has to calculate dt by subtracting the current frame-timeframe-time from the last-frame-time. The greater the time values get the more imprecise the time values get.
Your answer
Follow this Question
Related Questions
Subtract milliseconds from 12 hours? 1 Answer
Total Time Played since Installation 2 Answers
countdown timer acivation 1 Answer
If "equals" doesnt work. 3 Answers