python 程序运行方法-Python加速程序运行的方法

问题

你的程序运行太慢,你想在不使用复杂技术比如C扩展或JIT编译器的情况下加快程序运行速度。

解决方案

关于程序优化的第一个准则是“不要优化”,第二个准则是“不要优化那些无关紧要的部分”。 如果你的程序运行缓慢,首先你得使用14.13小节的技术先对它进行性能测试找到问题所在。

通常来讲你会发现你得程序在少数几个热点位置花费了大量时间, 比如内存的数据处理循环。一旦你定位到这些点,你就可以使用下面这些实用技术来加速程序运行。

使用函数

很多程序员刚开始会使用Python语言写一些简单脚本。 当编写脚本的时候,通常习惯了写毫无结构的代码,比如:

?

1

2

3

4

5

6

7

8

9

10

# somescript.py

import sys

import csv

withopen(sys.argv[1]) as f:

for rowin csv.reader(f):

# Some kind of processing

pass

很少有人知道,像这样定义在全局范围的代码运行起来要比定义在函数中运行慢的多。 这种速度差异是由于局部变量和全局变量的实现方式(使用局部变量要更快些)。 因此,如果你想让程序运行更快些,只需要将脚本语句放入函数中即可:

?

1

2

3

4

5

6

7

8

9

10

11

# somescript.py

import sys

import csv

def main(filename):

withopen(filename) as f:

for rowin csv.reader(f):

# Some kind of processing

pass

main(sys.argv[1])

速度的差异取决于实际运行的程序,不过根据经验,使用函数带来15-30%的性能提升是很常见的。

尽可能去掉属性访问

每一次使用点(.)操作符来访问属性的时候会带来额外的开销。 它会触发特定的方法,比如 __getattribute__()和 __getattr__() ,这些方法会进行字典操作操作。

通常你可以使用 from module import name 这样的导入形式,以及使用绑定的方法。 假设你有如下的代码片段:

?

1

2

3

4

5

6

7

8

9

10

11

12

import math

def compute_roots(nums):

result= []

for nin nums:

result.append(math.sqrt(n))

return result

# Test

nums= range(1000000)

for nin range(100):

r= compute_roots(nums)

在我们机器上面测试的时候,这个程序花费了大概40秒。现在我们修改 compute_roots()函数如下:

?

1

2

3

4

5

6

7

8

9

from mathimport sqrt

def compute_roots(nums):

result= []

result_append= result.append

for nin nums:

result_append(sqrt(n))

return result

修改后的版本运行时间大概是29秒。唯一不同之处就是消除了属性访问。 用 sqrt()代替了 math.sqrt()。 The result.append()方法被赋给一个局部变量result_append,然后在内部循环中使用它。

不过,这些改变只有在大量重复代码中才有意义,比如循环。 因此,这些优化也只是在某些特定地方才应该被使用。

理解局部变量

之前提过,局部变量会比全局变量运行速度快。 对于频繁访问的名称,通过将这些名称变成局部变量可以加速程序运行。 例如,看下之前对于 compute_roots()函数进行修改后的版本:

?

1

2

3

4

5

6

7

8

9

import math

def compute_roots(nums):

sqrt= math.sqrt

result= []

result_append= result.append

for nin nums:

result_append(sqrt(n))

return result

在这个版本中,sqrt 从 math 模块被拿出并放入了一个局部变量中。 如果你运行这个代码,大概花费25秒(对于之前29秒又是一个改进)。 这个额外的加速原因是因为对于局部变量 sqrt 的查找要快于全局变量 sqrt

对于类中的属性访问也同样适用于这个原理。 通常来讲,查找某个值比如 self.name 会比访问一个局部变量要慢一些。 在内部循环中,可以将某个需要频繁访问的属性放入到一个局部变量中。例如:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# Slower

class SomeClass:

...

def method(self):

for xin s:

op(self.value)

# Faster

class SomeClass:

...

def method(self):

value= self.value

for xin s:

op(value)

避免不必要的抽象

任何时候当你使用额外的处理层(比如装饰器、属性访问、描述器)去包装你的代码时,都会让程序运行变慢。 比如看下如下的这个类:

?

1

2

3

4

5

6

7

8

9

10

class A:

def __init__(self, x, y):

self.x= x

self.y= y

@property

def y(self):

return self._y

@y.setter

def y(self, value):

self._y= value

现在进行一个简单测试:

?

1

2

3

4

5

6

7

>>>from timeitimport timeit

>>> a= A(1,2)

>>> timeit("a.x","from __main__ import a")

0.07817923510447145

>>> timeit("a.y","from __main__ import a")

0.35766440676525235

>>>

可以看到,访问属性y相比属性x而言慢的不止一点点,大概慢了4.5倍。 如果你在意性能的话,那么就需要重新审视下对于y的属性访问器的定义是否真的有必要了。 如果没有必要,就使用简单属性吧。 如果仅仅是因为其他编程语言需要使用getter/setter函数就去修改代码风格,这个真的没有必要。

使用内置的容器

内置的数据类型比如字符串、元组、列表、集合和字典都是使用C来实现的,运行起来非常快。 如果你想自己实现新的数据结构(比如链接列表、平衡树等), 那么要想在性能上达到内置的速度几乎不可能,因此,还是乖乖的使用内置的吧。

避免创建不必要的数据结构或复制

有时候程序员想显摆下,构造一些并没有必要的数据结构。例如,有人可能会像下面这样写:

?

1

2

values= [xfor xin sequence]

squares= [x*xfor xin values]

也许这里的想法是首先将一些值收集到一个列表中,然后使用列表推导来执行操作。 不过,第一个列表完全没有必要,可以简单的像下面这样写:

?

1

squares= [x*xfor xin sequence]

与此相关,还要注意下那些对Python的共享数据机制过于偏执的程序所写的代码。 有些人并没有很好的理解或信任Python的内存模型,滥用 copy.deepcopy()之类的函数。 通常在这些代码中是可以去掉复制操作的。

讨论

在优化之前,有必要先研究下使用的算法。 选择一个复杂度为 O(n log n) 的算法要比你去调整一个复杂度为 O(n**2) 的算法所带来的性能提升要大得多。

如果你觉得你还是得进行优化,那么请从整体考虑。 作为一般准则,不要对程序的每一个部分都去优化,因为这些修改会导致代码难以阅读和理解。 你应该专注于优化产生性能瓶颈的地方,比如内部循环。

你还要注意微小优化的结果。例如考虑下面创建一个字典的两种方式:

?

1

2

3

4

5

6

7

a= {

"name" :"AAPL",

"shares" :100,

"price" :534.22

}

b= dict(name="AAPL", shares=100, price=534.22)

后面一种写法更简洁一些(你不需要在关键字上输入引号)。 不过,如果你将这两个代码片段进行性能测试对比时,会发现使用 dict()的方式会慢了3倍。 看到这个,你是不是有冲动把所有使用 dict()的代码都替换成第一种。 不够,聪明的程序员只会关注他应该关注的地方,比如内部循环。在其他地方,这点性能损失没有什么影响。

如果你的优化要求比较高,本节的这些简单技术满足不了,那么你可以研究下基于即时编译(JIT)技术的一些工具。 例如,PyPy工程是Python解释器的另外一种实现,它会分析你的程序运行并对那些频繁执行的部分生成本机机器码。 它有时候能极大的提升性能,通常可以接近C代码的速度。 不过可惜的是,到写这本书为止,PyPy还不能完全支持Python3. 因此,这个是你将来需要去研究的。你还可以考虑下Numba工程, Numba是一个在你使用装饰器来选择Python函数进行优化时的动态编译器。 这些函数会使用LLVM被编译成本地机器码。它同样可以极大的提升性能。 但是,跟PyPy一样,它对于Python 3的支持现在还停留在实验阶段。

最后我引用John Ousterhout说过的话作为结尾:“最好的性能优化是从不工作到工作状态的迁移”。 直到你真的需要优化的时候再去考虑它。确保你程序正确的运行通常比让它运行更快要更重要一些(至少开始是这样的).

以上就是Python加速程序运行的方法的详细内容,更多关于Python加速程序运行的资料请关注服务器之家其它相关文章!

原文链接:https://python3-cookbook.readthedocs.io/zh_CN/latest/c14/p14_make_your_program_run_faster.html

Python 加速程序运行主要有以下几个途径: 1. **优化算法**:选择更高效的算法可以大幅度提升程序性能。有时候,即使是简单的优化也能带来显著的效果。 2. **利用 Python 编程库和框架**:很多针对特定任务设计的库和框架已经经过了大量的优化工作。例如,对于数据处理、科学计算等应用领域,`NumPy`、`Pandas` 和 `SciPy` 等库通常比原生 Python 要快得多。同时,如`Dask`这样的库可以帮助处理大型数据集而不会导致内存溢出。 3. **多线程与异步编程**:利用 Python 的 `concurrent.futures` 模块或第三方库如 `asyncio` 进行多线程或多进程编程,可以并行执行任务,提高程序的执行效率。注意,由于 GIL(全局解释器锁),单 CPU 核心下多线程可能不会提供预期的速度提升;但对于多核心处理器,多线程仍然有明显的加速效果。 4. **使用 JIT 编译器**:JIT(Just-In-Time)编译器可以在程序运行时将部分或全部 Python 代码转换成机器码,减少解释过程带来的性能损耗。例如,`Numba` 就是一个流行的 Python JIT 编译器,它特别擅长加速数学密集型代码。 5. **降低 IO 开销**:IO 操作通常是 Python 程序的瓶颈之一。优化文件读写、数据库查询等操作,比如通过缓存策略减少频繁访问,可以有效提升性能。 6. **编写 Cython 或 C/C++ 插件**:对于最核心的部分,可以考虑使用 C/C++ 编写代码并通过 Cython 或其他工具将其集成到 Python 中,这种方式可以极大提高关键模块的执行速度。 7. **避免全局变量和递归调用**:过多使用全局变量可能导致性能下降,因为每次函数调用都需要从堆栈上下文切换和恢复状态。递归调用同样会消耗大量的系统资源,并可能导致栈溢出错误。 8. **使用低级语言编写的高性能库**:如使用 Cython 直接调用 C 函数,或者使用像 Numexpr 这样的库,它们底层都是用 C 或 C++ 实现的,能提供极高的性能。 9. **调整 Python 解释器设置**:适当调整 Python 解释器的配置选项,如启用 GC(垃圾回收)优化、调整循环缓冲区大小等,有时也可以提高程序运行效率。 实施上述方法时,应先对程序进行详细的性能分析,找出瓶颈所在,然后针对性地采取措施。记住,在追求性能的同时,也要考虑到代码的可维护性和可读性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值