前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python中的循环-比较和性能

Python中的循环-比较和性能

作者头像
计算机与AI
发布2020-12-14 11:33:44
3.3K0
发布2020-12-14 11:33:44
举报
文章被收录于专栏:计算机与AI计算机与AI

Python是当今最受欢迎的编程语言之一。这是一种具有优雅且易读语法的解释性高级语言。但是,Python通常比Java,C#尤其是C,C ++或Fortran慢得多。有时性能问题和瓶颈可能会严重影响应用程序的可用性。

幸运的是,在大多数情况下,有一些解决方案可以提高Python程序的性能。开发人员可以选择提高其代码速度。例如,一般建议是使用经过优化的Python内置或第三方例程,这些例程通常以C或Cython编写。此外,使用局部变量比使用全局变量更快,因此,在循环之前将全局变量复制到局部变量是一个好习惯。等等。

最后,总有可能用C,C ++或Cython编写自己的Python函数,从应用程序中调用它们并替换Python瓶颈例程。但这通常是一个极端的解决方案,实践中几乎没有必要。

使用Python循环时,特别是在进行大量迭代时,常常会出现性能问题。有许多有用的技巧可以改善代码并使之运行得更快,但这超出了本文的范围。

本文比较了按元素求和两个序列时几种方法的性能:

  • 使用while循环
  • 使用for循环
  • 将for循环用于列表推导
  • 使用第三方库 numpy

但是,性能并不是开发软件时唯一关心的问题。此外,根据《计算机编程艺术》中的Donald Knuth所说,“过早的优化是编程中所有(或至少其中大部分)邪恶的根源”。毕竟,正如蒂姆·彼得斯(Tim Peters)在《 Python Zen》中所说的,“可读性至关重要”。

问题陈述

我们将尝试按元素求和两个序列。换句话说,我们将采用两个大小相同的序列(列表或数组),并使用通过从输入中添加相应元素而获得的元素来创建第三个序列。

准备

我们将导入Python内置程序包random并生成一个列表r,其中包含100.000个伪随机数,范围从0到99(含):

代码语言:javascript
复制
代码语言:javascript
复制
import random
r = [random.randrange(100) for _ in range(100_000)]

我们还将使用第三方包numpy,因此我们将其导入:

代码语言:javascript
复制
代码语言:javascript
复制
import numpy as np

我们已经准备好出发了!

简单循环

首先让我们看一下一些简单的Python循环。

使用纯Python

我们将从两个具有1.000个元素的列表开始。整数变量n表示每个列表的长度。列表x和y是通过从r中随机选择n个元素获得的:

代码语言:javascript
复制
代码语言:javascript
复制
n = 1_000
x, y = random.sample(r, n), random.sample(r, n)

让我们看看获取具有n个元素的新列表z所需的时间,每个元素是x和y中相应元素的总和。

我们将首先测试while循环的性能:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
i, z = 0, []
while i < n:
z.append(x[i] + y[i])
i += 1

输出为:

每个循环160 µs±1.44 µs(平均±标准偏差,共运行7次,每个回路10000个)

请注意,的输出timeit取决于许多因素,并且每次可能都不同。

Python中的for循环针对这种情况进行了更好的优化,即遍历集合,迭代器,生成器等。让我们看看它是如何工作的:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
z = []
for i in range(n):
    z.append(x[i] + y[i])

输出为:

每个循环122 µs±188 ns(平均±标准偏差,共运行7次,每个循环10000个)

在这种情况下,for循环比while循环更快,但也更优雅。

列表推导与普通的for循环非常相似。它们适用于简单的情况(例如这种情况)。除了更紧凑之外,由于消除了一些开销,它们通常会稍微快一些:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
z = [x[i] + y[i] for i in range(n)

输出为:

每个回路87.2 µs±490 ns(平均±标准偏差,共运行7次,每个回路10000个)

请记住,当您需要循环时,不能在所有情况下都应用列表推导。一些更复杂的情况需要普通的for或while循环。

在NumPy中使用Python

numpy是第三方Python库,通常用于数值计算。特别适合操纵数组。它提供了许多有用的例程来处理数组,但也允许编写紧凑而优雅的代码而没有循环。

实际上,循环以及其他对性能至关重要的操作是在numpy较低级别上实现的。numpy与纯Python代码相比,这可使例程更快。另一个优势是numpy处理变量和类型的方式。

首先让我们使用Python整数x和y的列表创建对应numpy的64位整数数组:

代码语言:javascript
复制
代码语言:javascript
复制
x_, y_ = np.array(x, dtype=np.int64), np.array(y, dtype=np.int64)

numpy元素求和两个数组x_和y_就像x_ + y_一样容易。但是让我们检查一下性能:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
z = x_ + y_

输出为:

每个循环1.03 µs±5.09 ns(平均±标准偏差,共运行7次,每个循环1000000次)

这比我们使用列表推导时快了将近85倍。而且代码极其简单优雅。numpy数组可能是处理大型数组的更好选择。当数据更大时,性能优势通常会更大。

可能会更好。如果可以使用32位整数而不是64位整数,则在某些情况下可以节省内存和时间:

代码语言:javascript
复制
代码语言:javascript
复制
x_, y_ = np.array(x, dtype=np.int32), np.array(y, dtype=np.int32)

我们可以像以前一样添加这两个数组:

代码语言:javascript
复制
%%timeit
z = x_ + y_

输出为:

每个循环814 ns±5.8 ns(平均±标准偏差,共7次运行,每个循环1000000次)

下表显示n较大时获得的结果,即10_000和100_000。在这种情况下,它们显示相同的关系,使用时甚至可以提高性能numpy

嵌套循环

现在让我们比较嵌套的Python循环。

使用纯Python

我们将再次处理两个名为x和y的列表。它们每个都将包含100个内部列表,其中包含1.000个伪随机整数元素。因此,x和y实际上代表具有100行和1.000列的矩阵:

代码语言:javascript
复制
代码语言:javascript
复制
m, n = 100, 1_000
x = [random.sample(r, n) for _ in range(m)]
y = [random.sample(r, n) for _ in range(m)]

让我们看看使用两个嵌套的while循环添加它们的性能:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
i, z = 0, []
while i < m:
    j, z_ = 0, []
    while j < n:
        z_.append(x[i][j] + y[i][j])
        j += 1
    z.append(z_)
    i += 1

输出为:

每个循环19.7 ms±271 µs(平均±标准偏差,共运行7次,每个循环100个) 再次,我们可以使用嵌套的for循环来提高性能:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
z = []
for i in range(m):
    z_ = []
    for j in range(n):
         z_.append(x[i][j] + y[i][j])
    z.append(z_)

输出为:

每个循环16.4 ms±303 µs(平均±标准偏差,运行7次,每个循环100个循环) 在某些情况下,嵌套的for循环可用于列表推导,从而带来额外的好处:

代码语言:javascript
复制
代码语言:javascript
复制
%%timeit
z = [[x[i][j] + y[i][j] for j in range(n)] for i in range(m)]

输出为:

每个循环12.1 ms±99.4 µs(平均±标准偏差,共运行7次,每个循环100个)

我们可以看到,在嵌套循环的情况下,列表理解要比普通的for循环要快,而for循环要比while快。

在这种情况下,每个列表中都有100.000(100×1.000)个整数元素。此示例比具有100.000元素和单个循环的示例稍慢。这是所有三种方法的结论(列表理解,普通for和while循环)。

在NumPy中使用Python

numpy非常适合与多维数组一起使用。让我们使用x和y创建对应numpy的64位整数数组:

代码语言:javascript
复制
代码语言:javascript
复制
x_, y_ = np.array(x, dtype=np.int64), np.array(y, dtype=np.int64)

让我们检查一下性能:

代码语言:javascript
复制
%%timeit
z = x_ + y_

输出为:

每个循环69.9 µs±909 ns(平均±标准偏差,共运行7次,每个循环10000个), 这比列表理解的速度快173倍。但是如果我们使用32位整数,它可能会更快:

代码语言:javascript
复制
代码语言:javascript
复制
x_, y_ = np.array(x, dtype=np.int32), np.array(y, dtype=np.int32)

像以前一样进行性能检查:

代码语言:javascript
复制
%%timeit
z = x_ + y_

输出为:

每个循环34.3 µs±44.6 ns(平均±标准偏差,共运行7次,每个循环10000个) ,这比64位整数快两倍。

结果汇总

下图总结了获得的结果:

结论

本文比较了按元素添加两个列表或数组时Python循环的性能。结果表明,列表理解比普通的for循环要快,而while循环则要快。在所有这三种情况下,简单循环都比嵌套循环快一点。

numpy提供的例程和运算符可以大大减少代码量并提高执行速度。在处理一维和多维数组时特别有用。

请记住,此处得出的结论或结果之间的关系在所有情况下都不适用,无效或无用!提出它们是为了说明。处理效率低下的正确方法是发现瓶颈并执行自己的测试。


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 计算机与AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题陈述
  • 准备
  • 简单循环
    • 使用纯Python
      • 在NumPy中使用Python
      • 嵌套循环
        • 使用纯Python
          • 在NumPy中使用Python
          • 结果汇总
          • 结论
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档