web-dev-qa-db-fra.com

Barre de progression intelligente Calcul ETA

Dans de nombreuses applications, nous avons une barre de progression pour le téléchargement de fichier, pour une tâche de compression, pour une recherche, etc. Nous utilisons tous souvent des barres de progression pour informer les utilisateurs de ce qui se passe. Et si nous connaissons certains détails comme le volume de travail effectué et le travail qu'il reste à faire, nous pouvons même donner une estimation du temps, en extrapolant souvent le temps nécessaire pour atteindre le niveau de progression actuel.

Capture d'écran de la compression ETA http://jameslao.com/wp-content/uploads/2008/01/winrar-progress-bar.png

Mais nous avons également vu des programmes dont l'affichage "ETA" dans Time Left est simplement ridicule. Il prétend qu'une copie de fichier sera effectuée dans 20 secondes, puis une seconde plus tard, cela prendra 4 jours, puis il clignote à nouveau pour atteindre 20 minutes. Ce n'est pas seulement inutile, c'est déroutant! La raison pour laquelle l'ETA varie tellement est que le taux de progression lui-même peut varier et que les calculs du programmeur peuvent être excessivement sensibles.

Apple évite cela en évitant simplement toute prédiction précise et en ne donnant que de vagues estimations! La vague évasion d’Apple http://download.autodesk.com/esd/mudbox/help2009/images/MED/DaliSP1/English/Install_licensing/install_progress_MAC.png

C'est ennuyant aussi, est-ce que j'ai le temps de faire une petite pause ou est-ce que ma tâche sera terminée dans 2 secondes? Si la prédiction est trop floue, il est inutile de faire une prédiction du tout.

Méthodes simples mais mauvaises

En calcul ETA de premier passage, nous faisons probablement tous une fonction comme si p est le pourcentage fractionnel déjà fait et t le temps qu’il a pris jusqu’à présent, nous produisons t * (1-p)/p comme estimation de combien de temps ça va prendre pour finir. Ce simple ratio fonctionne "OK" mais c'est aussi terrible surtout à la fin du calcul. Si votre vitesse de téléchargement lente fait en sorte qu'une copie avance lentement pendant la nuit, et finalement le matin, quelque chose se déclenche et que la copie commence à aller à vitesse 100X plus vite, votre ETA à 90% peut indiquer "1 heure" et 10 secondes. plus tard, vous êtes à 95% et l'ETA dira "30 minutes", ce qui est clairement une hypothèse embarrassante. Dans ce cas, "10 secondes" est une estimation beaucoup, beaucoup, bien meilleure.

Lorsque cela se produit, vous pouvez penser à modifier le calcul pour utiliser recent vitesse, et non une vitesse moyenne, pour estimer l'ETA. Vous prenez le taux de téléchargement moyen ou le taux d'achèvement sur les 10 dernières secondes et utilisez ce taux pour projeter la durée de l'achèvement. Cela fonctionne assez bien dans l'exemple précédent de téléchargement au jour le jour qui a accéléré à la fin, car il donnera de très bonnes estimations de l'achèvement final à la fin. Mais cela pose toujours de gros problèmes. Il provoque un rebond violent de votre ETA lorsque votre taux varie rapidement sur une courte période. Vous obtenez le résultat "en 20 secondes, en 2 heures, en 2 secondes, en 30 minutes "affichage rapide de la honte de la programmation.

La question réelle:

Quel est le meilleur moyen de calculer le temps estimé d’achèvement d’une tâche, compte tenu de l’historique temporel du calcul? Je ne cherche pas de liens vers des toolkits GUI ou des bibliothèques Qt. Je demande à propos de algorithm de générer les estimations de temps de réalisation les plus saines et les plus précises.

Avez-vous eu du succès avec les formules mathématiques? Une sorte de moyenne, peut-être en utilisant la moyenne du taux sur 10 secondes avec le taux sur 1 minute avec le taux sur 1 heure? Une sorte de filtrage artificiel du type "si ma nouvelle estimation varie trop de l'estimation précédente, atténuez-la, ne la laissez pas trop rebondir"? Une sorte d’analyse d’histoire sophistiquée dans laquelle vous intégrez les progrès en fonction du temps pour trouver l’écart type du taux afin de fournir des statistiques d’erreur statistique à la fin? 

Qu'avez-vous essayé et qu'est-ce qui fonctionne le mieux?

67
SPWorley

Réponse originale

La société qui a créé ce site fabrique apparemment un système de planification répondant à cette question dans le contexte où les employés écrivent du code. Cela fonctionne avec la simulation de Monte Carlo du futur basée sur le passé.

Annexe: Explication de Monte Carlo

Voici comment cet algorithme fonctionnerait dans votre situation:

Vous modélisez votre tâche comme une séquence de microtasks, disons 1000. Supposons qu'une heure plus tard, vous en ayez terminé 100. Maintenant, vous exécutez la simulation pour les 900 étapes restantes en sélectionnant de manière aléatoire 90 microtasks terminés, en ajoutant leur temps et en les multipliant par 10. Vous disposez ici d'une estimation; Répétez N fois et vous avez N estimations pour le temps restant. Notez que la moyenne entre ces estimations sera d'environ 9 heures - pas de surprises ici. Mais en présentant la distribution résultante à l'utilisateur, vous lui communiquerez honnêtement les chances, par exemple. "avec la probabilité de 90%, cela prendra encore 3-15 heures"

Cet algorithme, par définition, produit un résultat complet si la tâche en question peut être modélisée comme un ensemble de indépendant, aléatoire microtasks. Vous ne pouvez obtenir une meilleure réponse que si vous savez en quoi la tâche diffère de ce modèle: par exemple, les installateurs ont généralement une liste de tâches de téléchargement/décompactage/installation et la vitesse pour l'un ne permet pas de prédire l'autre. 

Annexe: Simplifier Monte Carlo

Je ne suis pas un expert en statistiques, mais je pense que si vous regardez de plus près la simulation avec cette méthode, elle retournera toujours une distribution normale sous la forme d'une somme d'un grand nombre de variables aléatoires indépendantes. Par conséquent, vous n'avez pas besoin de le faire du tout. En fait, vous n'avez même pas besoin de stocker tous les temps complétés, car vous aurez seulement besoin de leur somme et de leur somme de carrés.

En notation peut-être pas très standard,

sigma = sqrt ( sum_of_times_squared-sum_of_times^2 )
scaling = 900/100          // that is (totalSteps - elapsedSteps) / elapsedSteps
lowerBound = sum_of_times*scaling - 3*sigma*sqrt(scaling)
upperBound = sum_of_times*scaling + 3*sigma*sqrt(scaling)

Avec cela, vous pouvez afficher le message disant que la chose se terminera entre [lowerBound, upperBound] à partir de maintenant avec une probabilité fixe (devrait être d'environ 95%, mais j'ai probablement manqué un facteur constant).

31
ilya n.

Voici ce que j'ai trouvé fonctionne bien! Pour les premiers 50% de la tâche, vous supposez que le taux est constant et extrapolez. La prévision temporelle est très stable et ne rebondit pas beaucoup.

Une fois que vous avez passé 50%, vous basculez stratégie de calcul. Vous prenez la fraction du travail qui reste à faire (1-p), puis vous remontez dans le temps dans un historique de vos propres progrès et vous trouvez (par recherche binaire et interpolation linéaire) le temps qu’il vous a fallu pour effectuer le dernier (1). -p) pourcentage et utilisez that comme votre estimation de temps.

Donc, si vous avez maintenant terminé 71%, il vous reste 29%. Vous parcourez votre historique et constatez depuis combien de temps vous étiez à (71-29 = 42%) l’achèvement. Signalez cette heure comme votre ETA. 

Ceci est naturellement adaptatif. Si vous avez une quantité de travail à effectuer, elle ne prend en compte que le temps nécessaire pour effectuer cette quantité. À la fin, lorsque vous avez terminé à 99%, vous utilisez uniquement des données très récentes et très récentes pour l'estimation.

Ce n'est pas parfait, bien sûr, mais cela change en douceur et est particulièrement précis à la fin, quand c'est le plus utile.

13
SPWorley

J'utilise généralement un Moyenne mobile exponentielle pour calculer la vitesse d'une opération avec un facteur de lissage de 0,1, par exemple, et l'utiliser pour calculer le temps restant. De cette manière, toutes les vitesses mesurées ont une influence sur la vitesse actuelle, mais les mesures récentes ont bien plus d'effet que celles du passé lointain.

Dans le code, cela ressemblerait à quelque chose comme ça:

alpha = 0.1 # smoothing factor
...
speed = (speed * (1 - alpha)) + (currentSpeed * alpha)

Si vos tâches ont une taille uniforme, currentSpeed serait simplement le temps nécessaire pour exécuter la dernière tâche. Si les tâches ont des tailles différentes et que vous savez qu'une tâche est supposée être deux fois plus longue, vous pouvez diviser le temps nécessaire à l'exécution de la tâche par sa taille relative pour obtenir la vitesse actuelle. En utilisant speed, vous pouvez calculer le temps restant en le multipliant par la taille totale des tâches restantes (ou simplement par leur nombre si les tâches sont uniformes).

Espérons que mon explication est suffisamment claire, il est un peu tard dans la journée. 

8
gooli

Bien que tous les exemples soient valables, pour le cas spécifique du "temps restant pour le téléchargement", j'ai pensé qu'il serait judicieux d'examiner les projets open source existants pour voir ce qu'ils font.

D'après ce que je peux voir, Mozilla Firefox est le meilleur pour estimer le temps qu'il reste.

Mozilla Firefox

Firefox garde une trace de la dernière estimation du temps restant et, en utilisant celle-ci et l’estimation actuelle du temps restant, il effectue une fonction de lissage du temps. Voir le code ETA ici . Ceci utilise une "vitesse" qui est précédemment calculée ici et est une moyenne lissée des 10 dernières lectures.

C'est un peu complexe, alors pour paraphraser:

  • Prenez une moyenne lissée de la vitesse basée sur 90% de la vitesse précédente et 10% sur la nouvelle vitesse.
  • Avec cette vitesse moyenne lissée, calculez le temps restant estimé.
  • Utilisez ce temps estimé restant et le temps estimé précédent pour créer un nouveau temps estimé (afin d'éviter les sauts)

Google Chrome

Chrome semble faire des sauts dans tous les sens, et le code le montre .

Une chose que j’aime bien avec Chrome, c’est la façon dont ils formulent le temps restant. Pendant au moins une heure, il est indiqué «il reste 1 heure». Pendant moins d’une heure, il reste «59 minutes »[.____. .] Pendant moins d'une minute, il reste "52 secondes"

Vous pouvez voir comment il est formaté ici

DownThemAll! Manager

Il n'utilise rien d'intelligent, ce qui signifie que l'ETA saute partout.

Voir le code ici

pySmartDL (un téléchargeur de python)  

Prend l'ETA moyen des 30 derniers calculs d'ETA. Cela semble être un moyen raisonnable de le faire.

Voir le code ici / blob/916f2592db326241a2bf4d8f2e0719c58b71e385/pySmartDL/pySmartDL.py # L651)

Transmission

Donne un assez bon ETA dans la plupart des cas (sauf au début, comme on pourrait s'y attendre).

Utilise un facteur de lissage au cours des 5 dernières lectures, similaire à Firefox mais pas aussi complexe. Fondamentalement similaire à la réponse de Gooli.

Voir le code ici

7
Patrick

Dans certains cas, lorsque vous devez effectuer la même tâche régulièrement, il peut être judicieux d'utiliser les temps d'achèvement antérieurs pour établir une moyenne.

Par exemple, j'ai une application qui charge la bibliothèque iTunes via son interface COM. La taille d'une bibliothèque iTunes donnée n'augmente généralement pas de manière considérable d'un lancement à l'autre, de sorte que dans cet exemple, il pourrait être possible de suivre les trois derniers temps de chargement et les taux de chargement, puis d'effectuer une moyenne par rapport à cela et calculer votre ETA actuelle.

Ce serait beaucoup plus précis qu'une mesure instantanée et probablement plus cohérent.

Cependant, cette méthode dépend de la taille de la tâche qui est relativement similaire à la précédente. Cela ne fonctionnerait donc pas pour une méthode de décompression ou autre chose où un flux d'octets donné contient les données à traiter.

Juste mon 0,02 $

3
Eric Smith

Tout d'abord, il est utile de générer une moyenne mobile courante. Cela pèse plus lourdement sur les événements récents.

Pour ce faire, conservez un lot d’échantillons (tampon circulaire ou liste), chacun une paire de progrès et de temps. Conservez les N dernières secondes d'échantillons. Générez ensuite une moyenne pondérée des échantillons:

totalProgress += (curSample.progress - prevSample.progress) * scaleFactor
totalTime += (curSample.time - prevSample.time) * scaleFactor

où scaleFactor passe linéairement de 0 à 1 en fonction inverse du temps passé (ce qui pèse plus lourdement sur les échantillons plus récents). Vous pouvez jouer avec cette pondération, bien sûr.

À la fin, vous pouvez obtenir le taux de change moyen:

 averageProgressRate = (totalProgress / totalTime);

Vous pouvez utiliser ceci pour déterminer l'ETA en divisant la progression restante par ce nombre.

Cependant, si cela vous donne un bon nombre de tendances, vous avez un autre problème: la gigue. Si, en raison de variations naturelles, votre taux de progression se déplace un peu (c'est bruyant) - par exemple. vous utilisez peut-être cela pour estimer le nombre de téléchargements de fichiers. Vous remarquerez que le bruit peut facilement faire basculer votre ETA, en particulier s'il est très éloigné dans le futur (plusieurs minutes ou plus).

Pour éviter que la gigue n'affecte votre ETA de manière excessive, vous souhaitez que ce taux de modification moyen réponde lentement aux mises à jour. Une façon de procéder consiste à conserver une valeur cachée de averageProgressRate et, au lieu de l'actualiser instantanément avec le nombre tendance que vous venez de calculer, vous le simulez sous la forme d'un objet physique lourd doté d'une masse, en appliquant une «force» simulée pour ralentir déplacez-le vers le numéro tendance. Avec la masse, il a un peu d'inertie et est moins susceptible d'être affecté par la gigue. 

Voici un échantillon approximatif:

// desiredAverageProgressRate is computed from the weighted average above
// m_averageProgressRate is a member variable also in progress units/sec
// lastTimeElapsed = the time delta in seconds (since last simulation) 
// m_averageSpeed is a member variable in units/sec, used to hold the 
// the velocity of m_averageProgressRate


const float frictionCoeff = 0.75f;
const float mass = 4.0f;
const float maxSpeedCoeff = 0.25f;

// lose 25% of our speed per sec, simulating friction
m_averageSeekSpeed *= pow(frictionCoeff, lastTimeElapsed); 

float delta = desiredAvgProgressRate - m_averageProgressRate;

// update the velocity
float oldSpeed = m_averageSeekSpeed;
float accel = delta / mass;    
m_averageSeekSpeed += accel * lastTimeElapsed;  // v += at

// clamp the top speed to 25% of our current value
float sign = (m_averageSeekSpeed > 0.0f ? 1.0f : -1.0f);
float maxVal = m_averageProgressRate * maxSpeedCoeff;
if (fabs(m_averageSeekSpeed) > maxVal)
{
 m_averageSeekSpeed = sign * maxVal;
}

// make sure they have the same sign
if ((m_averageSeekSpeed > 0.0f) == (delta > 0.0f))
{
 float adjust = (oldSpeed + m_averageSeekSpeed) * 0.5f * lastTimeElapsed;

 // don't overshoot.
 if (fabs(adjust) > fabs(delta))
 {
    adjust = delta;
            // apply damping
    m_averageSeekSpeed *= 0.25f;
 }

 m_averageProgressRate += adjust;
}    
3
Scott S

Votre question est bonne. Si le problème peut être divisé en unités discrètes, il est souvent préférable d’effectuer un calcul précis. Malheureusement, cela peut ne pas être le cas même si vous installez 50 composants, chacun d’eux pouvant représenter 2%, mais l’un d’eux peut être énorme. Une chose avec laquelle j'ai eu un succès modéré est de synchroniser le processeur et le disque et de donner une estimation décente basée sur des données d'observation. Le fait de savoir que certains points de contrôle sont réellement le point x vous permet de corriger les facteurs d’environnement (réseau, activité du disque, charge de la CPU). Cependant, cette solution n’est pas de nature générale, car elle repose sur des données d’observation. L'utilisation de données auxiliaires telles que la taille du fichier rpm m'a permis de rendre mes barres de progression plus précises, mais elles ne sont jamais à l'épreuve des balles. 

2
ojblass

Moyenne uniforme

L’approche la plus simple consisterait à prédire le temps restant de manière linéaire:

t_rem := t_spent ( n - prog ) / prog

t_rem est l'ETA prévu, t_spent est le temps écoulé depuis le début de l'opération, prog, le nombre de microtaches terminées de leur quantité totale n. Pour expliquer —n peut être le nombre de lignes dans un tableau à traiter ou le nombre de fichiers à copier.

Cette méthode n'ayant aucun paramètre, il n'est pas nécessaire de s'inquiéter du réglage de l'exposant d'atténuation. Le compromis est une mauvaise adaptation à un taux de progression en évolution car tous les échantillons ont une contribution égale à l'estimation, alors qu'il est seulement satisfaisant que les échantillons récents aient plus de poids que les anciens, ce qui nous conduit à

Lissage exponentiel du taux

dans laquelle la technique standard consiste à estimer le taux de progression en faisant la moyenne des mesures ponctuelles précédentes:

rate := 1 / (n * dt); { rate equals normalized progress per unit time }
if prog = 1 then      { if first microtask just completed }
    rate_est := rate; { initialize the estimate }
else
begin
    weight   := Exp( - dt / DECAY_T );
    rate_est := rate_est * weight + rate * (1.0 - weight);
    t_rem    := (1.0 - prog / n) / rate_est;
end;

dt indique la durée de la dernière microtache complétée et est égal au temps écoulé depuis la mise à jour précédente de la progression. Notez que weight n'est pas une constante et doit être ajusté en fonction de la durée pendant laquelle une certaine rate a été observée, car plus nous observons une certaine vitesse, plus la décroissance exponentielle des mesures précédentes est élevée. La constante DECAY_T indique la durée pendant laquelle le poids d'un échantillon diminue d'un facteur de e . SPWorley lui-même a suggéré une modification similaire à la proposition de gooli , bien qu'il l'ait appliquée au mauvais terme. Une moyenne exponentielle pour des mesures équidistantes est:

Avg_e(n) = Avg_e(n-1) * alpha + m_n * (1 - alpha)

mais que se passe-t-il si les échantillons ne sont pas équidistants, comme dans le cas d'une barre de progression typique? Tenez compte du fait que alpha ci-dessus n’est qu’un quotient empirique dont la valeur réelle est:

alpha = Exp( - lambda * dt ),

lambda est le paramètre de la fenêtre exponentielle et dt la quantité de changement depuis l'échantillon précédent, qui n'a pas besoin d'être time, mais tout paramètre linéaire et additif. alpha est constant pour les mesures équidistantes mais varie avec dt.

Marquez que cette méthode repose sur une constante de temps prédéfinie et n'est pas évolutive dans le temps. En d'autres termes, si le même processus est uniformément ralenti d'un facteur constant, ce filtre basé sur la cadence deviendra proportionnellement plus sensible aux variations de signal car à chaque étape, weight sera diminué. Cependant, si nous souhaitons un lissage indépendant de l’échelle de temps, nous devrions envisager

Lissage exponentiel de la lenteur

ce qui est essentiellement le lissage du taux renversé avec la simplification supplémentaire d'une constante weight de parce que prog croît par incréments équidistants:

slowness := n * dt;   { slowness is the amount of time per unity progress }
if prog = 1 then      { if first microtask just completed }
    slowness_est := slowness; { initialize the estimate }
else
begin
    weight       := Exp( - 1 / (n * DECAY_P ) );
    slowness_est := slowness_est * weight + slowness * (1.0 - weight);
    t_rem        := (1.0 - prog / n) * slowness_est;
end;

La constante sans dimension DECAY_P indique la différence de progression normalisée entre deux échantillons dont les poids sont dans le rapport de un à e . En d'autres termes, cette constante détermine la largeur de la fenêtre de lissage dans le domaine de progression plutôt que dans le domaine de temps. Cette technique est donc indépendante de l’échelle de temps et a une résolution spatiale constante.

Plus de recherche: lissage exponentiel adaptatif

Vous êtes maintenant équipé pour essayer les différents algorithmes de lissage exponentiel adaptatif . Rappelez-vous seulement de l'appliquer à lenteur plutôt qu'à rate .

1
Ant_222

Je souhaite toujours que ces choses me disent une gamme. Si le message dit: "Cette tâche sera probablement effectuée dans un délai de 8 minutes à 30 minutes", alors j'ai une idée du type de pause à prendre. Si ça rebondit partout, je serais tenté de le regarder jusqu'à ce que ça se calme, ce qui est une grosse perte de temps.

0
Nosredna

J'ai essayé et simplifié votre formule "facile"/"faux"/"OK" et cela fonctionne mieux pour moi:

t / p - t

En Python:

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

Cela économise une opération par rapport à (dur * (1-fait)/fait). Et, dans le cas Edge que vous décrivez, ignorer éventuellement le dialogue pendant 30 minutes supplémentaires importe peu après une nuit d'attente.

En comparant cette méthode simple à celle utilisée par Transmission , je l’ai trouvée plus précise jusqu’à 72% plus précise.

0
Cees Timmerman