Pour allocate()
ou pour allocateDirect()
, telle est la question.
Depuis quelques années, je ne fais que penser que, puisque DirectByteBuffer
s sont un mappage direct de la mémoire au niveau du système d’exploitation, il serait plus rapide avec les appels get/put que HeapByteBuffer
s. Je n'ai jamais vraiment cherché à connaître les détails exacts de la situation jusqu'à maintenant. Je veux savoir lequel des deux types de ByteBuffer
s sont plus rapides et à quelles conditions.
Ron Hitches dans son excellent livre Java NIO semble offrir ce que je pensais être une bonne réponse à votre question:
Les systèmes d'exploitation effectuent des opérations d'E/S sur les zones de mémoire. En ce qui concerne le système d'exploitation, ces zones de mémoire sont des séquences d'octets contiguës. Il n’est donc pas surprenant que seuls les tampons d’octets soient autorisés à participer aux opérations d’E/S. Rappelez-vous également que le système d'exploitation accédera directement à l'espace d'adressage du processus, dans ce cas le processus JVM, pour transférer les données. Cela signifie que les zones de mémoire cibles des opérations d'E/S doivent être des séquences d'octets contiguës. Dans la machine virtuelle Java, un tableau d'octets ne peut pas être stocké de manière contiguë en mémoire ou le récupérateur de place peut le déplacer à tout moment. Les tableaux sont des objets en Java et la manière dont les données sont stockées à l'intérieur de cet objet peut varier d'une implémentation JVM à une autre.
Pour cette raison, la notion de tampon direct a été introduite. Les tampons directs sont destinés à l'interaction avec les canaux et les routines d'E/S natives. Ils s'efforcent de stocker les éléments d'octet dans une zone mémoire qu'un canal peut utiliser pour un accès direct ou brut en utilisant un code natif pour indiquer au système d'exploitation de vider ou de remplir directement la zone mémoire.
Les tampons d'octets directs sont généralement le meilleur choix pour les opérations d'E/S. De par leur conception, ils prennent en charge le mécanisme d’E/S le plus efficace disponible pour la machine virtuelle Java. Les tampons d'octets non directs peuvent être transmis aux canaux, mais cela pourrait entraîner une pénalité de performances. Il n'est généralement pas possible qu'un tampon non direct soit la cible d'une opération d'E/S native. Si vous transmettez un objet ByteBuffer non direct à un canal pour écriture, le canal peut implicitement procéder comme suit à chaque appel:
- Créez un objet ByteBuffer direct temporaire.
- Copiez le contenu du tampon non direct dans le tampon temporaire.
- Effectuez l'opération d'E/S de bas niveau à l'aide du tampon temporaire.
- L'objet tampon temporaire sort de la portée et est finalement collecté.
Cela peut potentiellement entraîner la copie de la mémoire tampon et la perte d'objet sur chaque E/S, ce qui est exactement le genre de choses que nous aimerions éviter. Cependant, selon l’implémentation, les choses ne sont peut-être pas si mauvaises. Le moteur d'exécution mettra probablement en cache et réutilisera des tampons directs ou effectuera d'autres astuces astucieuses pour augmenter le débit. Si vous créez simplement un tampon pour une utilisation unique, la différence n'est pas significative. D'autre part, si vous utilisez le tampon de manière répétée dans un scénario très performant, il vaut mieux allouer des tampons directs et les réutiliser.
Les tampons directs sont optimaux pour les E/S, mais ils peuvent être plus coûteux à créer que les tampons non directs en octets. La mémoire utilisée par les tampons directs est allouée en appelant le code natif, spécifique au système d'exploitation, en contournant le segment de mémoire JVM standard. La configuration et la suppression des mémoires tampons directes peuvent s'avérer beaucoup plus coûteuses que les mémoires tampons résidantes, en fonction du système d'exploitation hôte et de la mise en œuvre de la machine virtuelle Java. Les zones de mémoire de stockage des mémoires tampons directes ne sont pas sujettes à la récupération de place car elles se situent en dehors du segment de mémoire JVM standard.
Les compromis de performance entre l’utilisation de tampons directs et non directs peuvent varier considérablement en fonction de la conception de la JVM, du système d’exploitation et du code. En allouant de la mémoire en dehors du tas, vous pouvez soumettre votre application à des forces supplémentaires inconnues de la machine virtuelle. Lorsque vous mettez en jeu d’autres pièces mobiles, assurez-vous d’obtenir l’effet souhaité. Je recommande l'ancienne maxime logicielle: faites-la d'abord fonctionner, puis faites-la rapidement. Ne vous inquiétez pas trop de l'optimisation dès le départ; concentrez-vous d'abord sur la correction. L’implémentation de la machine virtuelle Java peut être en mesure d’effectuer la mise en cache de la mémoire tampon ou d’autres optimisations qui vous donneront les performances dont vous avez besoin sans trop d’efforts inutiles de votre part.
Il n'y a aucune raison de s'attendre à ce que les tampons directs soient plus rapides pour l'accès à l'intérieur le jvm. Leur avantage vient lorsque vous les passez au code natif - tel que le code derrière les canaux de toutes sortes.
depuis DirectByteBuffers sont un mappage direct de la mémoire au niveau du système d'exploitation
Ils ne sont pas. Ce ne sont que des mémoires de processus d’application normales, mais ne peuvent pas être déplacées au cours de Java GC, ce qui simplifie considérablement les opérations dans la couche JNI. Ce que vous décrivez correspond à MappedByteBuffer
.
qu'il serait plus rapide avec les appels entrants/sortants
La conclusion ne découle pas de la prémisse; la prémisse est fausse; et la conclusion est également fausse. Ils sont plus rapides une fois dans la couche JNI, et si vous lisez et écrivez à partir du même DirectByteBuffer
ils sont beaucoup plus vite, car les données ne doivent jamais franchir la limite JNI à tout.
Le mieux est de faire vos propres mesures. La réponse rapide semble être que l'envoi depuis un tampon allocateDirect()
prend 25 à 75% moins de temps que la variante allocate()
(testé comme copie d'un fichier dans/dev/null), selon taille, mais que l'allocation elle-même peut être considérablement plus lente (même par un facteur de 100).
Sources: