有没有更快的方法在python中执行while循环?

时间:2014-04-05 01:19:49

标签: python

我花了一个小时的时间仔细研究我的代码,找出为什么在重写了一堆之后它就会陷入困境。虽然循环太慢,但我没有想到。我想我从未在时间非常关键的地方使用它们。

我最终将问题缩小到这个方法,经过测试,我发现while循环中的两行运行速度非常快。大约30/100000到1/10000秒,但当我将我的日期时间调用直接放在while循环之外时,它会减慢到大约1秒。

def query(self, command):
    result = ''
    result_list = []
    self.obd.write(command + "\r\n")
    while result != '>':
        result = self.obd.readline()
        result_list.append(result)
    self.result = result_list[-2].strip()

为什么while循环如此疯狂,我怎样才能加快速度?

为了解释我正在做什么,我从一个设备上获得串行输入,这个设备在输出的线数方面似乎有自己的想法。有时我需要的信息是在第二行,有时它在第三行,有时是第一行。我所知道的只是它是">"之前的最后一行。标志和我试过的其他方法给我留下了未读的缓冲数据,这些数据后来会让我感到困惑,所以我必须等待">"。

编辑:显然我没有充分解释我的所作所为。

我从上面的代码开始,然后编辑它以检查它的运行速度。

def query(self, command):
    result = ''
    result_list = []
    self.obd.write(command + "\r\n")
    while result != '>':
        a = datetime.datetime.now()
        result = self.obd.readline()
        result_list.append(result)
        b = datetime.datetime.now()
        print b - a
    self.result = result_list[-2].strip()

每次运行此方法时,平均运行时间不到1/10000秒。

def query(self, command):
    result = ''
    result_list = []
    self.obd.write(command + "\r\n")
    a = datetime.datetime.now()
    while result != '>':
        result = self.obd.readline()
        result_list.append(result)
    b = datetime.datetime.now()
    print b - a
    self.result = result_list[-2].strip()

这表示每次运行此方法时都会超过1秒。

while循环中发生的事情是正在读取串行端口。如果我绕它做一个for循环,它会工作一段时间,然后当缓冲区略微落后时停止,但我可以查询高达60赫兹的端口。

如果不是while循环,为什么我会得到我看到的结果?

3 个答案:

答案 0 :(得分:2)

虽然循环不慢。实际上,while循环的开销实际上是难以察觉的。如果您认为语句在循环外快速运行,则必须关闭测量值。

从文件或串行设备读取数据是您可以做的最慢的事情之一。由于你的while循环中有一个readline语句,这可能会减慢它的速度。也许它正在等待一系列输入,而等待正在减慢它的速度。

您的问题提到将日期时间调用从循环内部移动到外部但我在任何地方都没有看到任何日期时间功能,因此很难推测问题是否属于该部分。

答案 1 :(得分:2)

澄清一下,虽然所有解释性语言(如Python)中的循环与它们在编译语言(如c)中的实现相比非常慢。对于for循环和其他循环也是如此。原因是解释性语言具有高的每语句开销,而大多数编译语言几乎没有或没有这样的开销。即使使用列表推导,也会在循环的每次迭代中产生此开销。至少有三种方法可以优化或减轻解释语言中的循环:

  • 优化每个循环迭代,以强制加快运行时间。
  • 使用针对任务进行了优化的内置操作。
  • 使用库"矢量化"功能类似于numpy中提供的功能。 (读取/写入/操作数字数据时的最佳解决方案。)这些库通常部分使用编译代码组成,以加快重复操作。

在你的情况下,我建议第一个(通过仅存储标量而不是数组来优化内部):

def query(self, command):
    result = ''
    line = ''
    self.obd.write(command + "\r\n")
    while line != '>':
        result = line
        line = self.obd.readline()
    self.result = result.strip()

append函数比简单的标量赋值花费更多时间,因此节省了一些时间,并且您的代码已经忽略了倒数第二行以外的所有内容。

或者,您可以尝试使用经过优化的内置函数。如果obd支持readline(),那么它很有可能像文件一样支持readlines()read()。根据数据的长度和复杂程度,使用re.search结果read()有时会更快:

def query(self, command):
    self.obd.write(command + "\r\n")
    result = re.search('(?:^|\n)([^\n]*?)\n>(\n|$)', obd.read())
    self.result = result.group(1) if result else None

正则表达式并不像看起来那么复杂。它只搜索一行,后跟第二行等于>。它也不是非常有效率。

最后一种方法是使用非正则表达式内置函数来减少while循环运行的次数:

def query(self, command):
    self.obd.write(command + "\r\n")
    remaining = obd.read().lstrip()
    sep = ''
    while remaining and remaining[0] != '\n':
        chunk, sep, remaining = remaining.partition('\n>')
    self.result = chunk.rpartition('\n')[2] if sep else ''

对于行开头的每个>,只会运行一次,这可能只有一次。

请注意,后两个更改(正则表达式和使用分区)都依赖于首先读取整个文件。有两个副作用需要注意:

  • 读取整个文件所需的内存与将整个文件添加到列表相同,因此与以前的方法相比,没有节省内存,只节省了时间。
  • 因为读取了整个文件,所以还会读取 >后的字节/行,如果obd没有发送EOF,则会失败信号(就好像它是一根没有关闭的管道)。请注意这一点,特别是如果您打算让另一个文件继续从obd读取。

答案 2 :(得分:0)

我不确定问题到底是什么问题,这令人沮丧,但对于我挑选别人的代码并不令人沮丧。我终于得到了一个有效的解决方案。

我决定不使用readline(),而是使用read(1),每次调用从缓冲区中读取一个字节。这样做,我能够等待">"字符,并返回上一行。

以下是新方法:

def query(self, command):
    line = ''
    self.obd.write(command + "\r\n")
    while True:
        c = self.obd.read(1)
        line += c
        if c == '>':
            break
    # should work even if there is no newline character
    self.result = line.split('\r')[-2].strip()

这与我使用for循环的前一个方法的工作时间相同,即。 ~60hz,但不太可能让缓冲区充满垃圾。

感谢所有帮助。它让我走上正轨。