web-dev-qa-db-fra.com

Shrink_to_fit est-il le moyen approprié de réduire la capacité d’un `std :: vector` à sa taille?

En C++ 11, shrink_to_fit a été introduit pour compléter certains conteneurs STL (par exemple, std::vector, std::deque, std::string).

En résumé, sa fonctionnalité principale consiste à demander au conteneur associé à, de réduire sa capacité à s’adapter à sa taille . Cependant, cette demande n'est pas contraignante , et l'implémentation du conteneur est libre de l'optimiser et de laisser le vecteur avec une capacité supérieure à sa taille.

De plus, dans une précédente question SO, il était déconseillé à l'OP d'utiliser shrink_to_fit pour réduire la capacité de son std::vector à sa taille. Les raisons pour ne pas le faire sont citées ci-dessous:

shrink_to_fit ne fait rien ou il vous donne des problèmes de localisation de cache et c'est O(n) à exécuter (puisque vous devez copier chaque élément dans leur nouvelle maison plus petite). Habituellement, il est moins coûteux de laisser le jeu en mémoire @Massa

Quelqu'un pourrait-il avoir la gentillesse de répondre aux questions suivantes:

  • Les arguments de la citation sont-ils valables?
  • Si oui , quelle est la bonne façon de réduire la capacité d'un conteneur STL à sa taille (au moins pour std::vector).
  • Et s'il existe un meilleur moyen de réduire un conteneur, quelle est la raison de l'existence de shrink_to_fit après-tout?
19
101010

Les arguments de la citation sont-ils valables?

Mesurer et vous saurez. Êtes-vous contraint en mémoire? Pouvez-vous déterminer la taille correcte à l'avance? Il sera plus efficace de reserve que de shrink après le fait. En général, je suis enclin à accepter le principe selon lequel la plupart des utilisations conviennent probablement au mou.

Si tel est le cas, quelle est la bonne façon de réduire la capacité d'un conteneur STL à sa taille (au moins pour std :: vector).

Le commentaire ne s'applique pas seulement à shrink_to_fit, mais à tout autre moyen de réduire. Étant donné que vous ne pouvez pas realloc sur place, il vous faut acquérir un bloc de mémoire différent et le copier, quel que soit le mécanisme que vous utilisez pour le réduire.

Et s’il existe un meilleur moyen de réduire un conteneur, quelle est la raison de l’existence de shrink_to_fit après tout?

La demande est non contraignante, mais les solutions de rechange ne présentent pas de meilleures garanties. La question est de savoir si shrinking a un sens, le cas échéant, il est donc logique de fournir une opération shring_to_fit qui puisse tirer parti du fait que les objets sont en cours de déplacés vers un nouvel emplacement. . C'est à dire. Si le type T a un constructeur de déplacement noexcept(true), il allouera la nouvelle mémoire et déplacera les éléments.

Bien que vous puissiez obtenir la même chose en externe, cette interface simplifie l'opération. L'équivalent de shrink_to_fit en C++ 03 aurait été:

std::vector<T>(current).swap(current);

Mais le problème avec cette approche est que, lorsque la copie est faite sur le temporaire, elle ne sait pas que current va être remplacée, rien n'indique à la bibliothèque que can déplace les objets tenus. Notez que l’utilisation de std::move(current) n’obtiendrait pas l’effet souhaité, car elle aurait move l’ensemble de la mémoire tampon, en conservant le même capacity().

La mise en œuvre de cette solution à l’extérieur serait un peu plus lourde:

{
   std::vector<T> copy;
   if (noexcept(T(std::move(declval<T>())))) {
      copy.assign(std::make_move_iterator(current.begin()),
                  std::make_move_iterator(current.end()));
   } else {
      copy.assign(current.begin(), current.end());
   }
   copy.swap(current);
}

En supposant que la condition if soit correcte ... ce qui n'est probablement pas ce que vous voulez écrire à chaque fois que vous souhaitez cette opération.

  • Les arguments tiendront-ils?

Les arguments étant à l'origine miens, cela ne me dérange pas que je les défende un à un:

  1. Soit shrink_to_fit ne fait rien (...)

    Comme il a été mentionné, la norme indique (plusieurs fois, mais dans le cas de vector, il s'agit de la section 23.3.7.3 ...) que la demande n'est pas contraignante pour permettre une latitude d'implémentation pour les optimisations . Cela signifie que la mise en œuvre can define shrink_to_fit est non-op.

  2. (...) ou il vous donne des problèmes de localisation de cache

    Dans le cas où shrink_to_fit est not implémenté en tant que no-op, vous devez allouer un nouveau conteneur sous-jacent ayant la capacité size(), copier (ou, dans le meilleur des cas, déplacer) construire tous vos nouveaux éléments N = size() à partir de l'ancien. ceux-ci, détruisent tous les anciens (dans le cas du déplacement, cela devrait être optimisé, mais il est possible que cela implique une boucle sur l'ancien conteneur), puis la destruction de l'ancien conteneur en tant que tel. Ceci est fait, dans libstdc++-4.9, exactement comme David Rodriguez l’a décrit, par

          _Tp(__make_move_if_noexcept_iterator(__c.begin()),
              __make_move_if_noexcept_iterator(__c.end()),
              __c.get_allocator()).swap(__c);
    

    et dans libc++-3.5, par une fonction dans __alloc_traits qui fait approximativement la même chose.

    Oh, et une implémentation ne peut absolument pas compter sur realloc (même si elle utilise malloc à l'intérieur de ::operator new pour ses allocations de mémoire) parce que realloc, si elle ne peut pas être réduite sur place, laissera la mémoire seule (cas non-op) ou effectuez une copie au niveau du bit (et manquez l'opportunité de réajuster les pointeurs, etc., que les constructeurs de copie/déplacement C++ appropriés donneraient).

    Bien sûr, on peut écrire un allocateur de mémoire rétractable et l’utiliser dans le constructeur de ses vecteurs.

    Dans le cas facile où les vecteurs sont plus grands que les lignes de cache, tous ces mouvements exercent une pression sur le cache.

  3. et c'est O (n)

    Si n = size(), je pense qu’il a été établi plus haut que vous devez au moins faire une allocation de taille n, des constructions n de copie ou de déplacement, des destructions n et un désallocation de taille old_capacity.

  4. généralement, il est moins cher de laisser le temps libre en mémoire

    Évidemment, sauf si vous êtes vraiment pressé pour la mémoire libre (dans ce cas, il serait peut-être plus sage de sauvegarder vos données sur le disque et de les recharger plus tard à la demande ...)

  • Si tel est le cas, quelle est la bonne façon de réduire la capacité d'un conteneur STL à sa taille (au moins pour std :: vector).

La bonne façon de procéder est toujours shrink_to_fit... il vous suffit de ne pas vous y fier ou de bien connaître votre implémentation!

  • Et s’il existe un meilleur moyen de réduire un conteneur, quelle est la raison de l’existence de shrink_to_fit après tout?

Il n'y a pas de meilleur moyen, mais la raison de l'existence de shrink_to_fit est, AFAICT, que parfois votre programme peut ressentir une pression sur la mémoire et que c'est une façon de le traiter. Pas un très bon moyen, mais quand même.

HTH!

14
Massa
  • Si tel est le cas, quelle est la bonne façon de réduire la capacité d'un conteneur STL à sa taille (au moins pour std :: vector).

Le «truc d’échange» réduira un vecteur à la taille exacte requise (à partir de SQL plus efficace):

vector<Person>(persons).swap(persons);

Particulièrement utile lorsque le vecteur est vide, pour libérer toute la mémoire:

vector<Person>().swap(persons);

Les vecteurs ont constamment déclenché le code de détection des fuites de mémoire de mon testeur en raison des affectations conservées d'espace inutilisé, ce qui les a parfaitement triés.

C’est le genre d’exemple où l’importance de l’exécution (taille ou vitesse) me importe peu, mais l’utilisation exacte de la mémoire me préoccupe.

  • Et s’il existe un meilleur moyen de réduire un conteneur, quelle est la raison de l’existence de shrink_to_fit après tout?

Je ne sais vraiment pas à quoi sert une fonction qui ne peut absolument rien légalement. J'ai applaudi quand j'ai vu qu'il avait été introduit, puis désespéré quand j'ai découvert qu'on ne pouvait plus compter sur lui.

Peut-être verrons-nous peut-être () dans la prochaine version.

1
Andy Krouwel