【问题标题】:Python string formatting too slowPython字符串格式化太慢
【发布时间】:2010-04-14 13:03:41
【问题描述】:

我使用下面的代码来记录一个地图,当它只包含零时它很快,但是一旦地图中有实际数据,它就会变得慢得令人难以忍受......有什么办法可以更快地做到这一点?

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]))

【问题讨论】:

  • 地图中的数据集有多大?压缩(map[0]、map[1] 等)可能更容易,然后遍历生成的元组。
  • 你确定是字符串格式化导致的而不是写吗?
  • @wich,当我测试使用零与使用随机生成的浮点数并进行这种格式化时,我得到了大约 25% 的差异。很难想象有这样的数字会使格式化操作的费用增加一百多倍。您肯定认为格式化步骤会给您带来麻烦吗?您是否进行了分析,如果可以,您能否向我们展示代码以及 pstats 分析,表明这是您的问题所在?
  • @Mike 是的,我确信这需要很长时间,但进一步检查似乎是数据结构而不是格式的问题。不幸的是,数据结构不是我的……是的,问题出在格式化步骤中,但看起来出在地图元素的读取上。
  • 当 map 是 Python 内置函数时,为什么要使用一个名为 map 的变量(看起来)?

标签: python string formatting


【解决方案1】:

我建议您使用cProfile 模块运行您的代码,并按照http://docs.python.org/library/profile.html 中的说明对结果进行后处理。这将让您确切知道在调用str.__mod__ 以进行字符串格式化以及在其他事情上花费了多少时间,例如编写文件和为map[0][i] 进行__getitem__ 查找等。

【讨论】:

    【解决方案2】:

    首先我检查了 % 与反引号。 % 是比较快的。然后我检查了%(元组)与'string'.format()。一个最初的错误让我觉得它更快。但不是。 % 更快。

    因此,您已经以 Python 中最快的方式进行了大量的浮点到字符串转换。

    下面的演示代码是丑陋的演示代码。请不要教我 xrange 与 range 或其他迂腐。 KThxBye。

    我的临时且高度不科学的测试表明,Linux 上 Python 2.5 上的 (a) % (1.234,) 操作比 linux 上 Python 2.6 上的 % (1.234,...) 操作快,对于下面的测试代码,附带条件是尝试使用 'string'.format() 在 2.6 之前的 python 版本上不起作用。以此类推。

    # 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 % 元组:

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

    通过从 for 循环中删除 make-a-tuple 部分,我能够将 8.7 秒缩短到 8.5 秒。这并不多。大男孩有浮点格式,我相信它总是会很昂贵。

    替代方案:

    您是否考虑过不要将如此庞大的日志作为文本编写,而是使用可用的最快“持久性”方法保存它们,然后在需要时编写一个简短的实用程序将它们转储为文本?有些人使用 NumPy 处理非常大的数字数据集,他们似乎不会使用逐行转储来存储他们的东西。见:

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

    【讨论】:

    • 这种方法将所有正在发生的事情都归结为时间,这并不是了解时间如何随变化而变化的最有效方法。您通常只想隔离您正在查看的操作。
    • tempfile 模块提供了一种可靠的方法来制作临时文件,而不会对现有文件或平台依赖性造成不必要的干扰。此外,if os.path.exists(...) 引入了一个不必要的竞争条件,而不是仅仅调用 os.remove 并捕获错误,因此通常是更糟糕的形式。同样典型的错误形式是依靠手动调用close;如果您确实想确保关闭文件,请使用上下文管理器 (with open(afilename1, 'w') as log_file:)。如果出现异常,当前代码不会关闭文件。
    • 不推荐使用反引号调用repr。它很难阅读,对某些人来说很难打字。您的所有三个解决方案都做不同的事情,但这个与其他解决方案特别不同。
    • 这只是我五秒钟内的骇客。任何人都不应使用此代码。事实上,它根本无法在 Unix 上运行。 :-)
    • 您关于如何为各种选项计时的理由与基准测试和性能测试中使用的典型标准以及——实际上——一般的实验科学相悖。你不要试图看大局来精确地了解一个细节;你试图尽可能地隔离这个细节。在这种情况下,您将所有内容混为一谈,结果您错误地将两项任务所花费的时间混为一谈,其中一项您声称是时间安排,另一项也因您的选择而异,并且是造成瓶颈的罪魁祸首。
    【解决方案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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-28
      • 2011-07-28
      • 1970-01-01
      • 1970-01-01
      • 2015-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多