web-dev-qa-db-fra.com

Pourquoi '' .join () est plus rapide que + = en Python?

Je suis en mesure de trouver une multitude d'informations en ligne (sur Stack Overflow et autres) sur la façon dont il est très inefficace et une mauvaise pratique d'utiliser + Ou += Pour la concaténation en Python.

Je n'arrive pas à trouver POURQUOI += Est si inefficace. En dehors d'une mention ici que "il a été optimisé pour une amélioration de 20% dans certains cas" (toujours pas clair ce que sont ces cas), je ne trouve aucune information supplémentaire.

Que se passe-t-il à un niveau plus technique qui rend ''.join() supérieur aux autres méthodes de concaténation Python?

62
Rodney Wells

Disons que vous avez ce code pour construire une chaîne à partir de trois chaînes:

x = 'foo'
x += 'bar'  # 'foobar'
x += 'baz'  # 'foobarbaz'

Dans ce cas, Python doit d'abord allouer et créer 'foobar' avant de pouvoir allouer et créer 'foobarbaz'.

Donc pour chaque += qui est appelé, tout le contenu de la chaîne et tout ce qui y est ajouté doivent être copiés dans une mémoire tampon entièrement nouvelle. En d'autres termes, si vous avez N chaînes à joindre, vous devez allouer environ N chaînes temporaires et la première sous-chaîne est copiée ~ N fois. La dernière sous-chaîne n'est copiée qu'une seule fois, mais en moyenne, chaque sous-chaîne est copiée ~N/2 fois.

Avec .join, Python peut jouer un certain nombre d'astuces car les chaînes intermédiaires n'ont pas besoin d'être créées. CPython détermine la quantité de mémoire dont il a besoin à l'avance, puis alloue un tampon correctement dimensionné. Enfin, il copie chaque morceau dans le nouveau tampon, ce qui signifie que chaque morceau n'est copié qu'une seule fois.


Il existe d'autres approches viables qui pourraient conduire à de meilleures performances pour += dans certains cas. Par exemple. si la représentation de chaîne interne est en fait un rope ou si le runtime est en fait assez intelligent pour comprendre en quelque sorte que les chaînes temporaires ne sont d'aucune utilité pour le programme et les optimisent.

Cependant, CPython ne fait certainement pas pas ces optimisations de manière fiable (même si cela peut pour un quelques cas d'angle ) et comme c'est le implémentation la plus courante utilisée, de nombreuses meilleures pratiques sont basées sur ce qui fonctionne bien pour CPython. Le fait d'avoir un ensemble normalisé de normes permet également aux autres implémentations de concentrer également leurs efforts d'optimisation.

75
mgilson

Je pense que ce comportement est mieux expliqué dans chapitre sur le tampon de chaîne de Lua .

Pour réécrire cette explication dans le contexte de Python, commençons par un extrait de code innocent (un dérivé de celui des documents de Lua):

s = ""
for l in some_list:
  s += l

Supposons que chaque l fait 20 octets et que le s a déjà été analysé à une taille de 50 Ko. Lorsque Python concatène s + l il crée une nouvelle chaîne de 50 020 octets et copie 50 Ko de s dans cette nouvelle chaîne. Autrement dit, pour chaque nouvelle ligne, le programme déplace 50 Ko de mémoire et augmente. Après avoir lu 100 nouvelles lignes (seulement 2 Ko), l'extrait a déjà déplacé plus de 5 Mo de mémoire. Pour aggraver les choses, après la mission

s += l

l'ancienne chaîne est maintenant une poubelle. Après deux cycles de boucle, il y a deux anciennes chaînes faisant un total de plus de 100 Ko de déchets. Ainsi, le compilateur de langage décide d'exécuter son garbage collector et libère ces 100 Ko. Le problème est que cela se produit tous les deux cycles et que le programme exécute son garbage collector deux mille fois avant de lire la liste entière. Même avec tout ce travail, son utilisation de la mémoire sera un grand multiple de la taille de la liste.

Et à la fin:

Ce problème n'est pas propre à Lua: d'autres langages avec une véritable récupération de place et où les chaînes sont des objets immuables, présentent un comportement similaire, Java étant l'exemple le plus célèbre. (Java offre la structure StringBuffer pour améliorer le problème.)

Les chaînes Python sont également objets immuables .

7
hjpotter92