web-dev-qa-db-fra.com

Comment lancer un programme Java avec un temps d'exécution contrôlé avec précision?

Comme je connais la JVM avant de lancer une application Java, elle alloue une partie de RAM pour elle, et cette mémoire pourrait être contrôlée par l'utilisateur avant le lancement. Mais il y a un autre problème lorsque je lance une application a une exécution différente à chaque fois.

Voici un exemple très simple avec for loop:

package timespent;

public class Main {

    public static void main(String[] args) {

        long startTime = System.nanoTime();

        int j = 0;

        for (int i = 0; i < 1.E6; i++) {
            j = i + 1;
        }

        long endTime = System.nanoTime();

        long duration = (endTime - startTime);

        System.out.println("duration = " + duration);
    }
}

Il imprime des résultats différents:

duration = 5720542
duration = 7331307
duration = 7737946
duration = 6899173

Disons que je veux qu'il soit exécuté exactement 10 000 000 nanosecondes ou 10 millisecondes.

Ce que je veux?

Je veux que l'application Java soit exécutée avec une exécution exacte).

Pourquoi ai-je besoin de ça?

Lorsque je lance une application, je souhaite afficher le temps d'exécution exact restant dans la fenêtre de démarrage de l'application avant de charger tous les composants.

Je suppose que c'est une sorte de manipulation du processeur et je voulais savoir si c'était possible ou non.

Q1: Est-ce possible en Java?
Q2: Si ce n'est pas possible en Java alors y a-t-il un moyen d'y parvenir en accédant aux méthodes natives du système d'exploitation. Par exemple en priorisant le Java ou autre chose?
Q3: Que diriez-vous d'enregistrer l'état d'une application dans le fichier et de le charger en mémoire?

21
Asad Ganiev

Il existe un certain nombre de sources d'incertitude dans votre mesure du temps. Et toutes ces sources n'influencent pas seulement vos mesures, c'est le temps d'exécution lui-même qui est incertain. Parmi les sources d'incertitude figurent:

  • Utilisation du cache (quelles parties de la mémoire sont mises en cache dans le CPU). Vos données peuvent être expulsées du cache par votre processeur exécutant une tâche en arrière-plan.

  • Emplacement de la mémoire (la mémoire est-elle directement connectée au cœur du processeur en cours d'exécution?). Cela peut changer au fil du temps car votre processus peut être migré vers un autre noyau à tout moment.

  • Le logiciel interrompt (votre système d'exploitation préempte votre processus d'en exécuter un autre). Peut être quelque peu atténué en fonctionnant sur une machine silencieuse, mais rien ne garantit que vous ne serez pas interrompu.

  • Limitation thermique (votre CPU décide qu'il fait trop chaud et diminue sa vitesse d'horloge). Vous ne pouvez vraiment pas faire grand-chose à ce sujet, sauf si vous êtes prêt à travailler sur un processeur intégré avec une vitesse d'horloge fixe.

  • Interruptions matérielles (votre connecteur réseau a reçu des données d'une autre machine sur Internet). Vous n'avez aucune influence sur le moment où cela se produit.

  • Latences imprévisibles (vous lisez des données à partir du disque, mais d'abord, le disque doit attendre que les données arrivent sous la tête de lecture). Cela peut suivre des modèles lorsque vous répétez les mêmes actions encore et encore, mais une fois que vous obtenez une interruption matérielle non liée, cela peut entraîner un retard inattendu de 1/7200 rpm * 60 s/min = 8.3 ms.

  • Garbage collection (vous posez des questions sur Java, vous avez donc un GC en cours d'exécution en arrière-plan). Même les meilleurs et les plus modernes ramasseurs de déchets ne peuvent pas éviter complètement d'arrêter le monde de temps en temps. Et même lorsqu'ils n'arrêtent pas le monde, ils fonctionnent toujours en arrière-plan, introduisant du bruit d'exécution via le cache, le placement de la mémoire et les interruptions logicielles.

Ce sont probablement les sources les plus importantes, et il peut y en avoir d'autres. Le fait est que votre processus est jamais seul sur la machine. Sauf si vous exécutez sans système d'exploitation et désactivez toutes les interruptions matérielles, il vous suffit de vivre avec le fait que vos temps d'exécution varieront d'une exécution à l'autre, et il n'y a tout simplement aucun moyen de résoudre ce problème.

40
cmaster

Ce n'est tout simplement pas possible. Tout d'abord, la mesure du temps en nanosecondes n'est pas exacte. J'ai l'impression que ce post explique bien cela.

Deuxièmement, vous n'avez aucun contrôle sur la façon dont le CPU planifie l'exécution. D'autres tâches peuvent ralentir le temps processeur, ce qui retarde l'exécution de votre programme.

16

Le temps d'exécution précis du code arbitraire n'est pas déterministe, car il dépend d'autres choses que la machine physique fait simultanément.

Même si vous avez prévu de rendre le temps d'exécution "constant" en gardant la trace de l'horodatage de démarrage et de l'horodatage de fin prévu et en dormant le thread principal pendant la durée entre la sortie du programme, il variera toujours, une quantité raisonnablement importante.

Quand et pendant combien de temps les threads s'exécutent ou attendent sont hors du contrôle du programmeur.

9
Bohemian

[TL; DR] C'est très difficile/impossible.

Pour une réponse plus longue, voir Thèse de doctorat sur les tests de planéité par ajout de chemin - Chapitre Méthodologie d'analyse comparative pour certains des problèmes, notamment:

  1. Autres applications ayant des ressources. C'est à dire. ne pas avoir suffisamment de mémoire et le système d'exploitation doit paginer les applications; ou du code s'exécutant plus lentement lorsqu'une autre application partage le CPU.
  2. Résolution de l'horloge - votre code ne fonctionnera aussi vite que votre CPU est capable. Portez-le sur un autre ordinateur et vos benchmarks pourraient vous donner des résultats très différents, alors ne l'optimisez pas uniquement pour votre système.
  3. Chargement de classe - lorsque la JVM exécute le code pour la première fois, elle doit charger les classes en mémoire (généralement à partir du disque) et analyser le code d'octets de sorte que la première fois qu'elle s'exécute sera beaucoup plus lente que les temps suivants.
  4. Compilation juste à temps - lorsque la JVM charge pour la première fois le code octet, elle l'exécute en mode purement interprété. Une fois qu'il a exécuté un bloc de code d'octets (c'est-à-dire une fonction dans votre code) plusieurs fois (disons 10000), il pourrait compiler cette fonction en code natif. La compilation ralentira cette exécution, mais les invocations suivantes seront plus rapides car elle exécutera du code natif plutôt qu'interprété. Cependant, il ne s'agit pas d'une compilation unique et si la JVM constate que certains chemins d'exécution dans le bloc sont favorisés, elle peut recompiler le code d'octet pour essayer d'optimiser ces chemins et cela peut entraîner plusieurs versions natives de l'octet. jusqu'à ce que la JVM stabilise le code par rapport à ses statistiques.
  5. Collecte des ordures - parfois votre code sera interrompu pendant que Java invoque le garbage collector.

Donc, si vous souhaitez comparer votre application pour voir comment elle fonctionnerait de manière optimale, alors:

  • Arrêtez autant d'autres applications que possible;
  • Exécutez le code plusieurs dizaines de milliers de fois;
  • Ignorer les 10 000 à 20 000 premières exécutions (pour atténuer le chargement des classes et la compilation JIT); et
  • Ignorez les itérations lorsque la récupération de place se produit (cela est plus difficile à déterminer qu'il n'y paraît).

Cela vous donnera une idée des performances optimales, mais optimal et le monde réel sont deux choses très différentes.

6
MT0

La seule façon de s'en approcher serait d'utiliser Java en temps réel sur un système d'exploitation spécialement conçu pour prendre en charge l'exécution en temps réel.

5
Matt McHenry

Comme d'autres l'ont dit, il est impossible de connaître le temps exact restant en raison d'autres facteurs influençant la vitesse de votre programme. Vous pouvez cependant placer des jalons et faire référence à des exécutions passées pour obtenir un temps semi-précis calculé à partir de la variance du temps réel jusqu'à présent cette exécution par rapport aux exécutions précédentes, comme cela est fait lors de la copie de grands répertoires de fichiers sur Windows par exemple ou lors du téléchargement de grandes fichiers dans Chrome.

Donc, vous ne spécifiez pas votre problème exact, mais disons que c'est quelque chose comme traiter 100 000 opérations qui nécessitent de contacter un système tiers sur Internet et cela prend normalement environ 15 minutes. Vous pouvez suivre 1) l'heure de début, 2) l'heure de fin prévue et 3) la portion terminée. Donc, dites que lorsque vous avez terminé, vous pouvez prendre le temps écoulé et dire que c'est le temps restant. Fondamentalement, obtenez le taux d'opérations par seconde et divisez les opérations restantes par cela pour obtenir le nombre de secondes restantes.

double speed = completedOperations / elapsedSeconds;
double remainingSeconds = remainingOperations / speed;

Cela peut cependant changer. Supposons que vous démarrez le processus et qu'après avoir atteint 1/4 de chemin en 5 minutes, les sauvegardes hors site commencent, non seulement en détruisant les disques de l'ordinateur, mais aussi votre connexion Internet. Maintenant, les choses se déroulent à 1/10 de la vitesse. Votre temps d'achèvement estimé commencera à 20 minutes, puis à 5 minutes, il sera de 15 minutes. Cependant, il ralentit à ce moment-là, vous n'avez donc que 1/2 fini après 30 minutes, et votre temps restant à ce moment sera de 30 minutes. Maintenant, dites que les sauvegardes sont terminées, vous aurez en fait terminé en 10 minutes mais il est dit qu'il reste 30 minutes.

Il n'y a aucun moyen de contourner un problème comme celui-ci qui est hors de votre contrôle. Vous pouvez faire deux ou trois choses pour l'atténuer. Vous voudrez peut-être prendre la vitesse au cours des 30 dernières secondes de traitement. Ce sera le plus précis si les choses continuent à la vitesse actuelle. Vous pouvez enregistrer la vitesse moyenne à des moments de la journée historiquement si c'est un problème. Vous pouvez faire la moyenne de la vitesse totale de course et de la vitesse de dernière minute si la vitesse fluctue.

Une autre chose qui pourrait le perturber est la variance des données. Par exemple, si vous examinez des clients et les traitez en fonction de la date à laquelle ils sont devenus clients, vos 10 000 premières opérations peuvent concerner des clients fidèles qui sont avec vous depuis des années et ont des tonnes de données à traiter tandis que les 10 000 derniers peuvent être de nouveaux clients. avec peu de données qui traitent plus rapidement. Vous pouvez ensuite utiliser la quantité de données sous-jacente au lieu du nombre de clients ...


Cependant, si vous voulez être exact pour une raison quelconque (la plupart du temps), vous pouvez le simuler au prix du temps. Prenez le plus grand temps d'exécution normal et utilisez simplement le temps écoulé depuis le début pour fournir la progression et le temps restant. Ensuite, lorsque tout le travail est terminé, entrez une commande sleep() pour attendre le temps restant. Il y aura toujours une chance que la charge du système ou quelque chose le rende exceptionnellement long, mais vous pouvez alors changer votre temps maximum à cette nouvelle valeur.

Les temps de vos runs sont probablement sur une sorte de courbe, plus vous prolongez le temps, plus il est probable qu'un seul run se termine dans ce laps de temps, mais alors plus de runs n'attendront rien:

         #            ^
         #            |
        ###           |
        ###           |
       #####          R
      #######         U
    ###########       N
###################   S

Time-------------->

Certes, cela semble idiot, mais votre application s'exécutera à des vitesses différentes en raison de variables que vous ne pouvez pas contrôler, et si un temps d'exécution presque constant est important pour vous, c'est la seule façon.

3
Jason Goemaat

Cela ne marchera pas à votre façon. Cela dépend de l'état du système, comme la quantité de ressources système qui fonctionnent sur votre programme.

Comme je peux le voir, vous souhaitez afficher le temps restant pour ouvrir l'application. Dans ce cas, supposons que deux utilisateurs exécutent votre programme sur des machines différentes, qui ont une structure de base et une fréquence d'horloge différentes ...

Mais je peux donner une suggestion, vous pouvez simplement lire les données de votre programme et ajuster le temps sur cette base, comme le font les autres applications, qui affiche ..% chargé ou identique à la fonction de gestionnaire de téléchargement qui affiche ..% téléchargé.

1
rajiv baghel