EnsureCapacity () de Java SsortingngBuilder (SsortingngBuffer): Pourquoi est-il doublé et incrémenté de 2?

J’ai effectué une recherche à ce sujet, mais je ne trouvais pas pourquoi la méthode ensureCapacity() de SsortingngBuilder ensureCapacity() pas l’ancienne capacité en doublant mais en ajoutant deux.

Donc, lorsque la capacité par défaut de 16 est pleine, la prochaine valeur sera 34, à moins que la longueur de la chaîne ne dépasse pas 34. Pourquoi ne devrait-elle pas être 32?

Ma meilleure hypothèse est de considérer un caractère nul, ‘\ u0000’, mais je ne suis pas sûr. Quelqu’un peut-il me dire pourquoi?

Je crois que cela a à voir avec un moyen simple, bien que quelque peu idiot, d’assurer le cas du coin de très petites chaînes.

Par exemple, si j’ai la chaîne

 "" 

et je le double seulement, je n’aurai pas une taille suffisante pour y ranger autre chose. Si je double et ajoute un petit nombre constant d’espaces, je peux m’assurer que ma nouvelle valeur est plus grande que mon ancienne.

Pourquoi l’incrémenter de deux alors? Probablement une petite amélioration de performance. En ajoutant deux au lieu de 1, je peux éviter une expansion intermédiaire pour les petites extensions (0 à 10 caractères détaillés ci-dessous)

 "" => expand => "1" => expand => "123" expand => "1234567" expand => "123456789012345" 

qui est 4 développe par rapport à

 "" => expand => "12" => expand => "123456" => expand => "123456789012" 

qui est 3 se développe. Cela fonctionne aussi très bien pour les chaînes à un caractère (extension à 10 caractères)

 "1" => expand => "1234" => expand => "1234567890" 

tandis que la routine d’expansion de 1 caractère ressemble à

 "1" => expand => "123" => expand => "1234567" => expand => "123456789012345" 

Enfin, un incrément supplémentaire de deux a tendance à aligner les mots environ 50% du temps, tandis que des incréments d’un ou trois le feraient environ 25% du temps. Bien que cela ne semble pas très grave, certaines architectures ne peuvent pas prendre en charge les lectures non alignées sans appels d’interruption coûteux pour réécrire la lecture dans la CPU, ce qui entraîne toutes sortes de problèmes de performances.

Je pense que la raison est une combinaison de

  • une stratégie heuristique ancienne 😉 comment étendre la capacité, en particulier pour les tampons courts,

  • documenter cette stratégie dans les premiers documents de l’API Java,

  • Sun / Oracle faisant très attention de s’en tenir à un comportement déjà documenté.

SsortingngBuilder partage cette méthode avec son prédécesseur SsortingngBuffer, qui lit (probablement depuis les tout débuts, du moins dans j2sdk1.4_02, qui existait encore dans certains dossiers d’archives de ma machine):

 /** * Ensures that the capacity of the buffer is at least equal to the * specified minimum. * If the current capacity of this ssortingng buffer is less than the * argument, then a new internal buffer is allocated with greater * capacity. The new capacity is the larger of: * 
    *
  • The minimumCapacity argument. *
  • Twice the old capacity, plus 2. *
* If the
minimumCapacity argument is nonpositive, this * method takes no action and simply returns. * * @param minimumCapacity the minimum desired capacity. */ public synchronized void ensureCapacity(int minimumCapacity) { if (minimumCapacity > value.length) { expandCapacity(minimumCapacity); } }

Et il documente exactement le comportement temps-deux plus-deux, donc même si entre-temps un développeur de JRE avait trouvé une meilleure stratégie, il n’y avait aucune chance de la mettre en œuvre ici car elle ne serait pas conforme à la documentation.

Je suppose que l’alignement des objects est une clé car la length * 2 + 2 est efficace pour la mémoire (voir l’explication ci-dessous).

Considérons HotSpot JVM .

Tout d’abord, les objects Java sont alignés sur 8 octets et le tableau de caractères ne fait pas exception.

Deuxièmement, sizeof(object header) est égal à 8 bytes sur une machine virtuelle Java 32 bits et à 16 bytes sur une machine virtuelle Java 64 bits avec -XX: -UseCompressedOops .

Ainsi, le corps de l’object doit être aligné sur 8 bytes :
objectBodySize(charArray) == sizeOf(arrayLength) + sizeOf(arrayValues) == (4 bytes) + (arrayLength * 2 bytes) .

Si l’ancienne longueur du tableau est paire , la nouvelle longueur donnera toujours un alignement de taille zéro.

Exemples:

  1. oldCharArrayLength == 6 puis newCharArrayLength == 14 et objectBodySize(newCharArray) == 4 + 14 * 2 == 32

  2. oldCharArrayLength == 4 puis newCharArrayLength == 10 et objectBodySize(newCharArray) == 4 + 10 * 2 == 24

Il est important de noter que l’ indicateur -XX: + UseCompressedOops est disponible depuis la version 1.6, alors que SsortingngBuilder et AbstractSsortingngBuilder sont disponibles depuis la version 1.5 . Cela signifie que la stratégie ci-dessus avec deux caractères supplémentaires n’a aucun coût de mémoire sur les machines virtuelles Java 64 bits avant la version 1.6 , alors que sizeof(object header) == 12 bytes lorsqu’il est exécuté sur une machine virtuelle Java 64 bits avec -XX: + UseCompressedOops .

J’ai téléchargé le code source d’origine de Java 1.5 à partir du site Web Oracle. Il contient les lignes suivantes:

 /** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */ void expandCapacity(int minimumCapacity) { int newCapacity = (value.length + 1) * 2; if (newCapacity < 0) { newCapacity = Integer.MAX_VALUE; } else if (minimumCapacity > newCapacity) { newCapacity = minimumCapacity; } char newValue[] = new char[newCapacity]; System.arraycopy(value, 0, newValue, 0, count); value = newValue; } 

Donc, au moins deux choses sont claires:

  • la théorie selon laquelle d’autres corrections ont également été ajoutées est fausse (par exemple, “la sémantique impaire (double + 2) a plus de sens lorsqu’elle est la seule ligne de la fonction” n’est pas vraie)
  • très probablement il était à l’origine conçu comme “faisons de la place pour au moins un personnage de plus et multiplions-le par deux”
  public void ensureCapacity(int minimumCapacity) { if (minimumCapacity > value.length) { expandCapacity(minimumCapacity); } } void expandCapacity(int minimumCapacity) { int newCapacity = (value.length + 1) * 2; if (newCapacity < 0) { newCapacity = Integer.MAX_VALUE; } else if (minimumCapacity > newCapacity) { newCapacity = minimumCapacity; } value = Arrays.copyOf(value, newCapacity); } 

REMARQUE: value.length est la capacité du SsortingngBuffer, pas la longueur.

Cela n’a rien à voir avec une chaîne nulle car la capacité minimale est de 16.

Ce que je pense, c’est que les allocations de mémoire sont si longues, et si nous appelons EnsureCapacity () fréquemment avec un minimumCapacity croissant, (Capacity +1) * 2 allouera un peu plus de mémoire et réduira les allocations supplémentaires et gagnera du temps.

permet de considérer la capacité initiale comme 16,

seulement en doublant 16, 32, 64, 128, 256, 512, 1024, 2048, etc.

avec double +2 16, 34, 70, 142, 286, 574, 1150, 2302, etc.

Ainsi, la mémoire augmentera progressivement à chaque fois et peut diminuer le nombre d’allocations de mémoire.