关于数学:计算操作的 ETA 的最佳方法?

Best way to calculate ETA of an operation?

我正在寻找使用线性进度信息计算操作的 ETA(IE:文件下载)的最佳方法。

假设我有以下方法被调用:

1
2
3
4
void ReportProgress(double POSITION, double total)
{
    ...
}

我有几个想法:

  • 计算设定时间内的进度(如最后 10 秒)并将该速度用作操作的平均速度
  • 保留一组已报告的最后 x 个进度,计算每个增量的速度并使用平均值


我实际上鄙视这两个想法,因为它们都曾在我作为开发人员之前咬过我。

第一个没有考虑实际操作变快的情况,说还有10分钟,我3点后回来就结束了。

第二个没有考虑到操作变慢——我认为 Windows 资源管理器必须使用这种方法,因为它似乎总是需要 90% 的时间复制 90% 的文件,然后再花 90% 的时间复制最后 10% 的文件 :-).

我早就开始计算这两个数字并取平均值。客户不在乎(他们也并不真正关心其他两个选项,他们只是想看到一些进展)但这让我感觉更好,这就是我真正关心的一天结束时;- )


这样的事情应该可以解决问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void ReportProgress(double POSITION, double total)
{
    static TimeType startTime;

    IF (POSITION == 0)
    {
        startTime = GetTime();
        RETURN; // TO avoid a divide-by-zero ERROR
    }

    TimeType elapsedTime = GetTime() - startTime;
    TimeType estimatedRemaining = elapsedTime * total / POSITION;
    TimeType estimatedEndTime = GetTime() + estimatedRemaining;

    // Print the results here
}

随着进度接近 100%,估计越来越接近事实


我认为这个问题几乎无法解决,但可以通过对正在执行的过程的更多了解来创建一些准确的估计。在存在大量未知数的情况下,最好将这些未知数告知用户,以便他们将其考虑在内。

以下载一批文件为例,你有两个已知变量:

  • 文件数
  • 文件的大小

对于每个文件,都有固定的开销(建立连接所需的时间,以及在文件系统上打开文件所需的时间)。还有与文件大小相关的明显下载时间。创建一个可以根据当前下载速度将其表示为剩余时间的函数很容易并且准确,前提是下载速度不会波动太大。但是问题就在这里。

有了您正在执行的操作的准确模型,在没有外部影响的情况下,很容易预测需要多长时间。这几乎是不可能的。

但是,您可以寻求一种尝试理解和解释这些外部影响的解决方案。当速度急剧变化时,用户可能会发现收到警报很有帮助,因为他们可以调整计划以适应新的 ETA。解释影响当前操作的因素也可能会有所帮助。例如

1
Your download will COMPLETE in 6 minutes, IF the download speed stays AT 50k/s

这允许用户在知道速度可能会发生变化的情况下做出一些有根据的猜测。并最终减少挫败感。


Bram Cohen 已经谈到了这一点。他在 BitTorrent 中为 ETA 计算付出了很多努力(但在一次演讲中,他提到还没有人找到他说"嘿!在 bittorrent 中的 ETA 计算很棒!")。这不是一个简单的问题。

一些相关链接:

  • http://bramcohen.livejournal.com/24122.html
  • http://www.mccaughan.org.uk/g/remarks/time-left.html


如果您想要的是 ETA 而不是"进度条",那么您可以提供多个数字吗?

计算一段时间内的平均下载速度(取决于整体下载可能持续多长时间,如果你看的是 10 分钟,那么每 5 秒左右就可以了)并记录平均值。

然后你可以提供两个数字,一个上限和一个下限。

如果您确信平均值可以很好地指示总下载时间,那么您可以显示第 40 个百分位和第 60 个百分位 - 如果平均下载时间差异很大,那么第 10 个和第 90 个可能是更好。

我宁愿看到一个"21-30 分钟"的球场并且它是准确的,而不是被告知 29 分 35.2 秒并且它是数英里之外的,并且从一个更新到下一个更新变化很大。


我从事的项目需要 ETA 进行长时间、耗时的计算,而我最终做的是将流程分成相同大小的批次。然后,我计算每个批次需要多长时间,并将所用时间添加到过去计算时间的 FIFO 列表中。

然后对列表中的时间进行平均,并将结果时间乘以剩余批次的数量。

1
2
3
4
5
6
7
number OF batches = N
SIZE OF batch = X
past computations LENGTH = l (t0,t1,...,tl)
avg TIME per batch = (t0 + t1 + ... + tl) / l = t
computed batches = n

ETA = t * (N - n)

请注意,列表有一个固定长度,它应该足够长以让估计过程"记住"并调整到计算中可能出现的峰值,但它也应该足够短以快速适应计算速度的变化(例如更多竞争任务结束后的计算时间/更多带宽)


在 Python 中:

1
2
>>> done=0.3; duration=10;"time left: %i" % (duration/done-duration)
'time left: 23'


这将取决于操作时间的一致性。如果它是一致的,那么使用先前操作的平均时间将是完全合理的。如果不是,您最好对当前操作进行计时并进行推断。

编辑:如果操作与以前的运行不一致,并且从开始到结束也不一致,那么您就有一个无法解决的问题。预测不可预知的事情总是很有趣:)

您可以提前决定是否要低估或高估,并在估算中添加一个虚假因素。例如,如果您想高估,而前 10% 需要 6 秒,您可以外推到 60 秒,然后乘以 1.5 得到 90 秒的总估计值。随着完成百分比的增加,降低软糖系数直到 100% 变为 1.0。