【问题标题】:How to improve speed of this readline loop in python?如何提高python中这个readline循环的速度?
【发布时间】:2009-09-12 15:26:19
【问题描述】:

我正在将文本格式的 Databasedump 的几个部分导入 MySQL,问题是 在有趣的数据之前,前面有很多不有趣的东西。 我编写了这个循环来获取所需的数据:

def readloop(DBFILE):
    txtdb=open(DBFILE, 'r')

sline = ""

# loop till 1st "customernum:" is found
while sline.startswith("customernum:  ") is False: 
    sline = txtdb.readline()

while sline.startswith("customernum:  "):
    data = []
    data.append(sline)
    sline = txtdb.readline()
    while sline.startswith("customernum:  ") is False:
        data.append(sline)
        sline = txtdb.readline()
        if len(sline) == 0:
            break
    customernum = getitem(data, "customernum:  ")
    street = getitem(data, "street:  ")
    country = getitem(data, "country:  ")
    zip = getitem(data, "zip:  ")

文本文件非常大,所以循环到第一个想要的条目需要很长时间。任何人都知道这是否可以更快地完成(或者如果我修复这不是最好的主意)?

非常感谢!

【问题讨论】:

  • 请不要再写is False。请学会使用not

标签: python loops readline


【解决方案1】:

请不要写这段代码:

while condition is False:

布尔条件是 boolean 用于大声哭泣,因此可以直接测试(或否定和测试)它们:

while not condition:

您的第二个 while 循环没有写为“while 条件为真:”,我很好奇为什么您觉得需要在第一个循环中测试“为假”。

拉出 dis 模块,我想我会进一步剖析它。在我的 pyparsing 经验中,函数调用是性能杀手,所以如果可能的话最好避免函数调用。这是您的原始测试:

>>> test = lambda t : t.startswith('customernum') is False
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 LOAD_GLOBAL              1 (False)
             15 COMPARE_OP               8 (is)
             18 RETURN_VALUE

这里发生了两件昂贵的事情,CALL_FUNCTIONLOAD_GLOBAL。您可以通过为 False 定义一个本地名称来减少 LOAD_GLOBAL

>>> test = lambda t,False=False : t.startswith('customernum') is False
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 LOAD_FAST                1 (False)
             15 COMPARE_OP               8 (is)
             18 RETURN_VALUE

但是如果我们完全放弃'is'测试呢?:

>>> test = lambda t : not t.startswith('customernum')
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_ATTR                0 (startswith)
              6 LOAD_CONST               0 ('customernum')
              9 CALL_FUNCTION            1
             12 UNARY_NOT
             13 RETURN_VALUE

我们将LOAD_xxxCOMPARE_OP 合并为一个简单的UNARY_NOT。 “is False”当然对性能没有任何帮助。

现在,如果我们可以在不进行任何函数调用的情况下对一行进行一些粗略的消除会怎么样。如果该行的第一个字符不是'c',它就不可能以('customernum')开头。让我们试试吧:

>>> test = lambda t : t[0] != 'c' and not t.startswith('customernum')
>>> dis.dis(test)
  1           0 LOAD_FAST                0 (t)
              3 LOAD_CONST               0 (0)
              6 BINARY_SUBSCR
              7 LOAD_CONST               1 ('c')
             10 COMPARE_OP               3 (!=)
             13 JUMP_IF_FALSE           14 (to 30)
             16 POP_TOP
             17 LOAD_FAST                0 (t)
             20 LOAD_ATTR                0 (startswith)
             23 LOAD_CONST               2 ('customernum')
             26 CALL_FUNCTION            1
             29 UNARY_NOT
        >>   30 RETURN_VALUE

(请注意,使用 [0] 获取字符串的第一个字符并不会 创建切片 - 这实际上非常快。)

现在,假设没有大量以“c”开头的行,粗切过滤器可以使用所有相当快的指令来消除一行。事实上,通过测试 "t[0] != 'c'" 而不是 "not t[0] == 'c'" 我们为自己节省了一个无关的 UNARY_NOT 指令。

所以使用这个关于捷径优化的学习,我建议更改这段代码:

while sline.startswith("customernum:  ") is False:
    sline = txtdb.readline()

while sline.startswith("customernum:  "):
    ... do the rest of the customer data stuff...

到这里:

for sline in txtdb:
    if sline[0] == 'c' and \ 
       sline.startswith("customernum:  "):
        ... do the rest of the customer data stuff...

请注意,我还删除了 .readline() 函数调用,并使用“for sline in txtdb”对文件进行迭代。

我意识到 Alex 提供了完全不同的代码体来查找第一个“customernum”行,但我会尝试在算法的一般范围内进行优化,然后再拿出大而晦涩的块读取枪。

【讨论】:

    【解决方案2】:

    优化的一般思路是“按大块”(主要忽略行结构)定位第一条感兴趣的行,然后继续对其余的行进行逐行处理)。它有点挑剔和容易出错(一个接一个等),所以它确实需要测试,但总体思路如下......:

    import itertools
    
    def readloop(DBFILE):
      txtdb=open(DBFILE, 'r')
      tag = "customernum:  "
      BIGBLOCK = 1024 * 1024
      # locate first occurrence of tag at line-start
      # (assumes the VERY FIRST line doesn't start that way,
      # else you need a special-case and slight refactoring)
      blob = ''
      while True:
        blob = blob + txtdb.read(BIGBLOCK)
        if not blob:
          # tag not present at all -- warn about that, then
          return
        where = blob.find('\n' + tag)
        if where != -1:  # found it!
          blob = blob[where+1:] + txtdb.readline()
          break
        blob = blob[-len(tag):]
      # now make a by-line iterator over the part of interest
      thelines = itertools.chain(blob.splitlines(1), txtdb)
      sline = next(thelines, '')
      while sline.startswith(tag):
        data = []
        data.append(sline)
        sline = next(thelines, '')
        while not sline.startswith(tag):
          data.append(sline)
          sline = next(thelines, '')
          if not sline:
            break
        customernum = getitem(data, "customernum:  ")
        street = getitem(data, "street:  ")
        country = getitem(data, "country:  ")
        zip = getitem(data, "zip:  ")
    

    在这里,我已尝试尽可能多地保持您的结构完好无损,仅在此重构的“大创意”之外进行了微小的增强。

    【讨论】:

      【解决方案3】:

      我猜你正在编写这个导入脚本,在测试过程中等待很无聊,所以数据一直保持不变。

      您可以使用print txtdb.tell() 运行一次脚本来检测您要跳转到的文件中的实际位置。写下这些并将搜索代码替换为txtdb.seek( pos )。基本上这是为文件建立索引;-)

      另一种更传统的方式是以更大的块读取数据,一次几 MB,而不仅仅是一行中的几个字节。

      【讨论】:

      • 我希望,遗憾的是,顶部块不断从导入更改为导入,所以不能跳过它:-/谢谢你的想法':-)
      【解决方案4】:

      【讨论】:

      • readlines() 听起来很有趣,我会先检查该解决方案。非常感谢:-)
      【解决方案5】:

      告诉我们有关该文件的更多信息。

      您可以使用 file.seek 进行二分搜索吗?寻找到一半,阅读几行,确定你是在你需要的部分之前还是之后,递归。这会将您的 O(n) 搜索变成 O(logn)。

      【讨论】:

      • 该文件约为 7GB(今天)并且还在增长。它是纯ascii,我查找的关键字都以“:”结尾,但关键字的长度不同。在关键字之间是一些以“:”结尾的非有趣内容的行。
      • 你能从格式上看出你是找得太远还是不够远?将此算法转换为 logn 将产生巨大的性能优势
      猜你喜欢
      • 1970-01-01
      • 2017-10-06
      • 1970-01-01
      • 2019-08-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-21
      • 2014-01-20
      • 2019-01-28
      相关资源
      最近更新 更多