Re: [Python-de] strings zusammensetzen.
On 2017-09-16 08:33, Stefan Behnel <python-de@behnel.de> wrote:
Peter J. Holzer schrieb am 16.09.2017 um 09:28:
(String-Konkatenation ist in Python(3) zumindest bei langen Strings auch schön linear)
Es gibt sowohl für bytes-Objekte als auch für Unicode-Strings eine Sonderbehandlung, die realloc() verwendet. Auf vielen Platformen ist realloc() effizient und führt im Durchschnitt zu einer linearen Laufzeit auch bei mehreren Konkatenierungen. Aber nicht auf allen.
Ineffiziente Plattformen sollte man meiden :-). Allerdings ist das eher eine Frage, wie Python damit umgeht als wie gut die Malloc- Implementation ist - obwohl eine gute Malloc-Implementation gewisse Schwächen im Interpreter gut übertünchen kann (wie eine Diskussion zum gleichen Thema in Perl vor ein paar Jahren gezeigt hat).
Außerdem greift die Optimierung nicht in allen Fällen. Beispielsweise hast du Pech, wenn über die Konkatenierungen hinweg der Zeichenraum erst von ASCII auf BMP und dann auf Astral wechselt. Dann muss beim Wechsel der komplette bisherige String doch wieder im Speicher herum kopiert werden.
Das dürfte weitgehend vernachlässigbar sein, weil es maximal 2 solche Übergänge geben kann. Also viel weniger als die Kopiervorgänge, die üblicherweise tatsächlich stattfinden, weil realloc nicht in-place expandieren kann.
Also kurz: join() ist in jedem Fall die bessere Variante bei vielen Strings. Konkatenierung ist ok bei einer überschaubaren Menge und da insbesondere bei kurzen Strings, sollte aber in Schleifen vermieden werden.
Du vergisst, dass Listen einen nicht zu vernachlässigbaren Memory-Overhead haben. Gerade bei langen Listen sollte man darauf verzichten, sie aufzubauen, wenn man sie nicht unbedingt braucht (habe damit erst kürzlich 8 GB eingespart - pro Prozess (das war Perl und nicht Python, aber die beiden Sprachen sind sich da sehr ähnlich)). Kleiner Test: ------------------------------------------------------------------------ #!/usr/bin/python3 import sys import time n = int(sys.argv[1]) t0 = time.clock_gettime(time.CLOCK_MONOTONIC) s = "" for i in range(n): s += str(i) t1 = time.clock_gettime(time.CLOCK_MONOTONIC) print(n, t1-t0, n / (t1 - t0)) ------------------------------------------------------------------------ ------------------------------------------------------------------------ #!/usr/bin/python3 import sys import time n = int(sys.argv[1]) tick_size = 1000000 t0 = time.clock_gettime(time.CLOCK_MONOTONIC) elems = [] for i in range(n): elems.append(str(i)) s = "".join(elems) t1 = time.clock_gettime(time.CLOCK_MONOTONIC) print(n, t1 - t0, n / (t1 - t0)) ------------------------------------------------------------------------ Ergebnis: Performance ist praktisch gleich, aber auf einer 32-Bit-Maschine kommt die Konkatenierungsversion gut 3 mal so weit, bevor sie am Memory-Limit anstößt. (Graphik auf <http://www.hjp.at/programming/python/concat-vs-join/>) hp -- _ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung: |_|_) | | Man feilt solange an seinen Text um, bis | | | hjp@hjp.at | die Satzbestandteile des Satzes nicht mehr __/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Peter J. Holzer schrieb am 16.09.2017 um 22:46:
Gerade bei langen Listen sollte man darauf verzichten, sie aufzubauen, wenn man sie nicht unbedingt braucht
Natürlich. Einzig darauf zielt dein Beispielcode ja auch ab. Gilt übrigens genauso für lange Strings.
(habe damit erst kürzlich 8 GB eingespart - pro Prozess (das war Perl und nicht Python, aber die beiden Sprachen sind sich da sehr ähnlich)).
Wie du hier korrekt andeutest, handelt es sich bei dem gegebenen Fall um eine Optimierung. Optimierung bedeutet ja, dass du eigentlich gut geschriebenen Code ersetzt durch etwas, was dem spezifischen Anwendungsfall stärker angepasst ist und (nachweislich) unter den erwarteten Bedingungen effizienter funktioniert. Mit dem Risiko, dass es unter anderen (eben nicht erwarteten) Bedingungen vielleicht auch weniger gut funktioniert. Kann manchmal zu Y2K-Problemen führen, aber ansonsten ist dagegen nichts einzuwenden. Stefan
"Peter J. Holzer" <hjp-usenet3@hjp.at> writes:
elems = [] for i in range(n): elems.append(str(i)) s = "".join(elems)
Wenn Du hier schon auf Optimierung achtest: wozu dann erst lie Liste? Join nimmt jedes Iterable: s = "".join(map(str, range(x))) Ist kürzer, prägnanter, performanter und deutlich effektiver im Speicher. Letztlich ist es (wie immer) stark von der Aufgabenstellung abhängig: häufig hat man (wenn man denn einen großen String bauen will) gar keine Strings als Ausgangsbasis, sondern etwas anderes. Und da macht es keinen Sinn, temporär Listen von Strings zu erzeugen, sondern man sollte lieber mit Iterables arbeiten, die man mappt. Ole
Оlе Ѕtrеісhеr schrieb am 17.09.2017 um 10:50:
"Peter J. Holzer" writes:
elems = [] for i in range(n): elems.append(str(i)) s = "".join(elems)
Wenn Du hier schon auf Optimierung achtest: wozu dann erst lie Liste? Join nimmt jedes Iterable:
s = "".join(map(str, range(x)))
Ist kürzer, prägnanter, performanter und deutlich effektiver im Speicher.
Bezüglich "effektiver im Speicher" möchte ich anregen, den Code zu lesen. Intern erzeugt "".join(genexpr) zuerst eine Liste. Das passiert immer, wenn keine Sequenz übergeben wird. Schließlich muss zweimal über diese drüber gelaufen werden können, was bei Iteratoren nicht gegeben ist. Ein Eckchen schneller wird map() hier wohl trotzdem sein, aber wir sollten nicht vergessen, dass so etwas wie das Beispiel oben in der Praxis nur *sehr* selten auftreten dürfte. Stefan
On 2017-09-17 08:50, Оlе Ѕtrеісhеr <ole-usenet-spam@gmx.net> wrote:
"Peter J. Holzer" <hjp-usenet3@hjp.at> writes:
elems = [] for i in range(n): elems.append(str(i)) s = "".join(elems)
Wenn Du hier schon auf Optimierung achtest: wozu dann erst lie Liste?
Der das einer der beiden Fälle war, die ich miteinander vergleichen wollte. Und natürlich ist das ein Test-Beispiel und nichts, was so in Produktivcode irgendwo vorkommt.
Join nimmt jedes Iterable:
s = "".join(map(str, range(x)))
Ist kürzer, prägnanter,
Ja, definitiv. Aber es ist eine Änderung, die für den Vergleich irrelevant ist und daher den Leser vom Kern des Arguments ablenken würde. Daher habe ich das unterlassen.
performanter
Bei diesem trivialen Test-Beispiel: Ja. Wenn die Schleife etwas mehr macht als nur "str" aufzurufen, dürfte der Performance-Unterschied zwischen map und einer expliziten Schleife eher vernachlässigbar sein.
und deutlich effektiver im Speicher.
Nein. Ob die Liste implizit von join aufgebaut wird oder explizit vom Programmcode, macht keinen Unterschied. hp -- _ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung: |_|_) | | Man feilt solange an seinen Text um, bis | | | hjp@hjp.at | die Satzbestandteile des Satzes nicht mehr __/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
On 2017-09-17 08:50, Оlе Ѕtrеісhеr <ole-usenet-spam@gmx.net> wrote:
"Peter J. Holzer" <hjp-usenet3@hjp.at> writes:
elems = [] for i in range(n): elems.append(str(i)) s = "".join(elems)
Wenn Du hier schon auf Optimierung achtest: wozu dann erst lie Liste?
Weil das einer der beiden Fälle war, die ich miteinander vergleichen wollte. Und natürlich ist das ein Test-Beispiel und nichts, was so in Produktivcode irgendwo vorkommt.
Join nimmt jedes Iterable:
s = "".join(map(str, range(x)))
Ist kürzer, prägnanter,
Ja, definitiv. Aber es ist eine Änderung, die für den Vergleich irrelevant ist und daher den Leser vom Kern des Arguments ablenken würde. Daher habe ich das unterlassen.
performanter
Bei diesem trivialen Test-Beispiel: Ja. Wenn die Schleife etwas mehr macht als nur "str" aufzurufen, dürfte der Performance-Unterschied zwischen map und einer expliziten Schleife eher vernachlässigbar sein.
und deutlich effektiver im Speicher.
Nein. Ob die Liste implizit von join aufgebaut wird oder explizit vom Programmcode, macht keinen Unterschied. hp -- _ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung: |_|_) | | Man feilt solange an seinen Text um, bis | | | hjp@hjp.at | die Satzbestandteile des Satzes nicht mehr __/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
participants (3)
-
ole-usenet-spam@gmx.net
-
Peter J. Holzer
-
Stefan Behnel