Warum sind die Saiten so langsam?

23

Seit meiner allerersten Programmierstunde in der High School habe ich gehört, dass Saitenoperationen langsamer - dh teurer - sind als die mythische "durchschnittliche Operation". Warum macht sie so langsam? (Diese Frage ist absichtlich weit gefasst.)

Pops
quelle
11
Wenn Sie wissen, dass diese "Durchschnittsoperationen" mythisch sind, können Sie uns wenigstens sagen, welche davon es sind? Angesichts der Tatsache, dass Sie eine so vage Frage stellen, ist es schwierig, Ihrer Behauptung zu vertrauen, dass diese nicht spezifizierten Operationen wirklich mythisch sind.
SEH
1
@seh, das kann ich leider nicht beantworten. Die wenigen Male, in denen ich Leute gefragt habe, welche Saiten langsamer sind als, zucken sie nur ein bisschen mit den Schultern und sagen: "Sie sind nur langsam." Außerdem, wenn ich spezifischere Informationen hätte, wäre dies eine Frage für SO, nicht für Programmierer; es ist schon ein bisschen grenzwertig.
Pops
Was ist der Sinn ? Wenn die angegebenen Zeichenfolgen tatsächlich langsam sind, werden Sie sie dann nicht mehr verwenden?
Tulains Córdova
Vergiss es. Wenn Ihnen jemand solchen Unsinn erzählt, lautet die Gegenfrage: "Wirklich? Sind sie das? Sollten wir dann ein Int-Array verwenden?"
Ingo

Antworten:

47

"Die durchschnittliche Operation" findet auf Grundelementen statt. Aber selbst in Sprachen, in denen Zeichenfolgen als Primitive behandelt werden, sind sie immer noch Arrays unter der Haube, und alles, was die gesamte Zeichenfolge betrifft, benötigt O (N) Zeit, wobei N die Länge der Zeichenfolge ist.

Zum Beispiel erfordert das Hinzufügen von zwei Nummern im Allgemeinen 2-4 ASM-Anweisungen. Das Verketten ("Hinzufügen") zweier Zeichenfolgen erfordert eine neue Speicherzuordnung und entweder eine oder zwei Zeichenfolgenkopien, die die gesamte Zeichenfolge betreffen.

Bestimmte Sprachfaktoren können es noch schlimmer machen. In C beispielsweise ist eine Zeichenfolge einfach ein Zeiger auf ein nullterminiertes Zeichenarray. Das bedeutet, dass Sie nicht wissen, wie lange es dauert, und daher keine Möglichkeit besteht, eine Zeichenfolgen-Kopierschleife mit schnellen Verschiebevorgängen zu optimieren. Sie müssen jeweils ein Zeichen kopieren, damit Sie jedes Byte auf den Nullterminator testen können.

Mason Wheeler
quelle
4
Und bestimmte Sprachen machen es viel besser: Durch Delphis Codierung der Zeichenfolgenlänge am Anfang des Arrays wird die Verkettung von Zeichenfolgen sehr schnell.
Frank Shearar
4
@gablin: Es hilft auch, indem der String sich viel schneller selbst kopiert. Wenn Sie die Größe im Voraus kennen, müssen Sie nicht jedes Byte einzeln kopieren und jedes Byte auf einen Nullterminator überprüfen, damit Sie die volle Größe aller Register, einschließlich der SIMD-Register, für die Datenverschiebung und -erstellung verwenden können es bis zu 16 mal schneller.
Mason Wheeler
4
@mathepic: Ja, und das ist soweit in Ordnung, aber wenn Sie anfangen, mit libc oder anderem externen Code zu interagieren, wird a erwartet char*, nicht a strbuf, und Sie kehren zu Quadrat 1 zurück. Es gibt nur so viel von Ihnen kann tun, wenn ein schlechtes Design in die Sprache eingebrannt ist.
Mason Wheeler
6
@mathepic: Natürlich ist der bufZeiger da. Ich wollte nie andeuten, dass es nicht verfügbar ist. vielmehr, dass es notwendig ist. Jeder Code, der nichts über Ihren optimierten, aber nicht standardmäßigen Zeichenfolgentyp weiß, einschließlich grundlegender Dinge wie die Standardbibliothek , muss immer noch auf den langsamen, unsicheren zurückgreifen char*. Sie können das FUD nennen, wenn Sie möchten, aber das macht es nicht wahr.
Mason Wheeler
7
Leute, es gibt eine Joel Spolsky Kolumne über Frank Shearers Punkt: Back to Basics
user16764
14

Dies ist ein alter Thread und ich denke, dass die anderen Antworten großartig sind, aber etwas übersehen, also hier sind meine (späten) 2 Cent.

Syntaktische Zuckerbeschichtung verbirgt Komplexität

Das Problem mit Strings ist, dass sie in den meisten Sprachen Bürger zweiter Klasse sind und tatsächlich die meiste Zeit nicht wirklich Teil der Sprachspezifikation selbst sind: Sie sind ein in der Bibliothek implementiertes Konstrukt mit einigen gelegentlichen syntaktischen Zuckerbeschichtungen auf der Oberseite um sie weniger schmerzhaft zu machen.

Die direkte Folge davon ist, dass die Sprache einen großen Teil ihrer Komplexität vor Ihren Augen verbirgt und Sie für die versteckten Nebenwirkungen bezahlen, weil Sie sich angewöhnen, sie genau wie eine atomare Einheit auf niedriger Ebene zu betrachten andere primitive Typen (wie durch die Antwort mit den höchsten Bewertungen und andere erklärt).

Implementierungsdetails

Gute alte Reihe

Eines der Elemente dieser zugrunde liegenden "Komplexität" ist, dass die meisten String-Implementierungen auf die Verwendung einer einfachen Datenstruktur mit zusammenhängendem Speicherplatz zurückgreifen würden, um den String darzustellen: Ihr gutes altes Array.

Dies ist jedoch sinnvoll, da der Zugriff auf die gesamte Zeichenfolge schnell erfolgen soll. Dies bedeutet jedoch möglicherweise schreckliche Kosten, wenn Sie diese Zeichenfolge manipulieren möchten. Der Zugriff auf ein Element in der Mitte ist schnell, wenn Sie wissen, nach welchem ​​Index Sie suchen , aber wenn Sie nach einem Element suchen , das auf einer Bedingung basiert, ist dies nicht der Fall.

Das Zurückgeben der Zeichenfolge kann sogar kostspielig sein, wenn Ihre Sprache die Länge der Zeichenfolge nicht zwischenspeichert und sie zum Zählen von Zeichen durchlaufen muss.

Aus ähnlichen Gründen erweist sich das Hinzufügen von Elementen zu Ihrer Zeichenfolge als kostspielig, da Sie höchstwahrscheinlich Speicher neu zuweisen müssen, damit dieser Vorgang ausgeführt werden kann.

Daher verfolgen verschiedene Sprachen unterschiedliche Ansätze für diese Probleme. Java hat sich beispielsweise die Freiheit genommen, seine Zeichenfolgen aus bestimmten gültigen Gründen (Caching-Länge, Thread-Sicherheit) unveränderlich zu machen, und seine veränderlichen Gegenstücke (StringBuffer und StringBuilder) werden sich dafür entscheiden, Größe mithilfe größerer Blöcke zuzuweisen, die nicht zugeordnet werden müssen jedes Mal, sondern hoffen auf Best-Case-Szenarien. Es funktioniert im Allgemeinen gut, aber die Kehrseite ist, manchmal für Gedächtnisstörungen zu bezahlen.

Unicode-Unterstützung

Auch und gerade deshalb, weil die syntaktische Zuckerbeschichtung Ihrer Sprache dies vor Ihnen verbirgt, um gut zu spielen, halten Sie es oft nicht für Unicode-Unterstützung (insbesondere solange Sie es nicht wirklich brauchen) und gegen die Wand stoßen). Und einige Sprachen, die vorausschauend denken, implementieren keine Strings mit darunter liegenden Arrays von einfachen 8-Bit-Zeichenprimitiven. Sie wurden in UTF-8 oder UTF-16 gebacken oder was-hast-du-Unterstützung für Sie, und die Folge ist ein enorm höherer Speicherverbrauch, der oftmals nicht benötigt wird, und eine längere Verarbeitungszeit zum Zuweisen von Speicher, Verarbeiten der Zeichenfolgen. und implementieren Sie die gesamte Logik, die mit der Manipulation von Codepunkten einhergeht.


Das Ergebnis all dessen ist, dass, wenn Sie im Pseudocode etwas Äquivalentes tun zu:

hello = "hello,"
world = " world!"
str = hello + world

Es kann sein, dass es trotz aller Bemühungen der Sprachentwickler nicht so ist, wie Sie es möchten:

a = 1;
b = 2;
shouldBeThree = a + b

Im Anschluss möchten Sie vielleicht lesen:

Haylem
quelle
Gute Ergänzung zur aktuellen Diskussion.
Abel
Mir ist gerade klar geworden, dass dies die beste Antwort ist, da die mythische Aussage auf etwas angewendet werden kann, bei dem die RSA-Verschlüsselung langsam ist. Der einzige Grund, warum Zeichenfolgen an dieser peinlichen Stelle abgelegt werden, besteht darin, dass der Plus-Operator Zeichenfolgen in den meisten Sprachen bereitgestellt hat, wodurch Anfängern die Kosten für die Operation nicht bewusst werden.
Codism
@Abel: danke, schien mir der raum für allgemeinere details zu sein.
Haylem
@Codism: danke, bin froh, dass es dir gefallen hat. Ich denke in der Tat, dass dies in vielen Fällen angewendet werden kann, in denen es nur darum geht, dass Komplexität verborgen bleibt (und dass wir den Details auf niedrigerer Ebene nicht mehr so ​​viel Aufmerksamkeit schenken, bis wir es schließlich müssen, weil wir auf einen Engpass oder eine Art Mauer stoßen ).
Haylem
1

Der Ausdruck "durchschnittliche Operation" ist wahrscheinlich eine Abkürzung für eine einzelne Operation einer theoretischen Maschine mit wahlfreiem Zugriff und gespeichertem Programm . Dies ist die theoretische Maschine, mit der üblicherweise die Laufzeit verschiedener Algorithmen analysiert wird.

Die generischen Operationen sind normalerweise Laden, Addieren, Subtrahieren, Speichern und Verzweigen. Vielleicht auch lesen, drucken und anhalten.

Die meisten Zeichenfolgenoperationen erfordern jedoch mehrere dieser grundlegenden Operationen. Beispielsweise erfordert das Duplizieren einer Zeichenfolge normalerweise einen Kopiervorgang und daher eine Anzahl von Vorgängen, die proportional zur Länge einer Zeichenfolge sind (dh, sie ist "linear"). Das Suchen eines Teilstrings in einem anderen String ist ebenfalls linear komplex.

James Youngman
quelle
1

Es hängt ganz von der Operation ab, wie Zeichenfolgen dargestellt werden und welche Optimierungen vorhanden sind. Wenn Strings 4 oder 8 Bytes lang (und ausgerichtet) sind, wären sie nicht unbedingt langsamer - viele Operationen wären genauso schnell wie Primitive. Wenn alle Zeichenfolgen über einen 32-Bit- oder 64-Bit-Hash verfügen, sind viele Operationen ebenfalls genauso schnell (obwohl Sie die Hashing-Kosten im Voraus bezahlen).

Es kommt auch darauf an, was Sie mit "langsam" meinen. Die meisten Programme verarbeiten Zeichenfolgen sehr schnell für das, was benötigt wird. Zeichenfolgenvergleiche sind möglicherweise nicht so schnell wie der Vergleich von zwei Ints, aber nur die Profilerstellung zeigt, was "langsam" für Ihr Programm bedeutet.

Kevin Hsu
quelle
0

Lassen Sie mich Ihre Frage mit einer Frage beantworten. Warum dauert das Sprechen einer Wortfolge länger als das Sprechen eines einzelnen Wortes?

ChaosPandion
quelle
2
Das muss nicht sein.
user16764
3
Supercalifragilisticexpialidocious
Spoike
s / word /
Caleb
Lassen Sie mich Ihre Frage-Antwort mit einer Frage beantworten: Warum sagen Sie nicht, was Ihre Antwort bedeuten soll? Es ist schließlich alles andere als klar, wie es auf ein Laufzeitsystem angewendet werden kann.
PJTraill