2010-04-14 14 views
8

Io uso il seguente codice per registrare una mappa, è veloce quando contiene solo zeri, ma non appena ci sono dati reali nella mappa diventa insopportabilmente lenta ... C'è un modo per fai questo più velocemente?La formattazione della stringa Python è troppo lenta

log_file = open('testfile', 'w') 
for i, x in ((i, start + i * interval) for i in range(length)): 
    log_file.write('%-5d %8.3f %13g %13g %13g %13g %13g %13g\n' % (i, x, 
     map[0][i], map[1][i], map[2][i], map[3][i], map[4][i], map[5][i])) 
+0

Quanto è grande il set di dati nella mappa? Potrebbe essere più facile comprimere (mappa [0], mappa [1], ecc ...), quindi eseguire il ciclo attraverso le tuple risultanti. –

+0

@Josh Non penso che questo sia il problema, come detto, è veloce quando è tutto zero (come ben meno di un secondo), ma insopportabilmente lento con il contenuto reale (come due minuti per una mappa di 600 lunghezze). – wich

+1

sei sicuro che sia dovuto alla formattazione delle stringhe e non alla scrittura? – Francesco

risposta

3

vi consiglio di eseguire il codice utilizzando il modulo cProfile e post elaborazione i risultati come descritto a http://docs.python.org/library/profile.html. Questo ti consente di sapere esattamente quanto tempo è trascorso nella chiamata a str.__mod__ per la formattazione delle stringhe e quanto viene speso facendo altre cose, come scrivere il file e fare le ricerche __getitem__ per map[0][i] e così via.

2

Prima ho controllato% contro il backquoting. % è più veloce. Allora ho controllato% (tupla) contro 'string'.format(). Un bug iniziale mi ha fatto pensare che fosse più veloce. Ma no. % è più veloce.

Quindi, stai già facendo la tua enorme pila di conversioni float-to-string nel modo più veloce in cui puoi farlo in Python.

Il codice demo riportato di seguito è un brutto codice demo. Per favore, non farmi una lezione su un raggio d'arancia contro un raggio o su un'altra pedanteria. KThxBye.

Il mio test ad-hoc e altamente non scientifico indica che (a)% (1.234,) operazioni su Python 2.5 su Linux è più veloce di% (1.234, ...) operazione su Python 2.6 su linux, per il codice di prova sotto , a condizione che il tentativo di usare 'string'.format() non funzioni su versioni python precedenti alla 2.6. E così via.

# this code should never be used in production. 
# should work on linux and windows now. 

import random 
import timeit 
import os 
import tempfile 


start = 0 
interval = 0.1 

amap = [] # list of lists 
tmap = [] # list of tuples 

def r(): 
    return random.random()*500 

for i in xrange(0,10000): 
     amap.append ([r(),r(),r(),r(),r(),r()]) 

for i in xrange(0,10000): 
     tmap.append ((r(),r(),r(),r(),r(),r())) 




def testme_percent(): 
    log_file = tempfile.TemporaryFile() 
    try: 
     for qmap in amap: 
      s = '%g %g %g %g %g %g \n' % (qmap[0], qmap[1], qmap[2], qmap[3], qmap[4], qmap[5]) 
      log_file.write(s) 
    finally: 
     log_file.close(); 

def testme_tuple_percent(): 
    log_file = tempfile.TemporaryFile() 
    try:  
     for qtup in tmap: 
      s = '%g %g %g %g %g %g \n' % qtup 
      log_file.write(s); 
    finally: 
     log_file.close(); 

def testme_backquotes_rule_yeah_baby(): 
    log_file = tempfile.TemporaryFile() 
    try: 
     for qmap in amap: 
      s = `qmap`+'\n' 
      log_file.write(s); 
    finally: 
     log_file.close();   

def testme_the_new_way_to_format(): 
    log_file = tempfile.TemporaryFile() 
    try: 
     for qmap in amap: 
      s = '{0} {1} {2} {3} {4} {5} \n'.format(qmap[0], qmap[1], qmap[2], qmap[3], qmap[4], qmap[5]) 
      log_file.write(s); 
    finally: 
     log_file.close(); 

# python 2.5 helper 
default_number = 50 
def _xtimeit(stmt="pass", timer=timeit.default_timer, 
      number=default_number): 
    """quick and dirty""" 
    if stmt<>"pass": 
     stmtcall = stmt+"()" 
     ssetup = "from __main__ import "+stmt 
    else: 
     stmtcall = stmt 
     ssetup = "pass" 
    t = timeit.Timer(stmtcall,setup=ssetup) 
    try: 
     return t.timeit(number) 
    except: 
     t.print_exc() 


# no formatting operation in testme2 

print "now timing variations on a theme" 

#times = [] 
#for i in range(0,10): 

n0 = _xtimeit("pass",number=50) 
print "pass = ",n0 

n1 = _xtimeit("testme_percent",number=50); 
print "old style % formatting=",n1 

n2 = _xtimeit("testme_tuple_percent",number=50); 
print "old style % formatting with tuples=",n2 

n3 = _xtimeit("testme_backquotes_rule_yeah_baby",number=50); 
print "backquotes=",n3 

n4 = _xtimeit("testme_the_new_way_to_format",number=50); 
print "new str.format conversion=",n4 


#  times.append(n); 




print "done"  

Penso che si potrebbe ottimizzare il codice per costruire i tuple di carri da qualche altra parte, ovunque avete costruito quella mappa, in primo luogo, costruire la vostra lista di tuple, e quindi applicando la tupla fmt_string% in questo modo:

for tup in mytups: 
    log_file.write(fmt_str % tup) 

Sono stato in grado di radere gli 8,7 secondi fino a 8,5 secondi facendo uscire la parte di tuple making-of dal ciclo for. Che non è molto Il grande ragazzo ha una formattazione a virgola mobile, che credo sarà sempre costosa.

Alternativa:

Hai considerato non scrivere queste enormi tronchi come testo, e, invece, il risparmio utilizzando il metodo più veloce "persistenza" a disposizione, e poi scrivere un breve programma di utilità per eseguire il dump in testo, in caso di necessità? Alcune persone usano NumPy con dataset numerici molto grandi, e non sembra che userebbero un dump line-by-line per archiviare le loro cose. Vedere:

http://thsant.blogspot.com/2007/11/saving-numpy-arrays-which-is-fastest.html

+0

Questo approccio elimina tutto ciò che accade insieme nel tempo, il che non è il modo più efficace per capire come il tempo varia con un cambiamento. In genere vuoi isolare solo le operazioni che stai guardando. –

+0

Il modulo 'tempfile' fornisce un modo affidabile per creare file temporanei senza introdurre interferenze non necessarie con i file esistenti o la dipendenza dalla piattaforma. Inoltre, 'se os.path.exists (...)' introduce una condizione di competizione non necessaria semplicemente chiamando 'os.remove' e rilevando l'errore ed è generalmente una forma peggiore a causa di esso. In genere anche la forma cattiva si basa su "chiudi" per essere chiamato manualmente; se in realtà vuoi essere sicuro che un file venga chiuso, usa un gestore di contesto ('con open (afilename1, 'w') come log_file:'). Il codice corrente non chiuderà il file se c'è un'eccezione sollevata. –

+0

L'uso di backtick per richiamare 'repr' è deprecato. È difficile da leggere e per alcune persone difficile da digitare. Tutte e tre le tue soluzioni fanno cose diverse, ma questa è particolarmente diversa dalle altre. –

0

Senza voler guadare nella ottimizzare-questo-codice di palude, avrei scritto il codice più simile a questo:

log_file = open('testfile', 'w') 
x = start 
map_iter = zip(range(length), map[0], map[1], map[2], map[3], map[4], map[5]) 
fmt = '%-5d %8.3f %13g %13g %13g %13g %13g %13g\n' 
for i, m0, m1, m2, m3, m4, m5 in mapiter: 
    s = fmt % (i, x, m0, m1, m2, m3, m4, m5) 
    log_file.write(s) 
    x += interval 

Ma voglio pesare con la raccomandazione che non il nome variabili dopo builtins pitone , come map.

Problemi correlati