Python 字符串格式化太慢
我用下面的代码来记录一个地图(map),当地图里只有零的时候,速度很快,但一旦里面有了实际的数据,速度就变得非常慢……有没有什么办法可以让这个过程更快一些呢?
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]))
3 个回答
我不想深入讨论如何优化这段代码,但如果是我写的话,我会这样写:
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
不过我想提醒一下,最好不要用Python自带的名字来命名变量,比如说 map
。
首先,我对比了使用 % 和反引号的速度。结果发现 % 更快。接着,我又对比了 %(元组)和 'string'.format() 的速度。最开始我以为后者更快,但其实不是,% 还是更快。
所以,你现在在 Python 中进行大量的浮点数转字符串的操作,已经是最快的方法了。
下面的示例代码写得很糟糕,请不要给我讲 xrange 和 range 的区别,或者其他那些小细节。谢谢,再见。
我的非正式测试显示,在 Linux 上使用 Python 2.5 的 %(1.234,)操作比在 Linux 上使用 Python 2.6 的 %(1.234,...)操作要快,前提是 'string'.format() 在 2.6 之前的版本是无法使用的。还有其他类似的情况。
# 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"
我认为你可以通过在其他地方构建浮点数的元组来优化你的代码,比如在你创建那个映射的时候,先构建好元组列表,然后用 fmt_string % tuple 这种方式来应用。
for tup in mytups:
log_file.write( fmt_str % tup )
我通过把创建元组的部分从循环中移出去,把 8.7 秒的时间缩短到了 8.5 秒。虽然这变化不大,但主要的耗时还是在浮点数格式化上,我觉得这部分总是比较耗费时间。
另外一个建议:
你有没有考虑过不把这么大的日志直接写成文本,而是使用最快的“持久化”方法来保存它们,然后在需要的时候再写一个小工具把它们转成文本?有些人使用 NumPy 来处理非常大的数字数据集,似乎并不会逐行存储他们的数据。可以参考一下:
http://thsant.blogspot.com/2007/11/saving-numpy-arrays-which-is-fastest.html
我建议你使用 cProfile
模块来运行你的代码,然后按照 这个链接 上的说明处理结果。这样你就能清楚地知道在字符串格式化时,调用 str.__mod__
花费了多少时间,以及其他操作,比如写文件和查找 map[0][i]
等,花费了多少时间。