27 votes

À quel moment cela vaut-il la peine de réutiliser des tableaux en Java?

Quelle taille fait un tampon doivent être en Java avant qu'il vaut le coup de réutiliser?

Ou, dans l'autre sens: je peux allouer à plusieurs reprises, d'utiliser et de jeter byte[] objets OU d'exécuter une piscine pour les garder et de les réutiliser. Je pourrais allouer beaucoup de petits tampons de se jeter souvent, ou quelques grands qui ne le sont pas. À quelle est la taille est moins cher pour la piscine que pour les réaffecter, et comment faire de petites allocations comparer aux grandes?

EDIT:

Ok, les paramètres spécifiques. Dire un processeur Intel Core 2 Duo CPU, dernière VM version pour OS de choix. Cette question n'est pas aussi vague que ça sonne... un peu de code et un graphique de pouvoir y répondre.

EDIT2:

Vous avez posté beaucoup de bonnes règles générales et des discussions, mais la véritable question demande pour les nombres. Post 'em (et le code aussi)! La théorie est grande, mais la preuve en est le nombre. Il n'a pas d'importance si les résultats varient en fonction de certains à partir d'un système à l'autre, je suis à la recherche d'une estimation approximative (ordre de grandeur). Personne ne semble savoir si la différence de performance sera un facteur de 1,1, 2, 10, ou 100+, et c'est quelque chose qui compte. Il est important pour n'importe quel code Java de travailler avec de grands tableaux -- mise en réseau, la bio-informatique, etc.

Suggestions pour obtenir un bon point de repère:

  1. Réchauffer le code avant de l'exécuter dans l'indice de référence. Les méthodes doivent tous être appelé au moins 1000 10000 fois pour obtenir la pleine JIT optimisation.
  2. Assurez-vous comparé les méthodes de course pour au moins 1 10 secondes et l'utilisation du Système.nanotime si possible, pour obtenir des timings précis.
  3. Référence sur un système qui ne fonctionne minimale applications
  4. Référence de 3 à 5 fois et de faire rapport tous les temps, nous voyons donc quelle est la cohérence de il est.

Je sais que c'est vague et peu exigeants en question. Je vais vérifier cette question régulièrement, et les réponses d'obtenir des commentaires et évalué régulièrement. Paresseux réponses ne seront pas (voir ci-dessous pour les critères). Si je n'ai pas toutes les réponses qui sont rigoureux, je vais joindre une prime. Je pourrais de toute façon, pour récompenser une très bonne réponse avec un petit extra.

Ce que je sais (et n'ont pas besoin répété):

  • Java allocation de mémoire et GC sont rapides et de plus en plus rapides.
  • Le pool d'objet utilisé pour être une bonne optimisation, mais maintenant, ça fait mal la plupart du temps.
  • Objet la mise en commun est "pas une bonne idée, sauf si les objets sont coûteux à créer." Yadda yadda.

Ce que je NE sais PAS:

  • Combien de temps devrais-je attendre les allocations de mémoire pour exécuter (MO/s) sur un standard moderne CPU?
  • Comment fonctionne la répartition de la taille de l'effet taux d'attribution?
  • Quel est le seuil de rentabilité pour nombre/taille des allocations vs ré-utiliser dans une piscine?

Les itinéraires de réponse (le plus sera le mieux):

  • Un récent livre blanc présentant des chiffres pour l'attribution et le GC sur les Processeurs modernes (récente que dans la dernière année ou ainsi, JVM 1.6 ou version ultérieure)
  • Code concis et corriger les micro-benchmark je peux courir
  • Explication de comment et pourquoi les allocations impact sur les performances
  • Des exemples du monde réel/anecdotes de tester ce type d'optimisation

Le Contexte:

Je suis en train de travailler sur une bibliothèque ajout LZF de compression support de Java. Cette bibliothèque s'étend le H2 SGBD LZF classes, en ajoutant d'autres niveaux de compression (plus de compression) et la compatibilité avec les flux d'octets à partir de la C LZF de la bibliothèque. Une des choses que je suis en train de réfléchir est de savoir si ou non il vaut la peine d'essayer de réutiliser la taille fixe les tampons utilisés pour compresser/décompresser des flux. Les tampons peuvent être ~8 ko, ou ~32 ko, et dans la version originale, ils sont ~128 ko. Les tampons peuvent être attribués à une ou plusieurs reprises par streaming. Je suis à essayer de comprendre comment je veux poignée de tampons pour obtenir les meilleures performances, avec un oeil vers potentiellement multithreading dans l'avenir.

Oui, la bibliothèque SERA publié en open source si quelqu'un est intéressé à l'utilisation de ce.

25voto

Stephen C Points 255558

Si vous voulez une réponse simple, c'est qu'il n'y a pas de réponse simple. Aucun montant de l'appel de réponses (et, par implication de personnes) "paresseux" va aider.

Combien de temps devrais-je attendre les allocations de mémoire pour exécuter (MO/s) sur un standard moderne CPU?

À la vitesse à laquelle la JVM peuvent zéro de la mémoire, en supposant que la répartition ne déclenche pas de collecte des ordures. Si elle ne déclencher la collecte des ordures, il est impossible de prédire sans savoir ce que le GC algorithme est utilisé, la taille du segment de mémoire et d'autres paramètres, et une analyse de la demande de travail de l'ensemble de non-ordures des objets sur la durée de vie de l'application.

Comment fonctionne la répartition de la taille de l'effet taux d'attribution?

Voir ci-dessus.

Quel est le seuil de rentabilité pour nombre/taille des allocations vs ré-utiliser dans une piscine?

Si vous voulez une réponse simple, c'est qu'il n'y a pas de réponse simple.

La règle d'or est le plus grand de votre tas est (jusqu'à la quantité de mémoire physique disponible), plus le coût amorti de GC avec une poubelle de l'objet. Avec une vitesse de copie de garbage collector, le coût amorti de la libération d'un des ordures objet se rapproche de zéro tant que le tas devient plus grand. Le coût de la GC est en fait déterminé par (en termes simplistes) le nombre et la taille de la non-ordures objets que le GC a à traiter.

Sous l'hypothèse que votre tas est important, le coût de cycle de vie de l'allocation et de GC avec un objet de grande taille (dans un cycle de GC) s'approche du coût de la mise à zéro de la mémoire lorsque l'objet est alloué.

EDIT: Si vous désirez vous en quelques chiffres simples, écrire une application simple qui alloue et les rejets tampons de grande taille et l'exécuter sur votre machine avec divers GC et tas de paramètres et de voir ce qui se passe. Mais attention que cela ne va pas vous donner une réponse réaliste, parce que la vraie GC coûts dépendent d'une application non-ordures objets.

Je ne vais pas écrire un test pour vous, parce que je sais que ca va vous donner de fausses réponses.

EDIT 2: En réponse à l'OP de commentaires.

Donc, je devrais attendre les allocations à courir aussi vite que Système.arraycopy, ou d'un JITed tableau de l'initialisation de la boucle (1 go/s sur mon dernier banc, mais je suis dubitatif du résultat)?

Théoriquement oui. Dans la pratique, il est difficile de mesurer d'une manière qui sépare la répartition des coûts de la GC, les coûts.

Par la taille de segment de mémoire, vous dites que l'allocation d'une quantité de mémoire plus importante pour la JVM utilisation réduira la performance?

Non, je dis que c'est susceptible d' augmenter les performances. De façon significative. (À condition que vous ne courez pas dans les OS virtuel au niveau des effets de mémoire.)

Les Allocations sont juste pour les tableaux, et presque tout le reste dans mon code fonctionne sur la pile. Il faut la simplifier l'évaluation et la prévision de la performance.

Peut-être. Franchement, je pense que vous n'allez pas obtenir beaucoup d'amélioration par le recyclage des tampons.

Mais si vous avez l'intention d'aller dans cette voie, créer un pool de mémoire tampon d'interface avec deux implémentations. Le premier est un vrai thread-safe pool de mémoire tampon qui recycle les tampons. La deuxième est le mannequin de la piscine qui donne simplement un nouveau tampon à chaque fois alloc est appelé, et des friandises dispose comme un no-op. Enfin, permettre aux développeurs de choisir entre la piscine implémentations via un setBufferPool méthode et/ou les paramètres du constructeur et/ou de l'exécution de propriétés de configuration. La demande doit également être capable de fournir un pool de mémoire tampon de la classe / instance de sa propre fabrication.

14voto

akuhn Points 12241

Lorsqu'il est plus grand qu'un espace jeune.

Si votre tableau est plus grand que l'espace jeune du thread local, il est directement alloué dans l'ancien espace. La collecte des ordures sur l'ancien espace est beaucoup plus lente que sur le jeune espace. Donc, si votre tableau est plus grand que le jeune espace, il pourrait être judicieux de le réutiliser.

Sur ma machine, 32 Ko dépasse l'espace jeune. Il serait donc logique de le réutiliser.

3voto

duffymo Points 188155

Vous avez négligé de mentionner quoi que ce soit sur la sécurité des threads. S'il va être réutilisé par plusieurs threads, vous devrez vous soucier de la synchronisation.

3voto

kdgregory Points 21849

Une réponse à partir d'une direction complètement différente: permettre à l'utilisateur de votre bibliothèque de décider.

En fin de compte, cependant optimisé vous rendre à votre bibliothèque, ce n'est qu'une composante d'une application plus large. Et si cette application plus large rend l'utilisation occasionnelle de votre bibliothèque, il n'y a pas de raison qu'il doit payer pour maintenir un pool de tampons, même si la piscine est à seulement quelques centaines de kilo-octets.

Afin de créer votre mécanisme de mise en commun comme une interface, et sur la base de certains paramètres de configuration, sélectionnez l'application qui est utilisé par votre bibliothèque. Définir la valeur par défaut d'être ce que vos tests de comparaison de déterminer la meilleure solution.1 Et oui, si vous utilisez une interface, vous devrez compter sur la JVM assez intelligent pour inline appels.2


(1) Par "référence", je veux dire un long programme en cours d'exécution que les exercices de votre bibliothèque à l'extérieur d'un profiler, en passant un éventail d'entrées. Ceux-ci sont extrêmement utiles, mais aussi de mesurer le débit total après une heure à l'horloge murale. Sur plusieurs ordinateurs différents avec différentes tailles de tas et de différentes machines virtuelles, en fonctionnant en mono et multi-thread modes.

(2) Cela peut vous mettre dans une autre ligne de débat sur la performance relative des différents invoquer des opcodes.

2voto

Dennis Cheung Points 11818

Réponse courte: Ne pas de tampon.

Les raisons sont à suivre:

  • Ne pas l'optimiser encore jusqu'à ce qu'il devienne un goulot d'étranglement
  • Si vous recycler, les frais généraux de la gestion de ce pool sera un autre goulot d'étranglement
  • Essayez de faire confiance à l'équipe. Dans la dernière JVM, votre tableau alloué dans la PILE plutôt que de TAS.
  • Croyez-moi, le JRE habitude de ne les traiter plus rapidement et mieux que vous le BRICOLAGE.
  • Keep it simple, plus facile à lire et à déboguer

Lorsque vous devriez recycler un objet:

  • seulement si c'est lourd. La taille de la mémoire ne sera pas lourde, mais natif de ressources et de cycle du PROCESSEUR n', qui a coûté plus de finaliser et de cycle du PROCESSEUR.
  • Vous pouvez les recycler si elles sont "ByteBuffer" plutôt que de byte[]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X