【问题标题】:Calculate Time Remaining计算剩余时间
【发布时间】:2009-01-23 15:43:03
【问题描述】:

确定完成某件事的剩余时间的好算法是什么?我知道总共有多少行,已经完成了多少行,我应该如何估算剩余时间?

【问题讨论】:

    标签: c# algorithm


    【解决方案1】:

    为什么不呢?

    (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 分钟 :-)

    【讨论】:

    • 也许我在这里遗漏了一些东西。假设我们在 10 秒内处理了 100 行,还剩下 200 行。这给了我们 (100/10)*200 = 2000 秒的剩余时间。现在 10 秒过去了,不再处理任何行。现在,即使没有进行更多处理,剩余时间为 (100/20)*200 = 1000 秒。
    • 表达式应该是 (TimeTaken/linesProcessed)*linesLeft
    • 是的,表达方式是错误的;-) 但在精神上它是正确的!
    • 有时,当您的大脑关闭时,很高兴找到指出明显答案的人(只是在经过漫长的一天黑客攻击后变得不那么明显)。谢谢。
    • 也许您还应该记住第一条评论中的内容。修正它所说的:假设我们在 10 秒内处理了 100 行,剩下 200 行。这给了我们 (10/100)*200 = 20 秒的剩余时间。现在 10 秒过去了,不再处理任何行。现在,即使没有进行更多处理,剩余时间为 (20/100)*200 = 40 秒。从我的角度来看,应该重新计算每次迭代的剩余时间,而不是考虑整个程序历史,而是考虑最后 N 次/记录。韩国
    【解决方案2】:

    我很惊讶没有人用代码回答这个问题!

    @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 建议的那样),它将极大地帮助您。

    【讨论】:

    • 这其实是一个不错的库。我想如果你为它创建一个 NuGet 包,它可能会流行起来。
    • 请创建一个 NuGet 包
    • 抱歉,我的 .NET 经验已经过时了大约 7 年。如果您想为项目做出贡献并帮助创建 NuGet 包,我很乐意为项目添加协作者,甚至转让所有权:)
    【解决方案3】:

    确保管理perceived performance。

    尽管所有进度条在测试中所用的时间完全相同,但有两个特征让用户认为该过程更快,即使事实并非如此:

    1. 顺利完成的进度条
    2. 接近尾声的进度条

    【讨论】:

    • 是的,我读过那篇文章。我也感受到了他所说的话的影响。在 Vista 中复制感觉真的很慢,但显然是因为进度条而不是实际花费的时间。
    • 我同意 不满。它在 Windows 7 中要好得多,就像在 Windows 7 中的其他一切一样。
    【解决方案4】:

    不是为了恢复一个死问题,但我一直回来参考这个页面。
    您可以在 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: 我没有创建子类,而是创建了一个扩展方法。

    【讨论】:

    • 对实现像扩展方法这样的简单解决方案的想法表示赞赏
    【解决方案5】:

    通常,您在处理过程中的任何时间点都知道三件事:

    1. 截至该时间点 (A) 已处理了多少单元/块/项目。
    2. 处理这些项目需要多长时间 (B)。
    3. 剩余项目数 (C)。

    鉴于这些项目,剩余时间的估计(除非处理项目的时间是恒定的)

    B * C / A

    【讨论】:

    • 哪个处理时间永远不能真正保持不变,可以吗?差异可能可以忽略不计,尤其是在小批量时,但您无法在自己的应用程序之外控制系统,而且它不会在不同的硬件上保持不变。
    • 硬件变化的问题无关紧要,因为你是这个等式的当前运行,并且大概硬件在第 10 行和第 11 行之间变化不大。
    【解决方案6】:

    我做了这个,效果很好,随意根据您的变量类型或返回类型更改方法签名,可能您想要获取 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 会更容易。

    如果您使用控制台应用程序,那么逐行显示输出应该不会有问题。

    希望对某人有所帮助。

    【讨论】:

    • 我在 itemsPerSecond = ... 我的 processStart 是 23:53:26 而我的 DateTime.Now 是 23:53:28 得到一个除以零。为什么要除以零?
    • 嗨,Daniel,TimeSpan(添加或减去 DateTime 对象的结果)可能有“零”秒的差异。根据您的值,它应该是“2”秒。请验证“Ticks”的值,以确保“processStarted”和 DateTime.Now 之间的值不同
    【解决方案7】:

    这很大程度上取决于“某物”是什么。如果你可以假设处理每一行的时间是相似的,你可以做一个简单的计算:

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

    【讨论】:

    • 稍微编辑一下:TimeRemaining = (TotalTime - LinesRemaining) * TimePerLine
    【解决方案8】:

    我知道没有标准算法,我的建议是:

    • 创建一个变量来保存 %
    • 计算您希望跟踪的任务的复杂性(或估计)
    • 考虑到复杂性,不时增加 % 的增量。

    您可能会看到加载栏在某一点上的运行速度比另一点快得多的程序。好吧,这几乎是因为他们就是这样做的。 (尽管他们可能只是在主包装器中定期增加增量)

    【讨论】:

      【解决方案9】:

      其中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)
      

      【讨论】:

        【解决方案10】:

        这真的取决于正在做什么......行是不够的,除非每一行花费相同的时间。

        最好的方法(如果你的行不相似)可能是查看代码的逻辑部分,找出每个部分平均需要多长时间,然后使用这些平均时间来估计进度。

        【讨论】:

        • 他们可能不会花费相同的时间,但我真的只想估计剩余时间。我知道它不会 100% 准确。
        【解决方案11】:

        如果你知道完成的百分比,你可以简单地假设时间是线性的,比如

        timeLeft = timeSoFar * (1/百分比)

        可能会起作用。

        【讨论】:

          【解决方案12】:

          我已经知道完成百分比和经过的时间,所以这对我有帮助:

          TimeElapsed * ((100 - %complete) / %complete) = TimeRemaining

          然后我在每次 %complete 改变时更新这个值,给我一个恒定变化的 ETA。

          【讨论】:

          • 应该是TimeElapsed / Percent * 100.0
          • Alireza:不,不应该。这为您提供了整个过程的估计持续时间,而不是剩余时间。
          【解决方案13】:

          有两种显示时间的方式

          1. 已用时间和总体剩余时间: 所以经过的时间会增加,但剩余的时间可能会稳定所需的总时间(如果每秒稳定)

          2. 已用时间和剩余时间:
            所以剩余时间 = 所需总时间 - 已用时间

          我的想法/公式更可能是这样的:

          已处理 - 从正在运行的线程从 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 个

          我希望我的“跳跃”想法能帮助某人构建令人满意的东西

          【讨论】:

            【解决方案14】:

            这个怎么样....

            我用它浏览了一组记录(在一种情况下是 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")
            

            【讨论】:

              【解决方案15】:

              PowerShell 函数

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

              【讨论】:

                【解决方案16】:

                我更喜欢 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; 
                }
                

                【讨论】:

                  【解决方案17】:

                  在 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
                  

                  【讨论】:

                  • 从问题上的标签我认为这是关于 C# 而不是 python
                  • 它也有 12 年的历史,但我确信这个想法适用于各种语言。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-07-30
                  • 1970-01-01
                  • 2018-05-18
                  • 1970-01-01
                  相关资源
                  最近更新 更多