Depuis ma toute première classe de programmation au lycée, j'entends dire que les opérations de cordes sont plus lentes - c'est-à-dire plus coûteuses - que la mythique «opération moyenne». Pourquoi les rend-ils si lents? (Cette question est laissée intentionnellement large.)
23
Réponses:
"L'opération moyenne" a lieu sur des primitives. Mais même dans les langues où les chaînes sont traitées comme des primitives, elles sont toujours des tableaux sous le capot, et faire quoi que ce soit impliquant la chaîne entière prend du temps O (N), où N est la longueur de la chaîne.
Par exemple, l'ajout de deux nombres prend généralement 2 à 4 instructions ASM. La concaténation ("ajout") de deux chaînes nécessite une nouvelle allocation de mémoire et une ou deux copies de chaîne, impliquant la chaîne entière.
Certains facteurs linguistiques peuvent aggraver la situation. En C, par exemple, une chaîne est simplement un pointeur vers un tableau de caractères terminé par null. Cela signifie que vous ne savez pas combien de temps il est, il n'y a donc aucun moyen d'optimiser une boucle de copie de chaîne avec des opérations de déplacement rapide; vous devez copier un caractère à la fois afin de pouvoir tester chaque octet pour le terminateur nul.
la source
char*
, pas unstrbuf
, et vous revenez à la case 1. Il n'y a que tant de choses que vous peut faire quand un mauvais design est cuit dans la langue.buf
pointeur est là. Je n'ai jamais voulu laisser entendre qu'il n'est pas disponible; c'est plutôt nécessaire. Tout code qui ne connaît pas votre type de chaîne optimisé mais non standard, y compris des éléments aussi fondamentaux que la bibliothèque standard , doit toujours se replier sur le lent et dangereuxchar*
. Vous pouvez appeler ce FUD si vous le souhaitez, mais cela ne le rend pas faux.Ceci est un vieux fil et je pense que les autres réponses sont excellentes, mais oubliez quelque chose, alors voici mes (tardifs) 2 cents.
Le revêtement de sucre syntactique cache la complexité
Le problème avec les chaînes est qu'elles sont des citoyens de seconde classe dans la plupart des langues, et ne sont en fait pas la plupart du temps une partie de la spécification de la langue elle-même: elles sont une construction implémentée par la bibliothèque avec une couche de sucre syntaxique occasionnelle sur le dessus pour les rendre moins pénibles à utiliser.
La conséquence directe de cela est que le langage cache une très grande partie de leur complexité à votre vue, et vous payez pour les effets secondaires sournois parce que vous avez l'habitude de les considérer comme une entité atomique de bas niveau, tout comme d'autres types primitifs (comme expliqué par la réponse la plus votée et d'autres).
Détails d'implémentation
Good Ol 'Array
Un des éléments de cette "complexité" sous-jacente est que la plupart des implémentations de chaînes auraient recours à une structure de données simple avec un espace mémoire contigu pour représenter la chaîne: votre bon vieux tableau.
Cela a du sens, sachez que vous voulez que l'accès à la chaîne dans son ensemble soit rapide. Mais cela implique des coûts potentiellement terribles lorsque vous souhaitez manipuler cette chaîne. L'accès à un élément au milieu peut être rapide si vous savez quel index vous recherchez , mais pas la recherche d'un élément basé sur une condition.
Même le retour de la taille de la chaîne peut être coûteux, si votre langue ne met pas en cache la longueur de la chaîne et doit la parcourir pour compter les caractères.
Pour des raisons similaires, l' ajout d' éléments à votre chaîne s'avérera coûteux car vous devrez probablement réallouer de la mémoire pour que cette opération se produise.
Ainsi, différentes langues adoptent des approches différentes à ces problèmes. Java, par exemple, a pris la liberté de rendre ses chaînes immuables pour des raisons valables (longueur de mise en cache, sécurité des threads) et pour ses homologues mutables (StringBuffer et StringBuilder) choisiront d'allouer la taille en utilisant des morceaux de plus grande taille pour ne pas avoir besoin d'allouer à chaque fois, mais espérez plutôt les meilleurs scénarios. Cela fonctionne généralement bien, mais l'inconvénient est parfois de payer pour les impacts sur la mémoire.
Prise en charge Unicode
De plus, et encore une fois, cela est dû au fait que le revêtement de sucre syntaxique de votre langue vous le cache pour jouer bien, vous ne le pensez souvent pas en termes de support unicode (surtout aussi longtemps que vous n'en avez pas vraiment besoin). et a frappé ce mur). Et certains langages, étant avant-gardistes, n'implémentent pas de chaînes avec des tableaux sous-jacents de simples primitives char 8 bits. Ils ont cuit en UTF-8 ou UTF-16 ou ce que vous avez en charge, et la conséquence est une consommation de mémoire considérablement plus grande, qui n'est souvent pas nécessaire, et un temps de traitement plus long pour allouer de la mémoire, traiter les chaînes, et implémenter toute la logique qui va de pair avec la manipulation des points de code.
Le résultat de tout cela, c'est que lorsque vous faites quelque chose d'équivalent en pseudo-code pour:
Il se peut que ce ne soit pas - malgré tous les efforts que les développeurs de langage mettent en œuvre pour les faire se comporter comme vous le feriez sauf - aussi simple que:
À titre de suivi, vous voudrez peut-être lire:
la source
L'expression "opération moyenne" est probablement un raccourci pour une seule opération d'une machine théorique à programme d'accès aléatoire . Il s'agit de la machine théorique qu'il est habituel d'utiliser pour analyser le temps d'exécution de divers algorithmes.
Les opérations génériques sont normalement prises pour être charger, ajouter, soustraire, stocker, branche. Peut-être aussi lire, imprimer et arrêter.
Mais la plupart des opérations de chaîne nécessitent plusieurs de ces opérations fondamentales. Par exemple, la duplication d'une chaîne nécessite normalement une opération de copie, et donc un nombre d'opérations qui est proportionnel à la longueur d'une chaîne (c'est-à-dire qu'elle est "linéaire"). La recherche d'une sous-chaîne à l'intérieur d'une autre chaîne présente également une complexité linéaire.
la source
Cela dépend complètement de l'opération, de la façon dont les chaînes sont représentées et des optimisations existantes. Si les chaînes ont une longueur de 4 ou 8 octets (et sont alignées), elles ne seraient pas nécessairement plus lentes - de nombreuses opérations seraient aussi rapides que les primitives. Ou, si toutes les chaînes ont un hachage 32 bits ou 64 bits, de nombreuses opérations seraient également aussi rapides (bien que vous payiez le coût de hachage à l'avance).
Cela dépend aussi de ce que vous entendez par «lent». La plupart des programmes traitent les chaînes très rapidement pour ce qui est nécessaire. Les comparaisons de chaînes peuvent ne pas être aussi rapides que la comparaison de deux entiers, mais seul le profilage révélera ce que "lent" signifie pour votre programme.
la source
Permettez-moi de répondre à votre question par une question. Pourquoi dire une chaîne de mots prend plus de temps que de dire un seul mot?
la source