Calculer Le Temps Restant

Quel est un bon algorithme pour déterminer le temps restant pour quelque chose à compléter? Je sais combien de lignes au total il y a, et combien ont déjà terminé, Comment dois-je estimer le temps restant?

47
demandé sur Aaron Smith 2009-01-23 18:43:03

15 réponses

Pourquoi pas?

(linesProcessed / TimeTaken) (timetaken / linesProcessed) * LinesLeft = TimeLeft

TimeLeft sera alors exprimée dans n'importe quelle unité de temps timeTaken est.

Modifier:

Merci pour le commentaire que vous avez raison cela devrait être:

(TimeTaken / linesProcessed) * linesLeft = timeLeft

Donc nous avons

(10 / 100) * 200 = 20 secondes maintenant 10 secondes passent
(20 / 100) * 200 = 40 secondes restantes maintenant 10 secondes de plus et nous traitons 100 lignes de plus
(30 / 200) * 100 = 15 secondes et maintenant nous voyons tous pourquoi la boîte de dialogue Copier le fichier saute de 3 heures à 30 minutes: -)

53
répondu JoshBerke 2014-12-21 11:37:52

Je suis surpris que personne n'ait répondu à cette question avec du code!

La façon simple de calculer le temps, comme répondu par @ JoshBerke, peut être codée comme suit:

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
    ...
}

Cet exemple simple fonctionne très bien pour le calcul de progression simple.
Cependant, pour une tâche plus compliquée, il existe de nombreuses façons d'améliorer ce calcul!

Par exemple, lorsque vous téléchargez un fichier volumineux, la vitesse de téléchargement peut facilement fluctuer. Pour calculer le "ETA" le plus précis, un bon algorithme serait de ne considérer que les 10 dernières secondes de progrès. Regarde ETACalculator.cs pour une implémentation de cet algorithme!

ETACalculator.cs est de la Progression -- une bibliothèque open source que j'ai écrit. Il définit une structure très facile à utiliser pour toutes sortes de"calcul de progression". Il est facile d'avoir des étapes imbriquées qui rapportent différents types de progrès. Si vous êtes préoccupé par la performance perçue (comme @JoshBerke l'a suggéré), cela vous aidera énormément.

25
répondu Scott Rippey 2013-12-18 22:06:01

Assurez-vous de gérer les performances perçues .

Bien que toutes les barres de progression aient pris exactement le même temps dans le test, deux caractéristiques ont fait penser aux utilisateurs que le processus était plus rapide, même si ce n'était pas le cas:

  1. barres de progression qui se sont déplacées doucement vers l'achèvement
  2. barres de progression accélérées vers la fin
16
répondu Anthony Mastrean 2014-07-11 02:43:21

Pas pour relancer une question morte mais je revenais à référencer cette page.
Vous pouvez créer une méthode d'extension sur la classe Stopwatch pour obtenir une fonctionnalité qui obtiendrait une durée restante estimée.

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;
    }
}

Exemple:

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());
}

J'espère que ça sera utile à quelqu'un.


MODIFIER: Il convient de noter que c'est plus précis lorsque chaque boucle prend le même temps.
Edit 2: Au lieu de sous-classer j'ai créé une méthode d'extension.

8
répondu Chad Carisch 2015-10-01 15:19:34

Généralement, vous savez trois choses à tout moment pendant le traitement:

  1. Combien d'unités / morceaux/éléments ont été traités jusqu'à ce moment (A).
  2. Combien de temps il a fallu pour traiter ces éléments (B).
  3. le nombre d'éléments restants (C).

Compte tenu de ces éléments, l'estimation (sauf si le temps de traitement d'un élément est constant) du temps restant sera

B * C / A

3
répondu casperOne 2009-01-23 15:47:48

J'ai fait cela et cela fonctionne assez bien, n'hésitez pas à changer la signature de la méthode en fonction de vos types de variables ou aussi du type de retour, probablement vous aimeriez obtenir L'objet TimeSpan ou juste les secondes...

    /// <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();
    }

Vous devrez initialiser une variable DateTime lorsque le traitement démarre et l'Envoyer à la méthode à chaque itération.

N'oubliez pas que votre fenêtre sera probablement verrouillée si le processus est assez long, donc lorsque vous placez la valeur de retour dans un contrôle, n'oubliez pas d'utiliser la méthode .Refresh() de celui-ci.

Si vous utilisez des threads, vous pouvez essayer de définir le texte en utilisant la méthode Invoke(Action), serait plus facile à utiliser cette méthode d'extension pour l'archiver facilement.

Si vous utilisez une application console, vous ne devriez pas avoir de problèmes pour afficher la sortie ligne par ligne.

J'espère que ça aide quelqu'un.

3
répondu coloboxp 2017-05-23 10:31:16

Cela dépend beaucoup de ce qu'est le "quelque chose". Si vous pouvez supposer que le temps de traitement de chaque ligne est similaire, vous pouvez faire un calcul simple:

TimePerLine = Elapsed / LinesProcessed
TotalTime = TimePerLine * TotalLines
TimeRemaining = TotalTime - LinesRemaining * TimePerLine
2
répondu Michael Meadows 2009-01-23 15:47:11

Il n'y a pas d'algorithme standard que je connaisse, ma sugestion serait:

  • créez une variable pour enregistrer le %
  • Calculez la complexité de la tâche que vous souhaitez suivre (ou une estimation de celle-ci)
  • Mettez des incréments au % de temps en temps comme vous le souhaitez compte tenu de la complexité.

Vous avez probablement vu des programmes où la barre de charge s'exécute beaucoup plus rapidement en un point que dans un autre. Eh bien, c'est à peu près parce que c'est comme ça qu'ils le font. (si ils ont probablement juste mettez des incréments à intervalles réguliers dans le wrapper principal)

1
répondu fmsf 2009-01-23 15:46:55

time$("ms") représente l'heure actuelle en millisecondes depuis 00: 00: 00.00, et lof représente le total des lignes à traiter, et x représente la ligne actuelle:

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)
1
répondu Alexander V 2011-12-24 15:44:59

Cela dépend vraiment de ce qui est fait... les lignes ne suffisent pas à moins que chaque ligne individuelle prenne le même temps.

La meilleure façon (si vos lignes ne sont pas similaires) serait probablement de regarder les sections logiques du code pour savoir combien de temps chaque section prend en moyenne, puis utiliser ces timings moyens pour estimer la progression.

0
répondu chills42 2009-01-23 15:44:39

Si vous connaissez le pourcentage terminé, et vous pouvez simplement supposer que les échelles de temps linéairement, quelque chose comme

TimeLeft = timeSoFar * (1 / Pourcentage)

Pourrait fonctionner.

0
répondu GWLlosa 2009-01-23 15:46:03

Je connaissais déjà le pourcentage complet et le temps écoulé, donc cela m'a aidé:

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

J'ai ensuite mis à jour cette valeur chaque fois que %complete a changé, me donnant une ETA variable constante.

0
répondu Schrodo_Baggins 2013-06-25 17:27:05

Il y a 2 façons de montrer le temps

  1. Temps écoulé et temps restant global: ainsi, le temps écoulé augmentera mais le temps restant sera probablement stable (si par seconde est stable)

  2. Temps écoulé et temps restant:
    donc temps restant = total nécessaire-écoulé

Mon idée / formule est plus probable comme ceci:

Processed - mise à jour du thread en cours d'exécution de 0 à Total

J'ai une minuterie avec un intervalle de 1000 ms qui calcule traité par seconde:

processedPerSecond = Processed - lastTickProcessed;
lastTickProcessed = Processed;  //store state from past call

ProcessedPerSecond et lastTickProcessed sont des variables globales de la minuterie méthode

Maintenant, si nous voulons obtenir combien de secondes est nécessaire pour terminer le traitement (dans l'hypothèse constante idéale) totalSecondsNeeded = TotalLines / PerSecond

Mais nous voulons montrer le cas 2. TimeLeft donc TimeLeftSeconds = (Totallines-Processed) / PerSecond

TimeSpan remaining = new TimeSpan(0, 0, (transactions.Count - Processed) / processedPerSecond);
labelTimeRemaining.Text = remaining.ToString(@"hh\:mm\:ss");

Bien sûr, TimeLeftSeconds va "sauter" si PerSecond saute, donc si passé PerSecond était 10 puis 30 puis retour à 10, l'utilisateur le verra.

Il existe un moyen de calculer la moyenne, mais cela peut ne pas afficher le temps réel si le processus s'accélère à la fin

int perSecond = (int)Math.Ceiling((processed / (decimal)timeElapsed.TotalSeconds));  //average not in past second

Il peut donc être le choix pour un développeur de "choisir" une méthode qui sera la plus précise en fonction de la prédiction de la façon dont le traitement est" nerveux "

Nous pourrions également calculer et enregistrer chaque PerSecond, puis prendre les 10 dernières secondes et faire la moyenne, mais dans ce cas, l'Utilisateur devra attendre 10 secondes pour voir le premier calcul ou nous pourrions montrer le temps restant à partir de la première par seconde et ensuite progressivement la moyenne en additionnant jusqu'à 10 Dernier Persécuond

J'espère que mes pensées "nerveuses" aideront quelqu'un à construire quelque chose de satisfaisant

0
répondu Pawel Cioch 2013-10-09 16:55:53

Que diriez-vous de ceci....

J'ai utilisé ceci pour parcourir un ensemble d'enregistrements (lignes dans un fichier Excel, dans un cas)

L est le numéro de ligne actuel X est le nombre total de lignes dat_Start est défini sur Now () lorsque la routine commence

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")
0
répondu Brian Battles 2017-03-03 16:37:01

Fonction PowerShell

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

    return [TimeSpan]::FromSeconds($secondsRemaining)
}
0
répondu Vladislav Sorokin 2018-08-12 12:36:00