【发布时间】:2009-01-23 15:43:03
【问题描述】:
确定完成某件事的剩余时间的好算法是什么?我知道总共有多少行,已经完成了多少行,我应该如何估算剩余时间?
【问题讨论】:
确定完成某件事的剩余时间的好算法是什么?我知道总共有多少行,已经完成了多少行,我应该如何估算剩余时间?
【问题讨论】:
为什么不呢?
(linesProcessed / TimeTaken)(timetaken / linesProcessed) * LinesLeft = TimeLeft
TimeLeft 将以timeTaken 的任何时间单位表示。
感谢您的评论,这应该是正确的:
(TimeTaken / linesProcessed) * linesLeft = timeLeft
所以我们有
(10 / 100) * 200 = 20 秒现在 10 秒过去了(20 / 100) * 200 = 40 秒现在还剩 10 秒,我们又处理了 100 行(30 / 200) * 100 = 15 秒,现在我们都明白为什么复制文件对话框从 3 小时跳到 30 分钟 :-)
【讨论】:
我很惊讶没有人用代码回答这个问题!
@JoshBerke 回答的计算时间的简单方法可以编码如下:
DateTime startTime = DateTime.Now;
for (int index = 0, count = lines.Count; index < count; index++) {
// Do the processing
...
// Calculate the time remaining:
TimeSpan timeRemaining = TimeSpan.FromTicks(DateTime.Now.Subtract(startTime).Ticks * (count - (index+1)) / (index+1));
// Display the progress to the user
...
}
这个简单的例子非常适合简单的进度计算。
但是,对于更复杂的任务,有很多方法可以改进这种计算!
例如,当您下载大文件时,下载速度很容易波动。要计算最准确的“ETA”,一个好的算法是只考虑过去 10 秒的进度。查看ETACalculator.cs 了解该算法的实现!
ETACalculator.cs 来自 Progression ——我编写的一个开源库。它为各种“进度计算”定义了一个非常易于使用的结构。它使报告不同类型进度的嵌套步骤变得容易。如果您担心感知性能(正如@JoshBerke 建议的那样),它将极大地帮助您。
【讨论】:
尽管所有进度条在测试中所用的时间完全相同,但有两个特征让用户认为该过程更快,即使事实并非如此:
- 顺利完成的进度条
- 接近尾声的进度条
【讨论】:
不是为了恢复一个死问题,但我一直回来参考这个页面。
您可以在 Stopwatch 类上创建一个扩展方法,以获取能够获得估计剩余时间跨度的功能。
static class StopWatchUtils
{
/// <summary>
/// Gets estimated time on compleation.
/// </summary>
/// <param name="sw"></param>
/// <param name="counter"></param>
/// <param name="counterGoal"></param>
/// <returns></returns>
public static TimeSpan GetEta(this Stopwatch sw, int counter, int counterGoal)
{
/* this is based off of:
* (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 :-)
*
* pulled from http://stackoverflow.com/questions/473355/calculate-time-remaining/473369#473369
*/
if (counter == 0) return TimeSpan.Zero;
float elapsedMin = ((float)sw.ElapsedMilliseconds / 1000) / 60;
float minLeft = (elapsedMin / counter) * (counterGoal - counter); //see comment a
TimeSpan ret = TimeSpan.FromMinutes(minLeft);
return ret;
}
}
例子:
int y = 500;
Stopwatch sw = new Stopwatch();
sw.Start();
for(int x = 0 ; x < y ; x++ )
{
//do something
Console.WriteLine("{0} time remaining",sw.GetEta(x,y).ToString());
}
希望它对某人有用。
编辑:
应该注意的是,当每个循环花费相同的时间时,这是最准确的。
编辑2:
我没有创建子类,而是创建了一个扩展方法。
【讨论】:
通常,您在处理过程中的任何时间点都知道三件事:
鉴于这些项目,剩余时间的估计(除非处理项目的时间是恒定的)
B * C / A
【讨论】:
我做了这个,效果很好,随意根据您的变量类型或返回类型更改方法签名,可能您想要获取 TimeSpan 对象或只是秒...
/// <summary>
/// Calculates the eta.
/// </summary>
/// <param name="processStarted">When the process started</param>
/// <param name="totalElements">How many items are being processed</param>
/// <param name="processedElements">How many items are done</param>
/// <returns>A string representing the time left</returns>
private string CalculateEta(DateTime processStarted, int totalElements, int processedElements)
{
int itemsPerSecond = processedElements / (int)(processStarted - DateTime.Now).TotalSeconds;
int secondsRemaining = (totalElements - processedElements) / itemsPerSecond;
return new TimeSpan(0, 0, secondsRemaining).ToString();
}
您需要在处理开始时初始化一个DateTime 变量,并在每次迭代时将其发送到方法。
别忘了,如果进程很长,你的窗口可能会被锁定,所以当你把返回值放入控件时,别忘了使用它的.Refresh()方法。
如果您使用线程,那么您可以尝试使用Invoke(Action) 方法设置文本,使用this extension method to archieve it 会更容易。
如果您使用控制台应用程序,那么逐行显示输出应该不会有问题。
希望对某人有所帮助。
【讨论】:
这很大程度上取决于“某物”是什么。如果你可以假设处理每一行的时间是相似的,你可以做一个简单的计算:
TimePerLine = Elapsed / LinesProcessed
TotalTime = TimePerLine * TotalLines
TimeRemaining = TotalTime - LinesRemaining * TimePerLine
【讨论】:
我知道没有标准算法,我的建议是:
您可能会看到加载栏在某一点上的运行速度比另一点快得多的程序。好吧,这几乎是因为他们就是这样做的。 (尽管他们可能只是在主包装器中定期增加增量)
【讨论】:
其中time$("ms") 表示从 00:00:00.00 开始的当前时间(以毫秒为单位),lof 表示要处理的总行数,x 表示当前行:
if Ln>0 then
Tn=Tn+time$("ms")-Ln 'grand total of all laps
Rn=Tn*(lof-x)/x^2 'estimated time remaining in seconds
end if
Ln=time$("ms") 'start lap time (current time)
【讨论】:
这真的取决于正在做什么......行是不够的,除非每一行花费相同的时间。
最好的方法(如果你的行不相似)可能是查看代码的逻辑部分,找出每个部分平均需要多长时间,然后使用这些平均时间来估计进度。
【讨论】:
如果你知道完成的百分比,你可以简单地假设时间是线性的,比如
timeLeft = timeSoFar * (1/百分比)
可能会起作用。
【讨论】:
我已经知道完成百分比和经过的时间,所以这对我有帮助:
TimeElapsed * ((100 - %complete) / %complete) = TimeRemaining
然后我在每次 %complete 改变时更新这个值,给我一个恒定变化的 ETA。
【讨论】:
TimeElapsed / Percent * 100.0
有两种显示时间的方式
已用时间和总体剩余时间: 所以经过的时间会增加,但剩余的时间可能会稳定所需的总时间(如果每秒稳定)
已用时间和剩余时间:
所以剩余时间 = 所需总时间 - 已用时间
我的想法/公式更可能是这样的:
已处理 - 从正在运行的线程从 0 更新为 Total
我有一个间隔为 1000 毫秒的计时器,它计算每秒处理的次数:
processedPerSecond = Processed - lastTickProcessed;
lastTickProcessed = Processed; //store state from past call
processedPerSecond 和 lastTickProcessed 是定时器方法外的全局变量
现在,如果我们想知道完成处理需要多少秒(在理想的常数假设下) totalSecondsNeeded = TotalLines / PerSecond
但我们想展示案例 2. TimeLeft 所以 TimeLeftSeconds = (TotalLines - Processed) / PerSecond
TimeSpan remaining = new TimeSpan(0, 0, (transactions.Count - Processed) / processedPerSecond);
labelTimeRemaining.Text = remaining.ToString(@"hh\:mm\:ss");
当然,如果 PerSecond 跳跃,TimeLeftSeconds 会“跳跃”,所以如果 PerSecond 过去是 10,然后是 30,然后又回到 10,用户会看到它。
有一种方法可以计算平均值,但如果最后进程加快,这可能不会显示实时剩余时间
int perSecond = (int)Math.Ceiling((processed / (decimal)timeElapsed.TotalSeconds)); //average not in past second
因此,开发人员可能会根据对处理“跳跃”程度的预测来“选择”一种最准确的方法
我们也可以计算并保存每个 PerSecond,然后取最后 10 秒并取平均值,但在这种情况下,用户必须等待 10 秒才能看到第一个计算 或者我们可以显示从每秒第一个开始的剩余时间,然后逐步平均,最后每秒总计 10 个
我希望我的“跳跃”想法能帮助某人构建令人满意的东西
【讨论】:
这个怎么样....
我用它浏览了一组记录(在一种情况下是 Excel 文件中的行)
L 是当前行号 X 是总行数 当例程开始时,dat_Start 设置为 Now()
Debug.Print Format((L / X), "percent") & vbTab & "Time to go:" & vbTab & Format((DateDiff("n", dat_Start, Now) / L) * (X - L), "00") & ":" & Format(((DateDiff("s", dat_Start, Now) / L) * (X - L)) Mod 60, "00")
【讨论】:
PowerShell 函数
function CalculateEta([datetime]$processStarted, [long]$totalElements, [long]$processedElements) {
$itemsPerSecond = $processedElements / [DateTime]::Now.Subtract($processStarted).TotalSeconds
$secondsRemaining = ($totalElements - $processedElements) / $itemsPerSecond
return [TimeSpan]::FromSeconds($secondsRemaining)
}
【讨论】:
我更喜欢 System.Threading.Timer 而不是 System.Diagnostics.Stopwatch。
System.Threading.Timer,它在一个线程上执行单个回调方法 线程池线程
以下代码是使用 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;
}
顺便说一句,如果您想模拟和虚拟化单元测试的日期时间,您可以使用 System.Reactive.Concurrency.IScheduler (scheduler.Now.UtcDateTime) 而不是直接使用 DateTime。
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;
}
【讨论】:
在 Python 中:
首先创建一个包含每个条目所用时间的数组,然后计算剩余元素并计算平均所用时间
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() 与 k=5000 随机元素和 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
【讨论】: