list comprehension

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

Mittwoch 7. März 2012, 00:13

Guten Abend,

im Web hatte ich den Eindruck gewonnen, das Pythons List Comprehension aus Performancesicht besser ist als andere Alternative (Aussnahme threads etc ...)
Bei diesem simplen Beispiel ist dies aber nicht der Fall.

Code: Alles auswählen

import timeit

def p1(s):
    j = [i*2 for i in s]

def p2(s):
    for i in s:
        j = i*2
    
t1 = timeit.Timer("p1(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])", "from __main__ import p1") 
print "p1: ", t1.timeit() 

t2 = timeit.Timer("p2(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])", "from __main__ import p2") 
print "p2: ", t2.timeit() 

print "p1: ", min(t1.repeat(100, 10000))     
print "p2: ", min(t2.repeat(100, 10000)) 
p1: 4.45716592472
p2: 3.11650950051
p1: 0.0429442848183
p2: 0.0300219720663

Frage: Worin besteht normalerweise der Performancegewinn bei List Comprehension oder was muss da gegeben sein, damit dies schneller ist?

Danke
Claudia
DasIch
User
Beiträge: 2500
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Mittwoch 7. März 2012, 00:28

In p1 wird eine Liste erzeugt in p2 nicht, theoretisch könnte der Interpreter p2 soweit optimieren dass sie konstante Laufzeit hat. Dementsprechend sind die Ergebnisse des Vergleichs uninteressant weil sie nicht mehr Aussagen als dass unterschiedliche Algorithmen unterschiedliche Laufzeiten haben.

Wenn p2 dass gleiche wie p1 tun würde nur eben ohne LC, wäre p1 wahrscheinlich langsamer weil die zusätzlichen Methodenaufrufe recht viel Zeit kosten.

Code: Alles auswählen

def p2(s):
    j = []
    for i in s:
        j.append(i * 2)
Ein weitere Vorteil von LCs ist, dass der Interpreter Allokationen weg optimieren kann wenn es möglich ist die Länge der Liste im voraus zu bestimmten.
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

Mittwoch 7. März 2012, 00:42

@DasIch
da hast du natürlich Recht - fehlerhaften Code vergleichen macht wenig Sinn.
Wenn p2 dann geändert wurde, ist p1 schneller.

ok - heisst also dann, LC ist schneller wenn es darum geht eine Liste zu bearbeiten und in eine andere Liste zu übergeben, korrekt?

Danke
Claudia
deets

Mittwoch 7. März 2012, 01:01

DasIch hat geschrieben: Ein weitere Vorteil von LCs ist, dass der Interpreter Allokationen weg optimieren kann wenn es möglich ist die Länge der Liste im voraus zu bestimmten.
Ist das eine faktische Optimierung in CPython?
DasIch
User
Beiträge: 2500
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Mittwoch 7. März 2012, 01:31

myxin hat geschrieben:ok - heisst also dann, LC ist schneller wenn es darum geht eine Liste zu bearbeiten und in eine andere Liste zu übergeben, korrekt?
LCs sind eigentlich immer schneller wenn du eine Liste erzeugst.
deets hat geschrieben:Ist das eine faktische Optimierung in CPython?
(Noch) nicht, es gibt ein Issue sowie einen Patch der die Optimierung implementiert. PyPy hat die Optimierung in einem Branch.
BlackJack

Mittwoch 7. März 2012, 10:34

@myxin: Wenn Du LCs einsetzt weil sie schneller sind, dann ist Python vielleicht nicht die richtige Sprache für Dich. LCs verwendet man wenn man eine Liste aus einem iterierbaren Objekt erstellen möchte in dem man mit jedem Element etwas anstellt und eventuell welche aussortieren möchte, und die kompakte Schreibweise an der Stelle lesbarer ist. Nicht um etwas schneller zu machen. Das ist ein netter Nebeneffekt, sollte aber in der Regel nicht die Hauptmotivation dafür sein LCs einzusetzen.
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

Mittwoch 7. März 2012, 19:23

@BlackJack, nein, Performance war nicht hautpsächlich der Grund der Frage.

Primär wollte ich verstehen, warum dies so ist,
im 2ten Schritt will ich aber auch effizienten Code schreiben können.

Das heisst natürlich auch, dass dies letztendlich immer von Fall zu Fall entschieden werden muss
und es daher wichtig ist, zu verstehen warum etwas so ist.

Vielen Dank für die Hilfen
Claudia
BlackJack

Mittwoch 7. März 2012, 20:52

Hauptsächlich ist das schneller weil bei einer LC implizit ist, dass an eine Liste angehängt wird, während die Alternative ohne LC bei jedem Schleifendurchlauf das ``append``-Attribut von dem Ergebnis-Objekt abfragen muss. Das sieht man zum Beispiel wenn man sich von CPython den Bytecode disassemblieren lässt:

Code: Alles auswählen

In [195]: def f1(it):
   .....:     r = []
   .....:     for x in it:
   .....:         r.append(x * 2)
   .....:     return r
   .....: 

In [196]: def f2(it):
   .....:     return [x * 2 for x in it]
   .....: 

In [197]: import dis

In [198]: dis.dis(f1)
  2           0 BUILD_LIST               0
              3 STORE_FAST               1 (r)

  3           6 SETUP_LOOP              31 (to 40)
              9 LOAD_FAST                0 (it)
             12 GET_ITER            
        >>   13 FOR_ITER                23 (to 39)
             16 STORE_FAST               2 (x)

  4          19 LOAD_FAST                1 (r)
             22 LOAD_ATTR                0 (append)
             25 LOAD_FAST                2 (x)
             28 LOAD_CONST               1 (2)
             31 BINARY_MULTIPLY     
             32 CALL_FUNCTION            1
             35 POP_TOP             
             36 JUMP_ABSOLUTE           13
        >>   39 POP_BLOCK           

  5     >>   40 LOAD_FAST                1 (r)
             43 RETURN_VALUE        

In [199]: dis.dis(f2)
  2           0 BUILD_LIST               0
              3 DUP_TOP             
              4 STORE_FAST               1 (_[1])
              7 LOAD_FAST                0 (it)
             10 GET_ITER            
        >>   11 FOR_ITER                17 (to 31)
             14 STORE_FAST               2 (x)
             17 LOAD_FAST                1 (_[1])
             20 LOAD_FAST                2 (x)
             23 LOAD_CONST               1 (2)
             26 BINARY_MULTIPLY     
             27 LIST_APPEND         
             28 JUMP_ABSOLUTE           11
        >>   31 DELETE_FAST              1 (_[1])
             34 RETURN_VALUE
myxin
User
Beiträge: 47
Registriert: Dienstag 10. Januar 2012, 16:57

Freitag 9. März 2012, 19:20

@BlackJack
vielen Dank für den Tipp mit import dis, kannte ich auch noch nicht.
Leider verstehe ich nicht, was der Auszug bedeutet.
Muss mich dazu mal einlesen und ggf. nachfragen :wink:

Gruß
Claudia
EyDu
User
Beiträge: 4872
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Freitag 9. März 2012, 20:19

Das dis-Modul musst du nicht unbedingt versehen. BlackJack hat lediglich demonstriert, in welchen Bytecode welche Funktionen umgewandelt werden. Praktisch ist das für dich sicher nicht unbedingt relevant.
Das Leben ist wie ein Tennisball.
Antworten