Je travaille sur une application Java pour résoudre une classe de problèmes d'optimisation numérique - des problèmes de programmation linéaire à grande échelle pour être plus précis. Un seul problème peut être divisé en sous-problèmes plus petits qui peuvent être résolus en parallèle. Puisqu'il y a plus de sous-problèmes que de cœurs CPU, j'utilise un ExecutorService et définit chaque sous-problème comme un Callable qui est soumis à l'ExecutorService. La résolution d'un sous-problème nécessite d'appeler une bibliothèque native - un solveur de programmation linéaire dans ce cas.
Problème
Je peux exécuter l'application sur Unix et sur les systèmes Windows avec jusqu'à 44 cœurs physiques et jusqu'à 256 g de mémoire, mais les temps de calcul sur Windows sont un ordre de grandeur plus élevés que sur Linux pour les gros problèmes. Windows nécessite non seulement beaucoup plus de mémoire, mais l'utilisation du processeur au fil du temps passe de 25% au début à 5% après quelques heures. Voici une capture d'écran du gestionnaire de tâches sous Windows:
Observations
- Les temps de solution pour les grandes instances du problème global vont de quelques heures à plusieurs jours et consomment jusqu'à 32 g de mémoire (sous Unix). Les temps de résolution d'un sous-problème sont de l'ordre de ms.
- Je ne rencontre pas ce problème sur de petits problèmes qui ne prennent que quelques minutes à résoudre.
- Linux utilise les deux sockets prêts à l'emploi, tandis que Windows m'oblige à activer explicitement l'entrelacement de la mémoire dans le BIOS pour que l'application utilise les deux cœurs. Que ce soit le cas ou non, cela n'a aucun effet sur la détérioration de l'utilisation globale du processeur au fil du temps.
- Lorsque je regarde les threads dans VisualVM, tous les threads de pool sont en cours d'exécution, aucun n'est en attente ou autre.
- Selon VisualVM, 90% du temps CPU est consacré à un appel de fonction native (résolution d'un petit programme linéaire)
- Le garbage collection n'est pas un problème car l'application ne crée pas et ne dé-référence pas beaucoup d'objets. En outre, la plupart de la mémoire semble être allouée hors du tas. 4g de tas suffisent sous Linux et 8g sous Windows pour la plus grande instance.
Ce que j'ai essayé
- toutes sortes d'arguments JVM, XMS élevé, métaspace élevé, drapeau UseNUMA, autres GC.
- différentes JVM (Hotspot 8, 9, 10, 11).
- différentes bibliothèques natives de différents solveurs de programmation linéaire (CLP, Xpress, Cplex, Gurobi).
Des questions
- Qu'est-ce qui explique la différence de performances entre Linux et Windows d'une grande application Java multi-thread qui fait un usage intensif des appels natifs?
- Y a-t-il quelque chose que je puisse changer dans l'implémentation qui aiderait Windows, par exemple, devrais-je éviter d'utiliser un ExecutorService qui reçoit des milliers de Callables et faire quoi à la place?
ForkJoinPool
au lieu deExecutorService
? 25% d'utilisation du processeur est vraiment faible si votre problème est lié au processeur.ForkJoinPool
c'est plus efficace que la planification manuelle.Réponses:
Pour Windows, le nombre de threads par processus est limité par l'espace d'adressage du processus (voir également Mark Russinovich - Repousser les limites de Windows: processus et threads ). Pensez que cela provoque des effets secondaires quand il approche des limites (ralentissement des changements de contexte, fragmentation ...). Pour Windows, j'essayerais de diviser la charge de travail en un ensemble de processus. Pour un problème similaire que j'avais il y a des années, j'ai implémenté une bibliothèque Java pour le faire plus facilement (Java 8), jetez un œil si vous le souhaitez: Bibliothèque pour générer des tâches dans un processus externe .
la source
On dirait que Windows met en cache de la mémoire dans le fichier d'échange, après avoir été intact pendant un certain temps, et c'est pourquoi le processeur est goulot d'étranglement par la vitesse du disque
Vous pouvez le vérifier avec Process Explorer et vérifier la quantité de mémoire mise en cache
la source
Je pense que cette différence de performances est due à la façon dont le système d'exploitation gère les threads. JVM cache toute différence de système d'exploitation. Il existe de nombreux sites où vous pouvez en lire plus, comme celui-ci , par exemple. Mais cela ne signifie pas que la différence disparaît.
Je suppose que vous utilisez JVM Java 8+. Pour cette raison, je vous suggère d'essayer d'utiliser les fonctionnalités de programmation en flux et fonctionnelles. La programmation fonctionnelle est très utile lorsque vous avez de nombreux petits problèmes indépendants et que vous souhaitez passer facilement d'une exécution séquentielle à une exécution parallèle. La bonne nouvelle est que vous n'avez pas à définir de stratégie pour déterminer le nombre de threads à gérer (comme avec ExecutorService). Juste par exemple (tiré d' ici ):
Donc, je vous suggère de lire sur la programmation des fonctions, le flux, la fonction lambda en Java et d'essayer d'implémenter un petit nombre de tests avec votre code (adapté pour fonctionner dans ce nouveau contexte).
la source
Souhaitez-vous s'il vous plaît publier les statistiques du système? Le gestionnaire de tâches est assez bon pour fournir des indices si c'est le seul outil disponible. Il peut facilement dire si vos tâches attendent des E / S - ce qui ressemble au coupable en fonction de ce que vous avez décrit. Cela peut être dû à un problème de gestion de la mémoire, ou la bibliothèque peut écrire des données temporaires sur le disque, etc.
Lorsque vous parlez de 25% d'utilisation du processeur, voulez-vous dire que seuls quelques cœurs sont occupés à travailler en même temps? (Il se peut que tous les cœurs fonctionnent de temps en temps, mais pas simultanément.) Vérifiez-vous combien de threads (ou processus) sont réellement créés dans le système? Le nombre est-il toujours supérieur au nombre de cœurs?
S'il y a suffisamment de fils, nombre d'entre eux attendent-ils quelque chose? Si vrai, vous pouvez essayer d'interrompre (ou joindre un débogueur) pour voir ce qu'ils attendent.
la source