web-dev-qa-db-fra.com

Calculer le temps restant

Qu'est-ce qu'un bon algorithme pour déterminer le temps qu'il reste à remplir? Je sais combien il y a de lignes au total et combien en ont déjà terminé, comment devrais-je estimer le temps restant?

49
Aaron Smith

Pourquoi pas? 

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

TimeLeft sera alors exprimé dans l'unité de temps timeTaken.

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 supplémentaires
(30 / 200) * 100 = 15 secondes et nous voyons tous maintenant pourquoi la boîte de dialogue de copie de fichier passe de 3 heures à 30 minutes :-) 

54
JoshBerke

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

Le moyen simple de calculer l'heure, comme l'a répondu @JoshBerke, peut être codé 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 un 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 varier. Pour calculer "l'ETA" le plus précis, un bon algorithme consisterait à ne prendre en compte que les 10 dernières secondes de progrès. Découvrez ETACalculator.cs pour une implémentation de cet algorithme!

ETACalculator.cs provient de Progression - une bibliothèque open source que j'ai écrite. Il définit une structure très facile à utiliser pour toutes sortes de "calculs de progression". Il est facile d’avoir des étapes imbriquées qui signalent différents types de progrès. Si vous êtes préoccupé par les performances perçues (comme suggéré par @JoshBerke), cela vous aidera énormément.

26
Scott Rippey

Assurez-vous de gérer la performance perçue .

Bien que toutes les barres de progression aient pris exactement la même durée de test, deux caractéristiques ont incité les utilisateurs à penser que le processus était plus rapide, même si ce n'était pas le cas:

  1. barres de progression qui se sont bien déroulées
  2. barres de progression qui ont accéléré vers la fin
16
Anthony Mastrean

Ne pas faire revivre une question morte mais je revenais sans cesse pour faire référence à cette page.
Vous pouvez créer une méthode d’extension sur la classe Stopwatch pour obtenir une fonctionnalité permettant d’obtenir 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 cela sera utile à quelqu'un.


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

9
Chad Carisch

En règle générale, vous savez trois choses à tout moment lors du traitement:

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

Compte tenu de ces éléments, le estimation (sauf si le temps nécessaire pour traiter un élément est constant) du temps restant sera

B * C/A

3
casperOne

C’est ce que j’ai fait et cela fonctionne assez bien, n'hésitez pas à modifier la signature de la méthode en fonction de vos types de variable ou du type de retour, probablement vous souhaitez 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 au début du traitement 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. Par conséquent, lorsque vous placez la valeur de retour dans un contrôle, n'oubliez pas d'utiliser la méthode .Refresh().

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

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

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

3
coloboxp

Cela dépend grandement de ce qu'est le "quelque chose". Si vous pouvez supposer que le temps nécessaire pour traiter chaque ligne est similaire, vous pouvez effectuer un calcul simple:

TimePerLine = Elapsed / LinesProcessed
TotalTime = TimePerLine * TotalLines
TimeRemaining = TotalTime - LinesRemaining * TimePerLine
2
Michael Meadows

il n'y a pas d'algorithme standard que je connaisse, ma suggestion serait:

  • Créer une variable pour enregistrer le%
  • Calculez la complexité de la tâche que vous souhaitez suivre (ou une estimation de celle-ci)
  • Augmentez le% de temps à autre comme vous le jugeriez utile compte tenu de la complexité.

Vous avez probablement déjà vu des programmes dans lesquels la barre de charge s'exécute beaucoup plus rapidement à un moment donné qu'à un autre. Eh bien c'est à peu près parce que c'est comme ça qu'ils le font. (bien qu'ils aient probablement mis des incréments à intervalles réguliers dans l'emballage principal)

1
fmsf

time$("ms") représente l'heure actuelle en millisecondes depuis 00: 00: 00.00, et lof représente le nombre total de 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
Alexander V

Cela dépend vraiment de ce qui est fait ... les lignes ne suffisent pas, sauf si chaque ligne prend le même temps.

La meilleure façon (si vos lignes ne sont pas similaires) serait probablement d’examiner les sections logiques du code pour connaître la durée moyenne de chaque section, puis d’utiliser ces timings moyens pour évaluer les progrès.

0
chills42

Que dis-tu de ça....

J'ai utilisé cela 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 () au début de la routine

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
Brian Battles

Si vous connaissez le pourcentage atteint et que vous pouvez simplement supposer que le temps est linéaire, quelque chose comme 

timeLeft = timeSoFar * (1/Pourcentage)

pourrait fonctionner.

0
GWLlosa

Il y a 2 façons de montrer l'heure

  1. Temps écoulé et temps restant global: Ainsi écoulé augmentera mais restera probablement stable temps total nécessaire (si par seconde est stable)

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

Mon idée/formule ressemble plus à ceci:

Traité - mis à jour depuis le thread en cours d'exécution de 0 à Total 

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

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

processingPerSecond et lastTickProcessed sont des variables globales hors méthode timer

Maintenant, si nous souhaitons savoir combien de secondes sont nécessaires pour terminer le traitement (en supposant une constante idéale) TotalSecondsNeeded = TotalLines/PerSecond

mais nous voulons montrer le cas 2. TimeLeft so 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 "sautera" si PerSecond saute, donc si passé PerSecond était 10 puis 30 puis 10, alors l'utilisateur le verra.

Il y a un moyen de calculer la moyenne, mais cela peut ne pas montrer le temps réel restant si le processus accélère à la fin

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

Donc, il peut être le choix pour un développeur de "choisir" une méthode qui sera la plus précise possible en se basant sur la prédiction de la "rapidité" du traitement.

Nous pourrions également calculer et enregistrer chaque perSeconde, puis prendre la dernière moyenne de 10 secondes, mais dans ce cas, l'utilisateur devra attendre 10 secondes pour voir le premier calcul .__ ou nous pourrions afficher le temps restant à partir de la première par seconde, puis progressivement somme moyenne jusqu'à 10 dernières perSecondes

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

0
Pawel Cioch

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
Vladislav Sorokin

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

TimeElapsed * ((100 -% terminé) /% terminé) = TimeRemaining

J'ai ensuite mis à jour cette valeur chaque fois que% achevé était modifié, ce qui me donnait une ETA constante.

0
Schrodo_Baggins