3
votes

Comment augmenter la précision de la synchronisation du sommeil / pause en python?

J'ai mené une expérience pour comparer la précision du temps de mise en veille / pause en python et C ++

Résumé de l'expérience:

Dans une boucle de 1000000 itérations, dormez 1 microseconde à chaque itération.

Durée prévue:

1,000000 seconde (pour un programme précis à 100%)

En python:

#include <iostream>
#include <chrono>
#include <thread>

using namespace std;

using usec = std::chrono::microseconds;
using datetime = chrono::_V2::steady_clock::time_point;
using clk = chrono::_V2::steady_clock;

int main()
{
    datetime dt;
    usec timedelta = static_cast<usec>(1);

    dt = clk::now();

    const auto start = dt;

    for(int i=0; i < 1000000; ++i) {
        dt += timedelta;
        this_thread::sleep_until(dt);
    }

    const auto end = clk::now();

    chrono::duration<double> elapsed_seconds = end - start;

    cout << elapsed_seconds.count();

    return 0;
}

Attendue : 1,000000 s, Réel (approximatif): 2,603796

En C ++:

import pause
import datetime

start = time.time()
dt = datetime.datetime.now()
for i in range(1000000):
    dt += datetime.timedelta(microseconds=1)
    pause.until(dt)
end = time.time()
print(end - start)

Attendu : 1.000000 sec, Réel (approximatif): 1.000040

Il est évident que C ++ est beaucoup plus précis, mais je développe un projet en python et je dois augmenter la précision. Des idées?

P.S Ce n'est pas grave si vous suggérez une autre bibliothèque / technique python tant qu'elle est plus précise :)


7 commentaires

pause.until (dt) ne devrait-il pas être en dehors de la portée de la boucle?


Non, car je dois effectuer une action (par exemple print ("bonjour") ) à chaque itération puis dormir 1 microseconde, puis imprimer, ... etc.


@ user5173426 Je pense que l'idée est d'additionner tous les délais de réveil.


Que se passe-t-il lorsque vous faites simplement pause.until (1) ?


@Galik Aaaaah! Est-ce vrai?


@AhmedHussein Avez-vous vérifié combien de temps il faut pour effectuer une instruction print () ou les autres actions que vous souhaitez effectuer? Le délai de réveil pourrait-il être petit par rapport à cela?


@ user5173426 Oh, pause.until (1) donne une bien meilleure précision 1.053545. Mais c'est quand même moins précis que C ++


4 Réponses :


0
votes
1.0014092922210693

1 commentaires

Oh, il semble que votre réponse ne soit pas correcte. Selon @bmat comment:> "Au fait, je pense que pause.until (1) ne fait rien car il attend un horodatage Unix - en.wikipedia.org/wiki/Unix_time par exemple nombre de millisecondes depuis 1970 etc ". Si vous supprimez la ligne: dt + = datetime.timedelta (microseconds = 1) cela ne prendra pas 1 seconde. plus. Ai-je raison ?



0
votes

La bibliothèque de pause dit que

La précision doit être de 0,001 seconde près, cependant, cela dépendra de la> précision de la veille de votre système et d'autres facteurs de performance.

Si vous multipliez 0,001 par 1000000, vous obtiendrez une grosse erreur accumulée.

Quelques questions:

Pourquoi avez-vous besoin de dormir?

Quelle est la précision minimale requise?

Dans quelle mesure les opérations que vous appelez sont-elles cohérentes dans le temps? Si ces appels de fonction varient de plus de 0,001, l'erreur accumulée sera davantage due aux opérations que vous effectuez que ne peut être attribuée aux pauses / sommeil.


4 commentaires

* Pourquoi je dois faire cela n'est pas pertinent .. c'est une expérience * Aucune précision min / max spécifiée .. Je dois le rendre aussi précis que possible * Concernant les opérations, je n'ai ajouté aucune opération dans l'expérience. Parce que dans le cas idéal, nous omettons le temps de fonctionnement


Je ne pense pas qu'il soit possible d'obtenir une précision à la microseconde pour mettre un thread en veille en python.


Au fait, je pense que pause.until (1) ne fait rien car cela attend un horodatage unix - en.wikipedia.org/wiki/Unix_time par exemple nombre de millisecondes depuis 1970 etc.


Le commentaire de bmat semble correct. @ user5173426 Veuillez vérifier son commentaire :)



0
votes

Sleeping un thread est intrinsèquement non déterministe - vous ne pouvez pas vraiment parler de «précision» pour le thread sleep en général - peut-être uniquement dans le contexte d'un système et d'une plate-forme particuliers - il y en a tout simplement trop facteurs qui peuvent éventuellement jouer un rôle par exemple combien de cœurs de processeur, etc.

Pour illustrer ce point, une expérience de réflexion :

Supposons que vous ayez créé plusieurs threads (au moins 1 000) et que vous les ayez planifiés pour qu'ils s'exécutent exactement au même moment. À quelle «précision» vous attendez-vous alors?


1 commentaires

Je comprends votre point. Mais dans mon expérience, la même machine a été utilisée à la fois en python et en C ++ avec 1 thread et les mêmes spécifications.



2
votes

Le problème n'est pas seulement que la minuterie de veille de python est inexacte, mais que chaque partie de la boucle nécessite un certain temps.

Votre code d'origine a une durée d'exécution de ~ 1,9528656005859375 sur mon système.

Si je n'exécute que cette partie de votre code sans aucune veille:

dt = datetime.datetime.now()
ts = dt.timestamp()
for i in range(1000000):
    pause.until(ts+i/1000000)

Alors le temps requis pour cette boucle est déjà ~ 0.45999741554260254.

Si je ne lance que

dt = datetime.datetime.now()
ts = dt.timestamp()
for i in range(1000000):
    ts += 0.000001
    pause.until(ts)

Alors le temps d'exécution du code est ~ 0.5583224296569824.

En utilisant toujours la même date:

dt = datetime.datetime.now()
ts = dt.timestamp()
for i in range(1000000):
    pause.until(ts)

Résultats dans un runtime de ~ 1.326077938079834

Si vous faites de même avec l'horodatage:

dt = datetime.datetime.now()
for i in range(1000000):
    pause.until(dt)

Ensuite, le run -time passe à ~ 0.36722803115844727

Et si vous incrémentez l'horodatage d'une microseconde:

for i in range(1000000):
   pause.milliseconds(0)

Ensuite, vous obtenez un runtime de ~ 0.9536933898925781

Le fait qu'il soit plus petit que 1 est dû à des inexactitudes en virgule flottante, l'ajout de print (ts-dt.timestamp ()) après que la boucle affiche ~ 0.95367431640625, donc la durée de la pause elle-même est correcte, mais le ts + = 0.000001 accumule une erreur.

Vous obtiendrez le meilleur résultat si vous comptez les itérations que vous avez eues et ajoutez iterationCount / 1000000 à l'heure de début:

for i in range(100000):
   dt += datetime.timedelta(microseconds=1)

Et cela entraînerait ~ 1.000023365020752

Donc dans mon cas, pause elle-même permettrait déjà une précision avec moins de 1 microseconde. Le problème est en fait dans la partie datetime qui est requise à la fois pour datetime.timedelta et sleep_until.

Donc, si vous voulez avoir une précision de l'ordre de quelques microsecondes, vous devez alors rechercher une bibliothèque de temps qui fonctionne mieux que datetime.


6 commentaires

Je suis d'accord. Proposez-vous une bibliothèque plus précise?


@AhmedHussein le problème n'est pas la précision, les fonctions de date sont "précises" . Le problème est la durée d'exécution de ces fonctions de date. Vous pouvez travailler directement sur l'horodatage, alors vous n'avez que l'inexactitude en virgule flottante.


Qu'entendez-vous par travailler directement sur l'horodatage? Code vous donnez un exemple?


@AhmedHussein les deux derniers blocs de code de ma réponse.


Pouvons-nous augmenter la précision en nanosecondes? Ce sera très utile dans ma candidature


@AhmedHussein C'est une question complètement différente, et là pour vous devriez créer une nouvelle question pour elle. Quoi qu'il en soit, une conception qui nécessite une pause et une précision à la microseconde est déjà discutable. Les nanosecondes dans cette conception n'ont pas beaucoup de sens, il me semble que vous devriez changer quelque chose dans la conception de votre programme.