web-dev-qa-db-ja.com

残りのダウンロード時間を(正確に)推定するにはどうすればよいですか?

確かに、残りのファイルサイズを現在のダウンロード速度で割ることはできますが、ダウンロード速度が変動する場合(変動する場合)は、非常に良い結果にはなりません。よりスムーズなカウントダウンを作成するためのより良いアルゴリズムは何ですか?

61
mpen

私は数年前に、現在のスループットが事前定義された範囲外になったときにリセットを伴う移動平均を使用するディスクイメージングおよびマルチキャストプログラムの残り時間を予測するアルゴリズムを作成しました。急激な変化が起こらない限り、スムーズな状態を保ち、すぐに調整してから再び移動平均に戻ります。こちらのチャートの例をご覧ください:

enter image description here

そのチャート例の太い青い線は、時間の経過に伴う実際のスループットです。転送の前半ではスループットが低く、後半では劇的に上昇することに注意してください。オレンジ色の線は全体の平均です。完了までにかかる時間を正確に予測するために十分に調整されていないことに注意してください。灰色の線は移動平均です(つまり、最後のN個のデータポイントの平均です-このグラフではNは5ですが、実際には、Nを十分に平滑化するために大きくする必要がある場合があります)。それはより速く回復しますが、それでも調整するにはしばらく時間がかかります。 Nが大きいほど時間がかかります。したがって、データにかなりのノイズが含まれている場合は、Nを大きくする必要があり、復旧時間が長くなります。

緑の線は、私が使用したアルゴリズムです。移動平均のように進みますが、データが事前定義された範囲(薄い青と黄色の細い線で指定)の外に移動すると、移動平均がリセットされ、すぐに跳ね上がります。事前定義された範囲は、標準偏差に基づくこともできるため、データが自動的にうるさい程度に調整できます。私はこれらの値をExcelに投げ込んでこの答えを図式化したので、完璧ではありませんが、アイデアはわかります。

ただし、このアルゴリズムが残り時間の適切な予測に失敗するようにデータが工夫される可能性があります。肝心なのは、データがどのように動作するかを予測し、それに応じてアルゴリズムを選択する必要があるということです。私のアルゴリズムは、私が見ているデータセットに対してうまく機能したので、それを使い続けました。

もう1つの重要なヒントは、通常、開発者はプログレスバーのセットアップとティアダウンの時間と時間の見積もりの​​計算を無視することです。これにより、永遠に99%または100%のプログレスバーが長時間そこにとどまる(キャッシュがフラッシュされているか、他のクリーンアップ作業が行われている間)か、ディレクトリのスキャンまたは他のセットアップ作業が行われると、時間の経過とともに急激に予測されます。ただし、進行状況のパーセンテージは発生しないため、すべてが破棄されます。セットアップ時間とティアダウン時間を含むいくつかのテストを実行し、それらの時間が平均であるか、ジョブのサイズに基づいているかを見積もり、その時間を進行状況バーに追加できます。たとえば、最初の5%の作業はセットアップ作業であり、最後の10%はティアダウン作業であり、中央の85%はダウンロードまたはトラッキングの繰り返しプロセスです。これも非常に役立ちます。

7
rcronk

指数移動平均 はこれに最適です。新しいサンプルを追加するたびに、古いサンプルが全体の平均に対して重要度が低くなるように、平均を平滑化する方法を提供します。それらはまだ考慮されていますが、その重要性は指数関数的に低下します。そのため、名前が付けられています。そして、それは「移動」平均であるため、1つの数値を保持するだけで済みます。

ダウンロード速度を測定するコンテキストでは、式は次のようになります。

averageSpeed = SMOOTHING_FACTOR * lastSpeed + (1-SMOOTHING_FACTOR) * averageSpeed;

SMOOTHING_FACTORは0から1までの数値です。この数値が大きいほど、古いサンプルの廃棄が速くなります。数式でわかるように、SMOOTHING_FACTORは1であり、単に最後の観測値を使用しています。いつ SMOOTHING_FACTORは0ですaverageSpeedは変更されません。したがって、中間に何かが必要であり、通常は適切なスムージングを得るために低い値にします。 0.005は、平均ダウンロード速度のかなり良い平滑化値を提供することがわかりました。

lastSpeedは、最後に測定されたダウンロード速度です。この値を取得するには、毎秒タイマーを実行して、前回の実行以降にダウンロードされたバイト数を計算します。

averageSpeedは、明らかに、残りの推定時間を計算するために使用する数値です。これを最初のlastSpeed測定値に初期化します。

115
Ben Dolman
speed=speedNow*0.5+speedLastHalfMinute*0.3+speedLastMinute*0.2
7
TiansHUo

私ができる最善のことは、残りのファイルサイズを平均ダウンロード速度(これまでにダウンロードしたものをダウンロード時間で割ったもの)で割ることです。これは最初は少し変動しますが、ダウンロードが長くなるほど安定します。

5
Andreas Brinck

ベンドルマンの回答に加えて、アルゴリズム内で変動を計算することもできます。よりスムーズになりますが、平均速度も予測されます。

このようなもの:

prediction = 50;
depencySpeed = 200;
stableFactor = .5;
smoothFactor = median(0, abs(lastSpeed - averageSpeed), depencySpeed);
smoothFactor /= (depencySpeed - prediction * (smoothFactor / depencySpeed));
smoothFactor = smoothFactor * (1 - stableFactor) + stableFactor;
averageSpeed = smoothFactor * lastSpeed + (1 - smoothFactor) * averageSpeed;

変動の有無に関係なく、予測とdepencySpeedに適切な値を設定すると、どちらも安定します。あなたはそれをインターネット速度に応じて少し遊んでいなければなりません。この設定は、平均速度600 kB/sに最適ですが、0〜1MBの間で変動します。

2
Tim

Ben Dolmanの回答は非常に役に立ちましたが、数学の傾向があまりない私のような人にとって、これをコードに完全に実装するのにまだ1時間ほどかかりました。 Pythonで同じことを言う、より簡単な方法を次に示します。不正確な点がある場合はお知らせください。ただし、私のテストでは非常にうまく機能しています。

def exponential_moving_average(data, samples=0, smoothing=0.02):
    '''
    data: an array of all values.
    samples: how many previous data samples are avraged. Set to 0 to average all data points.
    smoothing: a value between 0-1, 1 being a linear average (no falloff).
    '''

    if len(data) == 1:
        return data[0]

    if samples == 0 or samples > len(data):
        samples = len(data)

    average = sum(data[-samples:]) / samples
    last_speed = data[-1]
    return (smoothing * last_speed) + ((1 - smoothing) * average)

input_data = [4.5, 8.21, 8.7, 5.8, 3.8, 2.7, 2.5, 7.1, 9.3, 2.1, 3.1, 9.7, 5.1, 6.1, 9.1, 5.0, 1.6, 6.7, 5.5, 3.2] # this would be a constant stream of download speeds as you go, pre-defined here for illustration

data = []
ema_data = []

for sample in input_data:
    data.append(sample)
    average_value = exponential_moving_average(data)
    ema_data.append(average_value)

# print it out for visualization
for i in range(len(data)):
    print("REAL: ", data[i])
    print("EMA:  ", ema_data[i])
    print("--")
1
Spencer