📌  相关文章
📜  Python|让程序运行得更快

📅  最后修改于: 2022-05-13 01:55:20.625000             🧑  作者: Mango

Python|让程序运行得更快

众所周知, Python编程语言有点慢,目标是在不借助更极端的解决方案(例如 C 扩展或即时 (JIT) 编译器)的情况下加速它。
虽然优化的第一条规则可能是“不做”,但第二条规则几乎可以肯定是“不优化不重要的”。为此,如果程序运行缓慢,可以从分析代码开始。通常情况下,人们会发现程序将时间花在几个热点上,例如内部数据处理循环。一旦确定了这些位置,就可以使用严肃的技术使程序运行得更快。
许多程序员开始使用Python作为编写简单脚本的语言。在编写脚本时,很容易陷入一种只编写结构很少的代码的做法。
代码 #1:考虑到此代码。

Python3
# abc.py
import sys
import csv
 
with open(sys.argv[1]) as f:
    for row in csv.reader(f):
        # Some kind of processing


Python3
# abc.py
import sys
import csv
 
def main(filename):
    with open(filename) as f:
        for row in csv.reader(f):
        # Some kind of processing
 
main(sys.argv[1])


Python3
import math
 
def compute_roots(nums):
    result = []
    for n in nums:
        result.append(math.sqrt(n))
    return result
 
# Test
nums = range(1000000)
for n in range(100):
    r = compute_roots(nums)


Python3
from math import sqrt
 
def compute_roots(nums):
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result


Python3
import math
 
def compute_roots(nums):
    sqrt = math.sqrt
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result


Python3
# Slower
class SomeClass:
    ...
    def method(self):
        for x in s:
            op(self.value)
# Faster
class SomeClass:
    ...
    def method(self):
        value = self.value
        for x in s:
            op(value)


一个鲜为人知的事实是,像这样在全局范围内定义的代码比在函数中定义的代码运行得慢。速度差异与局部变量与全局变量的实现有关(涉及局部变量的操作更快)。因此,只需将脚本语句放在一个函数中即可使程序运行得更快。代码#2:

Python3

# abc.py
import sys
import csv
 
def main(filename):
    with open(filename) as f:
        for row in csv.reader(f):
        # Some kind of processing
 
main(sys.argv[1])

速度差异在很大程度上取决于正在执行的处理,但 15-30% 的加速并不少见。

选择性地消除属性访问 -

每次使用点 (.)运算符访问属性都需要付出代价。在幕后,这会触发特殊方法,例如 __getattribute__() 和 __getattr__(),这通常会导致字典查找。
人们通常可以通过使用from module import name形式的 import 以及选择使用绑定方法来避免属性查找,如下面的代码片段所示 -
代码#3:

Python3

import math
 
def compute_roots(nums):
    result = []
    for n in nums:
        result.append(math.sqrt(n))
    return result
 
# Test
nums = range(1000000)
for n in range(100):
    r = compute_roots(nums)

输出 :

This program runs in about 40 seconds when running on the machine.


代码 #4:更改 compute_roots()函数

Python3

from math import sqrt
 
def compute_roots(nums):
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result

输出 :

This program runs in about 29 seconds when running on the machine.



两个版本的代码之间的唯一区别是消除了属性访问。代码使用 sqrt(),而不是使用 math.sqrt()。 result.append() 方法被额外放入一个局部变量 re
sult_append 并在内部循环中重用。
但是,必须强调的是,这些更改仅在频繁执行的代码中才有意义,例如循环。所以,这种优化真的只在精心挑选的地方才有意义。

了解变量的局部性——

如前所述,局部变量比全局变量快。对于经常访问的名称,可以通过使这些名称尽可能本地化来获得加速。
代码 #5:compute_roots()函数的修改版本

Python3

import math
 
def compute_roots(nums):
    sqrt = math.sqrt
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result

在这个版本中, sqrt 已从数学模块中提取并放入局部变量中。这段代码将运行大约 25 秒(比之前版本的 29 秒有所改进)。这种额外的加速是由于 sqrt 的本地查找比 sqrt 的全局查找快一点。
在课堂上工作时,位置参数也适用。通常,查找诸如 self.name 之类的值将比访问局部变量慢得多。在内部循环中,将常用的属性提升到局部变量中可能是值得的,如下面的代码所示。
代码#6:

Python3

# Slower
class SomeClass:
    ...
    def method(self):
        for x in s:
            op(self.value)
# Faster
class SomeClass:
    ...
    def method(self):
        value = self.value
        for x in s:
            op(value)

动态打字:

Python速度慢的原因是因为它是动态类型的,现在我们将更详细地讨论这一点,但我想与Java之类的语言进行比较。现在在Java中,一切都是静态类型的,并且这种语言实际上是在运行之前编译的,这与在运行时通过解释器编译的Python不同。现在在Java中发生的事情是,当您编写代码时,您需要定义每个变量将是什么类型,您的方法和函数将返回什么类型,并且您几乎必须准确定义一切将是什么贯穿你的代码。现在虽然这会导致更长的开发时间并且需要更长的时间来编写你的代码,但是它的作用是在你编译时提高效率,现在它真正起作用的原因以及它比Python代码运行得更快的原因是因为如果您知道特定变量或对象的类型,您可以执行大量不同的优化并避免在实际运行代码时执行大量不同的检查,因为这些检查是在Java中的编译时执行的本质上,当您编写要尝试编译它的代码时,您无法编译任何没有实际或什至只是键入错误的Java代码,它会说这种类型不准确,您可以不这样做,你不能编译它,因为它知道当涉及到运行时它不会工作,所以基本上所有这些在代码运行时实际上需要在Python中执行的检查都是预先执行的,并且有由于这种静态类型的长度,只需进行大量优化。现在有人可能会问这样一个问题,为什么Python不这样做?对此的回答是Python是动态类型的,这仅仅意味着任何变量都可以改变它的类型,并且可以在程序运行时在任何时候改变它的值,这意味着我们实际上不能事先编译整个程序,因为我们可以'不要一次做所有这些检查,因为我们不知道这些变量将是什么类型,它们会在运行时改变,会发生不同的事情,因此我们无法获得所有这些优化我们可能会使用Java、C 或 C++ 等较低级别的语言,这是该语言速度慢的根本原因,这种动态类型和任何快速语言都将有一个可以运行的编译器,它将使确保一切都很好,它会在它真正结束运行时运行代码之前进行所有这些检查,在Python中发生的事情是你的所有代码实际上是在运行时编译和检查的,而不是在运行之前编译它在你运行代码之前,所有的时间都是国王,许多不同的检查正在发生,以确保说这个对象是正确的,这些类型是正确的,一切都是一样的。

并发:

现在接下来要谈的显然是Python中缺乏并发性。这将是影响速度的主要因素,如果您使用Java、C 编写应用程序,您可以将所有内容分散到多个线程中,这允许您利用 CPU 的所有内核,以便将其分解为现代计算我们大多数人都有四个核心 CPU 或更高,这允许我们实际上同时运行四个任务,而现在使用Python这是不可能的。 Python说,对于每个解释器,我们一次最多可以运行一个线程,而一个线程只是 CPU 内核上发生的某种操作,这意味着即使我们在Python程序中创建了许多线程,我们也只能是在Java程序或 C 程序中使用一个 CPU 内核可能会使用全部八个或全部使用四个,这显然会导致速度提高 4 倍或 8 倍,现在我们可以通过使用多处理在Python中解决这个问题,但是有一些问题。