string interpolation VS. string addition

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Donnerstag 11. Januar 2007, 16:03

EDIT: Fehler überarbeitet! Die Interpolationsvariant ist die schneller von beiden!


Vorsetzung vom Thread: http://www.python-forum.de/topic-8713.html

Falls der Thread im Falschen Sub-Forum ist, dann bitte verschieben.

``s = x[0] + x[1] + ...`` VS. ``s = '%s%s... % (x[0], x[1], ...`` - Geschwindigkeitsmessung

Folgendes Script erzeugt ``output.py``das bei `MAX_OBJ=3` so aussieht. Es hat einmal die ``list`` `obj_list` mit 3 Elementen vom Typ ``str``, bei dem jedes Element genau 2890 Chars lang ist. Weiterhin werden die Funktionen ``f1`` und ``f2`` generiert, dass die Elemente von ``obj_list`` unterschiedlich zurückgibt.


Diese Script...

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time


from mylib.benchmark import Benchmark

t1 = time.clock()
print "Load ``output.py``",
from output import f1, f2
t2 = time.clock() - t1
print "OK (%.2fsec.)" % t2

print "Start Benchmark\n"
bm = Benchmark()

bm.add_function(f1, "return '%s%s... % (x[0], x[1], ...")
bm.add_function(f2, "return x[0] + x[1] + ...          ")
bm.run()
bm.print_ranking()

...lädt ``output.py`` und macht einen Benchmark zwischen ``f1`` und ``f2`` um zu sehen welche Variante schneller ist. Das benötigte Modul ``benchmark`` gibt es hier. Den thread dazu ist findet ihr hier.


Dan nun der Ablauf geklärt ist, hier ein par Benchmarks:

Mit 1000 Elementen (`MAX_OBJ=1000`):

Code: Alles auswählen

Load ``output.py`` OK (0.00sec.)
Start Benchmark

[Die Laufzeit wird ermittelt]
run:      f1 - return '%s%s... % (x[0], x[1], ... OK (1.81sec.)
run:      f2 - return x[0] + x[1] + ...           OK (1.82sec.)

[Ranking wird erzeugt]
Ranking wurde erzeugt.
-------------------------------------------------------------------------------
Funktion: f2 - return x[0] + x[1] + ...           ist am langsamsten; Zeit: 1.819344 sec
Funktion: f1 - return '%s%s... % (x[0], x[1], ... ist um 0.70% schneller; Zeit: 1.806714 sec
-------------------------------------------------------------------------------
Wie man erkenne kann kaum ein Unterschied. Bei mehreren durchläufen ist mal f1 und mal f2 ein wenig schneller ;) Nebenbei sie angemerkt, dass bei ersten ausführen des Scriptes, das laden von ``output.py`` mit 1000 Elementen ungefähr 2.05 Sekunden dauerte. Nach dem daraus eine ``output.pyc`` erzeugt wurde, beim ersten start, beträgt die Ladedauer ~0.0 Sekunden.

Mit 10.000 Elementen (`MAX_OBJ=10000`):
Jetzt wollen wir mal Python zum schwitzen bringen. Achtet mal darauf wie lange es dauert ``output.py`` beim ersten Start zu laden.

Code: Alles auswählen

Load ``output.py`` OK (186.24sec.)
Start Benchmark

[Die Laufzeit wird ermittelt]
run:      f1 - return '%s%s... % (x[0], x[1], ... OK (111.80sec.)
run:      f2 - return x[0] + x[1] + ...           OK (183.74sec.)

[Ranking wird erzeugt]
Ranking wurde erzeugt.
-------------------------------------------------------------------------------
Funktion: f2 - return x[0] + x[1] + ...           ist am langsamsten; Zeit: 183.744273 sec
Funktion: f1 - return '%s%s... % (x[0], x[1], ... ist um 64.35% schneller; Zeit: 111.797196 sec
-------------------------------------------------------------------------------
Das laden des unkompilierten ``output.py`` dauerte ~3 Minuten. ``f1`` ~1,85 Minuten und ``f2`` ~3 Minuten. Somit hat sich meine anfängliche Vermutung dass die Interpolationsvariante ("" % ()) schneller ist doch nun als richtig erwiesen :) ~65% ist schon viel und mehr als ich vermutet habe :)


Zweiter durchlaufe, nach dem die ``output.py`` in ``output.pyc`` kompiliert wurde:

Code: Alles auswählen

Load ``output.py`` OK (0.01sec.)
Start Benchmark

[Die Laufzeit wird ermittelt]
run:      f1 - return '%s%s... % (x[0], x[1], ... OK (111.45sec.)
run:      f2 - return x[0] + x[1] + ...           OK (184.55sec.)

[Ranking wird erzeugt]
Ranking wurde erzeugt.
-------------------------------------------------------------------------------
Funktion: f2 - return x[0] + x[1] + ...           ist am langsamsten; Zeit: 184.550015 sec
Funktion: f1 - return '%s%s... % (x[0], x[1], ... ist um 65.58% schneller; Zeit: 111.454085 sec
-------------------------------------------------------------------------------
:)

...

Kleine Erklärung zu der Startdauer von ``output.py`` bei 10.000 Elementen in ``obj_list``, damit auch die letzten zweifler befridigt sind und nicht denken das meine Scripte "Kaputt sind":
Erstmal bedenkt das jeder string in ``obj_lis`` 2890 Zeichen hat (jeder stinrg ist dabei identisch!)! Dan kommt hinzu das 10.000 strings in ``obj_list`` sind ;) Das ergibt 2890000 Zeichen, was eine menge ist. Python kompiliert alle Module bei ersten Start immer in ``.pyc``. Dabei werden gleiche Strings (Also mit gleichen Inhalt) erfasst und alle Objekte die auf den gleiche string "Zeigen" werden auf eben diesen referenziert um Speicherplatz zu Sparen :) Die ``output.py`` bei 10.000 Elementen ist 27.9MB groß. So eine große Datei zu parsern und dabei alle Mengen der Relation, siehe hier, in eine Menge zusammenzufassen dauert seine Zeit. Also ingrunde werden redundante Mengen zu einer Menge zusammengefast:
LALR(k)-Parser hat geschrieben:Im einfachen Worten bedeutet das, dass im zuvor berechneten LR(1)-Automaten Zustände zusammengeführt werden, deren Kern identisch ist. Der Kern zweier Zustände ist identisch, falls die Items der beiden Zustände bis auf die Follow-Mengen (Lookaheads) identisch sind.
. Ich hoffe ich interpretiere das richtig als nicht Informatiker. Nach dem das fertig ist, ist die output.pyc nur noch 296KB groß, weil alle redundanten strings beseitigt sind und alle 10.000 Elemente von ``obj_list``nur noch auf den einen String zeigen! Damit sollte geklärt sein warum der zweite start nach dem kompilieren nur noch 0.01sec statt 3 Minuten dauert ;)


BTW: Nochmals Sorry das mir dieser peinliche Fehler mit den vertauschten Labels im Benchmarkscript passiert ist.

BTW2: Ich habe alle Testst noch mit deaktivierten GC gemacht und es war kein unterschied in der Geschwindigkeit auszumachen. Schön ;)

P.S.: Als PC kam ein AMD A64 X2 3800 (DualCore) @ 2GHz pro Core zum Einsatz, wobei nur ein Core genutzt wurde, da Python noch nicht SMP Unterstützung bietet.
Zuletzt geändert von sape am Freitag 12. Januar 2007, 04:20, insgesamt 1-mal geändert.
rafael
User
Beiträge: 189
Registriert: Mittwoch 26. Juli 2006, 16:13

Donnerstag 11. Januar 2007, 17:40

Anmerkung: Diese Script ladet -> lädt ;)

Das Ergebnis überrascht mich, da in meiner Python Referenz von GE-PACKT steht, dass der Plus-Operator ziemlich viel Rechenzeit auf sich nimmt und man da besser join() nehmen würde.
BlackJack

Donnerstag 11. Januar 2007, 17:58

Der Argumentation kann ich nicht folgen. Weil das Buch sagt, dass ``+`` langsamer ist als `join()` darf es nichts geben was noch langsamer ist!?
rafael
User
Beiträge: 189
Registriert: Mittwoch 26. Juli 2006, 16:13

Donnerstag 11. Januar 2007, 18:06

Ups, hab ich wohl was vergessen :D

Aber dort steht auch
"Verwenden Sie den Formatierungsoperator % anstelle des Operators +, um einen String aus Einzelheiten zusammenzusetzen.
Ungünstig ist z.B.

Code: Alles auswählen

webseite = "<html>" + head + body + "</html>
Schneller ist:

Code: Alles auswählen

webseite = "<html%s%s</html>" % (head, body)
"
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Donnerstag 11. Januar 2007, 18:08

rafael hat geschrieben:[...]dass der Plus-Operator ziemlich viel Rechenzeit auf sich nimmt und man da besser join() nehmen würde.
Ja in schleifen ist das tatsächlich so (Hab noch nicht gemessen aber es wird so gesagt).

Die Variante ist langsamer...

Code: Alles auswählen

foo = ['1', '2', '3'] # ...
bar = ''
for elem in foo:
    bar += elem
...als diese.

Code: Alles auswählen

foo = ['1', '2', '3'] # ...
bar = list()
for elem in foo:
    bar.append(elem)
bar = "".join(bar)
...

BTW: Die Ergebnisse vom Benchmark sollen nicht dazu verleiten das man sich jetzt für die "Addition"-Variante entscheidet ;) Wie man sieht, macht sich das erste bei ungefähr 10.000 Elementen bemerkbar. Keiner würde in der Praxis auf die Idee kommen eine Konkatenation mit 10.000 Elemente zu machen (Nichtmal mit 50 ^^).

Außerdem sollte man auch den Post von Leonidas im anderen Thread beachten, wo er schrieb das man sich immer für die sauberste und übersichtlichste Lösung entscheiden sollte und nicht für eine die am schnellsten ist. (Ausnahme ist bei schleifen. Da sollte man lieber die ``"".join`` Variante nehmen, wie BlackJack schon im Post vom vorigen Thread meinte.)

lg

EDIT: Links zu den Post hinzugefügt.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Donnerstag 11. Januar 2007, 22:28

BlackJack hat geschrieben:Der Argumentation kann ich nicht folgen. Weil das Buch sagt, dass ``+`` langsamer ist als `join()` darf es nichts geben was noch langsamer ist!?
Stimmt, dass schließt nicht aus das es was langsameres gibt.

Folgende ist aber nach wie vor interessant:
Whenever joining more than two strings, use string interpolation, not addition:

Code: Alles auswählen

s = x + y + z # Bad.
s = '%s%s%s' % (x, y, z) # Good.
s = ''.join([x, y, z]) # Best, but not as general.
This has to do with efficiency; the intermediate string x+y is made (and thus copied) before x+y+z is made, so it's less efficient. People who use string concatenation in a for loop will be swiftly kicked in the head.
http://supybot.com/documentation/help/t ... king/style
http://supybot.com/

Da scheint sich der Autor geirrt zu haben bzw. könnt das ``s = x + y + z`` VS. ``s = '%s%s%s' % (x, y, z)`` nicht von ihm nicht nur in beug auf Performacnce gemeint sein, sondern auch Speicherverbrauch. Er redet da von Effizienz. meinte er damit aber die Geschwindigkeit und/oder Speicherverbrauch? Bei der Interpolationvariante muss mindestens auch temporär was Zwischengespeichert werden wie bei der "Additionsvariante". Ob für die "Additiosvariante" mehrere temporäre Speicherpunkte benutzt werden? Ich denke nicht.

Keine Ahnung. Morgen weiß ich eventuell mehr.

lg und ne gn8
sape
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Donnerstag 11. Januar 2007, 22:33

rafael hat geschrieben: Aber dort steht auch
"Verwenden Sie den Formatierungsoperator % anstelle des Operators +, um einen String aus Einzelheiten zusammenzusetzen.
Ungünstig ist z.B.

Code: Alles auswählen

webseite = "<html>" + head + body + "</html>
Schneller ist:

Code: Alles auswählen

webseite = "<html%s%s</html>" % (head, body)
"
Naja, das dachte ich eigentlich auch. Aber ich hallte es nun für ein unhaltbares gerücht. Selbst bei 1000 Objekten die mit ``+`` zusammengefügt und ``%`` ist keine wirklich schneller. Hab mit 1000 Objekten ca. 10 Runs gemacht und der unterscheid betrug immer ca. 0.01sec biw 0.03sec. Mysteriös wird es aber wie man sieht bei 10.000 :? Keine Ahnung warum das so ist. Aber egal, das ist ehe weit entfernt jeder Praxis.

lg
Python 47
User
Beiträge: 574
Registriert: Samstag 17. September 2005, 21:04

Donnerstag 11. Januar 2007, 23:11

Gut das du dir die Mühe machst, das alles zu überprüfen, aber ich verstehe nicht, was dich an der Sache so fasziniert, mind. 10 Posts(In diesen und im anderen Thread) zu posten. Kannst du mir das evt. erklären?
mfg

Thomas :-)
cracki
User
Beiträge: 72
Registriert: Montag 25. Dezember 2006, 05:01

Donnerstag 11. Januar 2007, 23:29

edit: @sape, mich nervts auch dass du ueberall mehrere posts hintereinander schreibst. benutz die editfunktion. dafuer ist sie da.

sape, leider kann ich deine scripts nicht sehen, weil pocoo gerade kaputt ist.

von dem was die anderen sagen kann ich aber doch ableiten, dass deine benchmarks sehr kaputt sind. ein script das 3 minuten zum laden braucht, das kann nur kaputt sein.

dazu kommt, dass du mit python nicht die noetige erfahrung hast. du wuesstest also garnicht, wie du zu benchmarkenden code so schreibst, dass du auch das messen kannst was du willst.

wenn die scripte mal verfuegbar sind, guck ich sie mir nochmal an und geb meinen senf dazu...
Zuletzt geändert von cracki am Donnerstag 11. Januar 2007, 23:35, insgesamt 1-mal geändert.
...meh...
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Donnerstag 11. Januar 2007, 23:32

cracki hat geschrieben:sape, leider kann ich deine scripts nicht sehen, weil pocoo gerade kaputt ist.
Hu?
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Donnerstag 11. Januar 2007, 23:56

birkenfeld hat geschrieben:
cracki hat geschrieben:sape, leider kann ich deine scripts nicht sehen, weil pocoo gerade kaputt ist.
Hu?
Er meint eigentlich LodgeIt. So gibt dieser Link eine ViewDoesNotExist-Exception:

Code: Alles auswählen

ViewDoesNotExist: Tried pastebin.static.views.error500. Error was: 'tuple' object has no attribute 'lower'
      args = ("Tried pastebin.static.views.error500. Error was: 'tuple' object has no attribute 'lower'",)
My god, it's full of CARs! | Leonidasvoice vs Modvoice
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Freitag 12. Januar 2007, 03:01

Python 47 hat geschrieben:Gut das du dir die Mühe machst, das alles zu überprüfen, aber ich verstehe nicht, was dich an der Sache so fasziniert, mind. 10 Posts(In diesen und im anderen Thread) zu posten. Kannst du mir das evt. erklären?
Was mich an der Sache interessiert ist schnell erklärt.
1. Erstmal bin ich generell ein Fan von Benchmarks (CPU, RAM, Graka, etc).
2. Zweitens Interessiert mich generell wieviel Geschwindigkeit und benötigten Speichermenge eine Sachen X in Python benötigt.
3. Zweitens ist für mich insofern relevant, damit ich nachehr für einen Testinterpreter (Geschrieben in Python. Als LALR-Parser kommt Pyparsing zum Einsatz, das relatiev Simple ist.), die effizientesten Bytecode nutze um so wenig Speicher wie nötig brauche und auch die schnellste Variante nutze. psyco wird selbstverständlich auch für einen Geschwindigkeitsbost vom Lexerteil der mit Pyparsing implementiert ist genutzt und für die Interpretereinheit.
cracki hat geschrieben:edit: @sape, mich nervts auch dass du ueberall mehrere posts hintereinander schreibst. benutz die editfunktion. dafuer ist sie da.
Komisch, bisher hat mich kein Moderator oder Administrator darauf angesprochen. Davon abgesehen ist es in diesem Forum gestatte Doppel, Dreifach, etc Posts zu machen und nicht wie in anderen Foren. Davon machen auch die Mods und Admins gebrauch.
cracki hat geschrieben: von dem was die anderen sagen kann ich aber doch ableiten, dass deine benchmarks sehr kaputt sind. ein script das 3 minuten zum laden braucht, das kann nur kaputt sein.
Warum machst du dir nicht die mühe und leist dir meine Post durch? Es wird eine Liste erzeugt mit 10.000 (In Worten Zehntausend) Elementen von dem JEDES Element einen string hat der 3000 Zeichen enthält. Nun frage dich mal selber wie lange das dauert im nicht kompilierten Zustand! Dan wird jedes Element in ``+`` und in ``%s%s... %(...`` verfahren returned. Das alles ist auch dem Post zu entnehmen aber nur wenn man sich die Mühe macht den Post zu Lesen und ihn auch zu verstehen, da er sehr Komprimiert ist und ich auch davon ausgehe das man die Scripts und deren Vorgang versteht (Und die Basics in Python).

Also nochmal als Pseudocode:

Code: Alles auswählen

obj_list = [# Ein String mit 3000 Zeichen der erste#, #... er Zweite#, ...,
                #.... der 9999ste#]
def f1():
   return obj_list[0] + obj_list[1] + obj_list[2] + ... + obj_list[9999]

def f2():
    .... # das gleiche bolss als Interpolation -> ``%s%s... % (...``
Nun sollte es klarer sein. Und glaub mir, eine ``list`` mit 10.000 Elementen wo jedes Ellement 3000 Zeichen enthält braucht in nicht kompilierten Zustand ca. 3 Minuten für das Laden ;)
cracki hat geschrieben: dazu kommt, dass du mit python nicht die noetige erfahrung hast. du wuesstest also garnicht, wie du zu benchmarkenden code so schreibst, dass du auch das messen kannst was du willst.
Ich glaube das kannst Du nicht beurteilen, wie viel Erfahrung ich habe.
cracki hat geschrieben: wenn die scripte mal verfuegbar sind, guck ich sie mir nochmal an und geb meinen senf dazu...
Ja, das solltest du wirklich bevor du mich unberechtigterweise der Unwissenheit bezichtigst. BTW: Ich habe kein Problem damit wenn mir jemand sagt das die Ergebnisse Käse sind (im gegenteil wenn was Falsch ist, möchte ich sogar darauf hingewiesen werden, damit ich was draus Lerne und es nächstes mal besser mache.). Aber wenn man sowas macht, dann sollte man sich die Mühe machen erstmal die Ergebnisse zu Analysieren (Post richtig lesen, das gelesen Verstehen, selber Tests aufstellen, etc..) und dann Argumente vorbringen und __nicht__ solche Sachen wie "Du hast ehe keine Erfahrung => Du bist ein N00b" ;)


Davon mal abgesehen: Die mühe mache ich mir sowieso. Und wenn ich das schon mache, kann ich ja auch meine Ergebnisse Postenund andere daran teilhaben lassen. Eventuell ergibt sich sogar das es wirklich brauchbar ist oder eben das es vollkommen Falsch ist. Dann kann man überlegen wie man das am besten anders macht (Wie zum Beispiel der Vorschlag von Y0GI)

Wenn es jemanden stört, kann er ja diesen Thread ignorieren. Falls es einen Administrator stört (was ich sehr bezweifle) dann kann er den Thread gerne schließen. Damit habe ich kein Problem und respektiere die Entscheidung.

Danke fürs zuhören, falls du dir den Post nicht so durchgelesen hast wie den ersten ;)

lg

BTW: Der Grund warum ich einen neuen Thread aufgemacht habe ist, das der andere angefangen hat langsam zu Blockieren (phpBB sag ich da nur). Die Post Vorschau und das abschicken eines Postes hat zu Lange gedauert.
Zuletzt geändert von sape am Freitag 12. Januar 2007, 04:27, insgesamt 1-mal geändert.
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Freitag 12. Januar 2007, 03:41

Scheiße! Ich habe die Labels der Funktionen im Benchmark vertauscht gehabt! :oops: Mist!...

Die Interpolationsvariante ist doch schneller als die "Additionsvariante"!

Sorry, ich hoffe es nimmt mir keiner übel :( Werde den Post jetzt schnell überarbeiten und das geänderte Script neu uploaden.

EDIT: Fertig :) Habe alle Benchmarks noch mal neu gemacht und den ersten Post editiert. Noch mal Sorry mit den Vertauschten Labels. BTW: Kein unterschied mit oder ohne GC ;)

lg

EDIT:
Was ist eigentlich mit LodgeIt los :? Ging doch gestern noch alles ohne Probleme.
cracki
User
Beiträge: 72
Registriert: Montag 25. Dezember 2006, 05:01

Freitag 12. Januar 2007, 13:59

sape:

du erzeugst eine riesige source datei anstatt die strings zur laufzeit zu erzeugen. klar brauchts laden da unmengen von zeit.

deine benchmarks beruhen auf monstroesen formatstrings. wie waers wenn du kleine formatstrings nimmst, dafuer aber mehrere tausend iterationen misst und dann die zeiten vergleichst?
sape hat geschrieben:
cracki hat geschrieben:dazu kommt, dass du mit python nicht die noetige erfahrung hast. du wuesstest also garnicht, wie du zu benchmarkenden code so schreibst, dass du auch das messen kannst was du willst.
Ich glaube das kannst Du nicht beurteilen, wie viel Erfahrung ich habe.
lieg ich denn sooo falsch mit meiner vermutung?
...meh...
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

Freitag 12. Januar 2007, 14:31

Leonidas hat geschrieben:
birkenfeld hat geschrieben:
cracki hat geschrieben:sape, leider kann ich deine scripts nicht sehen, weil pocoo gerade kaputt ist.
Hu?
Er meint eigentlich LodgeIt. So gibt dieser Link eine ViewDoesNotExist-Exception:
fixed
TUFKAB – the user formerly known as blackbird
Antworten