Calculate Time Remaining

Viewed 63547

What's a good algorithm for determining the remaining time for something to complete? I know how many total lines there are, and how many have completed already, how should I estimate the time remaining?

17 Answers

Why not?

(linesProcessed / TimeTaken) (timetaken / linesProcessed) * LinesLeft = TimeLeft

TimeLeft will then be expressed in whatever unit of time timeTaken is.

Edit:

Thanks for the comment you're right this should be:

(TimeTaken / linesProcessed) * linesLeft = timeLeft

so we have

(10 / 100) * 200 = 20 Seconds now 10 seconds go past
(20 / 100) * 200 = 40 Seconds left now 10 more seconds and we process 100 more lines
(30 / 200) * 100 = 15 Seconds and now we all see why the copy file dialog jumps from 3 hours to 30 minutes :-)

Make sure to manage perceived performance.

Although all the progress bars took exactly the same amount of time in the test, two characteristics made users think the process was faster, even if it wasn't:

  1. progress bars that moved smoothly towards completion
  2. progress bars that sped up towards the end

Generally, you know three things at any point in time while processing:

  1. How many units/chunks/items have been processed up to that point in time (A).
  2. How long it has taken to process those items (B).
  3. The number of remaining items (C).

Given those items, the estimate (unless the time to process an item is constant) of the remaining time will be

B * C / A

It depends greatly on what the "something" is. If you can assume that the amount of time to process each line is similar, you can do a simple calculation:

TimePerLine = Elapsed / LinesProcessed
TotalTime = TimePerLine * TotalLines
TimeRemaining = TotalTime - LinesRemaining * TimePerLine

there is no standard algorithm i know of, my sugestion would be:

  • Create a variable to save the %
  • Calculate the complexity of the task you wish to track(or an estimative of it)
  • Put increments to the % from time to time as you would see fit given the complexity.

You probably seen programs where the load bar runs much faster in one point than in another. Well that's pretty much because this is how they do it. (though they probably just put increments at regular intervals in the main wrapper)

That really depends on what is being done... lines are not enough unless each individual line takes the same amount of time.

The best way (if your lines are not similar) would probably be to look at logical sections of the code find out how long each section takes on average, then use those average timings to estimate progress.

If you know the percentage completed, and you can simply assume that the time scales linearly, something like

timeLeft = timeSoFar * (1/Percentage)

might work.

PowerShell function

function CalculateEta([datetime]$processStarted, [long]$totalElements, [long]$processedElements) {
    $itemsPerSecond = $processedElements / [DateTime]::Now.Subtract($processStarted).TotalSeconds
    $secondsRemaining = ($totalElements - $processedElements) / $itemsPerSecond

    return [TimeSpan]::FromSeconds($secondsRemaining)
}

I prefer System.Threading.Timer rather than System.Diagnostics.Stopwatch.

System.Threading.Timer, which executes a single callback method on a thread pool thread

The following code is an example of a calculating elapsed time with Threading.Timer.

public class ElapsedTimeCalculator : IDisposable
{
    private const int ValueToInstantFire = 0;

    private readonly Timer timer;
    private readonly DateTime initialTime;

    public ElapsedTimeCalculator(Action<TimeSpan> action)
    {
        timer = new Timer(new TimerCallback(_ => action(ElapsedTime)));
        initialTime = DateTime.UtcNow;
    }

    // Use Timeout.Infinite if you don't want to set period time.
    public void Fire() => timer.Change(ValueToInstantFire, Timeout.Infinite);

    public void Dispose() => timer?.Dispose();

    private TimeSpan ElapsedTime => DateTime.UtcNow - initialTime;
}

BTW You can use System.Reactive.Concurrency.IScheduler (scheduler.Now.UtcDateTime) instead of using DateTime directly, if you would like to mock and virtualize the datetime for unit tests.

public class PercentageViewModel : IDisposable
{
    private readonly ElapsedTimeCalculator elapsedTimeCalculator;

    public PercentageViewModel()
    {
       elapsedTimeCalculator = new ElapsedTimeCalculator(CalculateTimeRemaining))
    }

    // Use it where You would like to estimate time remaining.
    public void UpdatePercentage(double percent)
    {
        Percent = percent;
        elapsedTimeCalculator.Fire();
    }

    private void CalculateTimeRemaining(TimeSpan timeElapsed)
    {
        var timeRemainingInSecond = GetTimePerPercentage(timeElapsed.TotalSeconds) * GetRemainingPercentage;

        //Work with calculated time...  
    }

    public double Percent { get; set; }

    public void Dispose() => elapsedTimeCalculator.Dispose();

    private double GetTimePerPercentage(double elapsedTime) => elapsedTime / Percent;

    private double GetRemainingPercentage => 100 - Percent; 
}

In Python:

First create a array with the time taken per entry, then calculate the remaining elements and calculate average time taken

import datetime from datetime
import time

# create average function**
def average(total):
    return float(sum(total)) / max(len(total), 1)

# create array
time_elapsed = []

# capture starting time
start_time = datetime.now()

# do stuff

# capture ending time
end_time = datetime.now()

# get the total seconds from the captured time (important between two days)
time_in_seconds = (end_time - start_time).total_seconds()

# append the time to a array
time_elapsed.append(time_in_seconds)

# calculate the remaining elements, then multiply it with the average time taken
est_time_left = (LastElement - Processed) * average(time_elapsed)

print(f"Estimated time left: {time.strftime('%H:%M:%S', time.gmtime(est_time_left))}")

** timeit() with k=5000 random elements and number=1000

def average2(total):
    avg = 0
    for e in total: avg += e
    return avg / max(len(total),1)

>> timeit average          0.014467999999999925
>> timeit average2         0.08711790000000003
>> timeit numpy.mean:      0.16030989999999967
>> timeit numpy.average:   0.16210096000000003
>> timeit statistics.mean: 2.8182458
Related