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

在python中有没有更快的方法来执行while循环?,python,Python,我只是花了一个小时的时间在我的代码中寻找原因,在我重写了一堆代码之后,我发现了它为什么会死缠烂打。我没有想到while循环这么慢。我想我从来没有在时间非常紧迫的地方使用过它们 我最终将问题缩小到这个方法,在测试之后,我发现while循环中的两行运行得非常快。大约30/100000到1/10000秒,但是当我将datetime调用直接放在while循环之外时,它会减慢到大约1秒 def query(self, command): result = '' result_list =

我只是花了一个小时的时间在我的代码中寻找原因,在我重写了一堆代码之后,我发现了它为什么会死缠烂打。我没有想到while循环这么慢。我想我从来没有在时间非常紧迫的地方使用过它们

我最终将问题缩小到这个方法,在测试之后,我发现while循环中的两行运行得非常快。大约30/100000到1/10000秒,但是当我将datetime调用直接放在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+秒

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()
while循环中发生的事情是从中读取串行端口。如果我在它周围做一个for循环,它会工作一段时间,然后在缓冲区稍微落后时停止,但我可以查询高达60 hz的端口


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

while循环并不慢。事实上,while循环的开销实际上是难以察觉的。如果您认为语句在循环之外运行得很快,那么您的测量必须关闭

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


您的问题提到将datetime调用从循环内部移动到外部,但我没有看到任何datetime函数,因此很难推测这是否是问题的一部分

而循环并不慢。事实上,while循环的开销实际上是难以察觉的。如果您认为语句在循环之外运行得很快,那么您的测量必须关闭

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


您的问题提到将datetime调用从循环内部移动到外部,但我没有看到任何datetime函数,因此很难推测这是否是问题的一部分

澄清一下,所有解释性语言(如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,则很有可能像这样的文件也支持readline或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
那里的正则表达式并不像看上去那么复杂。它只搜索一行,后跟第二行,等于>。它的效率也不是很高

最后一种方法是使用非regex内置函数来减少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 ''
对于一行开头的每个>,它将只运行一次while,这可能只运行一次

请注意,第二个两个更改regex和使用partition都依赖于第一次读取整个文件。有两个问题 需要注意的ide效果:

读取整个文件所需的内存与将整个文件添加到列表中所需的内存相同,因此与以前的方法相比,不会节省内存,只会节省时间。 因为整个文件都被读取,>之后的字节/行也被读取,如果obd不发送EOF信号,就像管道没有关闭一样,那么它将失败。请注意这一点,特别是如果您打算让另一个文件继续从obd读取。
为了澄清这一点,所有解释性语言(如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,则很有可能像这样的文件也支持readline或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
那里的正则表达式并不像看上去那么复杂。它只搜索一行,后跟第二行,等于>。它的效率也不是很高

最后一种方法是使用非regex内置函数来减少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 ''
对于一行开头的每个>,它将只运行一次while,这可能只运行一次

请注意,第二个两个更改regex和使用partition都依赖于第一次读取整个文件。有两个副作用需要注意:

读取整个文件所需的内存与将整个文件添加到列表中所需的内存相同,因此与以前的方法相比,不会节省内存,只会节省时间。 因为整个文件都被读取,>之后的字节/行也被读取,如果obd不发送EOF信号,就像管道没有关闭一样,那么它将失败。请注意这一点,特别是如果您打算让另一个文件继续从obd读取。
我不确定问题到底出在哪里,这很令人沮丧,但还不足以让我分清别人的代码。不过,我终于找到了一个可行的解决方案

我决定不使用readline,而是使用read1,它在每次调用时从缓冲区读取一个字节。这样,我就可以等待>字符,并返回上一行

以下是新方法:

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)的工作时间相同,但不太可能让缓冲区充满垃圾


谢谢你的帮助。这让我走上了正确的道路。

我不确定问题到底出在哪里,这很令人沮丧,但还不足以让我分清别人的代码。不过,我终于找到了一个可行的解决方案

我决定不使用readline,而是使用read1,它在每次调用时从缓冲区读取一个字节。这样,我就可以等待>字符,并返回上一行

以下是新方法:

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)的工作时间相同,但不太可能让缓冲区充满垃圾


谢谢你的帮助。这让我走上了正确的道路。

而环路本身并不慢。您所经历的缓慢是由于其他原因造成的,例如您在while循环中所做的操作。while循环的迭代次数比您想象的要多,或者它在短时间内阻塞了I/O。串行设备的波特率是多少?波特率是115200。当我使用for循环时,我可以每秒发送60条命令并得到60条回复,直到for循环中断。也就是说,您是否使用cProfile查找代码在哪里花费时间?而循环本身并不慢。您所经历的缓慢是由于其他原因造成的,例如您在while循环中所做的操作。您的while循环的迭代次数比您想象的要多,或者它在I/O上阻塞了一段时间
短时间。串行设备的波特率是多少?波特率是115200。当我使用for循环时,我可以每秒发送60条命令并得到60条回复,直到for循环中断为止。你是否使用cProfile来查找代码在哪里花费时间?我为此进行了测试。读线并不慢。我编辑了我的问题来展示我做了什么来测试。好吧,对不起。出于某种原因,我没有思考。这个循环运行了很多次,我没有注意到由于某种原因它运行了多少次。我现在正在调查。我必须看看readline命令返回了什么,我已经测试过了。读线并不慢。我编辑了我的问题来展示我做了什么来测试。好吧,对不起。出于某种原因,我没有思考。这个循环运行了很多次,我没有注意到由于某种原因它运行了多少次。我现在正在调查。我必须看看readline命令返回了什么。我喜欢这个答案,不幸的是,由于某种原因,它对我不起作用。不过,您使用read而不是readline确实让我找到了解决方案。我将把解决我问题的方法发布为答案,但谢谢你让我走上正轨。我选择了你的答案作为接受答案,因为它本应该有效。就我而言,这并没有发生,这让我走上了正确的道路。谢谢!如果我冒昧地猜测我的答案没有开箱即用的原因,我猜obd可能不会发送EOF。我喜欢这个答案,不幸的是,由于某种原因,它对我不起作用。不过,您使用read而不是readline确实让我找到了解决方案。我将把解决我问题的方法发布为答案,但谢谢你让我走上正轨。我选择了你的答案作为接受答案,因为它本应该有效。就我而言,这并没有发生,这让我走上了正确的道路。谢谢!如果我冒险猜测一下我的答案不符合要求的原因,我猜obd可能不会发送EOF。