提高 Python 性能的 9 个技巧

原文地址:提高 Python 性能的 9 个技巧 

与 Java 等语言相比,Python 的性能较差。使用这些技巧来识别和修复 Python 代码中的问题,以调整其性能。

优化的应用程序和网站从构建良好的代码开始。然而,事实是您不需要担心 90% 的代码的性能,对于许多脚本来说可能是 100%。如果 ETL 脚本只运行一次或每晚运行一次,那么它花费一秒或一分钟并不重要。

不过,如果用户被迫等待缓慢的应用程序完成任务或网页显示结果,这确实很重要。即便如此,也可能只有代码库的一小部分应该受到指责。

最大的性能优势通常来自于编码甚至开始之前的性能规划,而不是在性能缓慢发生之后。也就是说,应用程序开发人员可以通过多种方式解决代码性能问题。

以下九个技巧专门针对Python 性能,尽管其中一些技巧也适用于其他语言:

  1. 选择正确的数据类型。

  2. 了解标准函数、方法和库。

  3. 查找注重性能的库。

  4. 理解不同的理解。

  5. 使用生成器函数、模式和表达式。

  6. 考虑如何处理大数据。

  7. 运行配置文件来识别有问题的代码。

  8. 考虑 CPython 替代品。

  9. 专注于有意义的改进。

选择正确的数据类型

使用集合的最佳数据类型。只要有集合,就可以轻松创建列表。您几乎可以在任何地方使用列表来代替集合或元组,并且列表的作用远不止这些。

但是,使用集合或元组进行某些操作会更快,并且这两种类型通常比列表使用更少的内存。要选择用于集合的最佳类型,您必须首先了解您正在使用的数据以及要执行的操作。

使用timeit我们可以看到使用集合测试成员资格比使用列表测试要快得多:

> python testtimeit_data_type.py

执行时间(带列表):7.966896300087683秒

执行时间(带设置):4.913181399926543秒

有时从列表创建临时集或元组会更快。例如,要查找两个列表中的公共值,创建两个集合并使用set junction()可能会更快。这取决于数据长度和操作,因此最好使用您期望的数据和操作进行测试。

了解标准函数、方法和库

了解 Python 的标准功能。模块经过优化,几乎总是比您编写的代码更快。许多 Python 函数很少需要,而且很容易忘记它们的存在。您可能记得set intersection() ,但是如果您需要它们,您会记得Difference()或isdisjoint()吗?

偶尔浏览一下Python 文档(https://docs.python.org/3/library),或者当您遇到性能问题时,了解哪些功能可用。当您开始一个新项目时,请仔细阅读与您自己的工作相关的那些部分。

如果您只有时间研究一个模块,请将其设为itertools——如果您认为可以使用它的功能,请考虑安装more-itertools(https://github.com/more-itertools/more-itertools)(标准库中没有)。itertools 的一些功能乍一看可能没什么用,但如果您正在从事某些工作并记住您在 intertools 中看到的可以提供帮助的内容,请不要感到惊讶。即使您没有看到立即使用,也最好知道有什么可用。

查找注重性能的库

如果您正在做一些大事,那么很可能有人已经创建了一个性能良好的库来帮助您。您可能需要多个库来为项目的不同区域提供功能,其中可能包括以下部分或全部内容:

  • 科学计算。

  • Vision

  • 机器学习。

  • 报告。

  • 整合。

注意发布日期、文档、支持和社区。较旧的库的性能可能不再像以前那样好,并且您可能需要熟悉给定库的人员的帮助才能实现所需的性能。

多个库通常提供相同的功能集。要确定选择哪一个,请使用适合您需求的数据为每个测试创建一个快速测试。您可能会发现其中一个更容易使用,或者另一个提供更好的开箱即用性能。

熊猫 Pandas

Pandas 是初学者常用的数据分析库。它值得学习的原因有两个:其他库使用它或提供兼容的接口,并且您可以找到它的最多帮助和示例。

北极星 Polars

pandas 的替代品(例如 Polars)为许多操作提供了更好的性能。Polars 具有不同的语法,可能会带来学习曲线,但值得一看,以启动新的大数据项目或解决现有项目中的性能问题。

Dask

如果您经常处理非常大的数据,您还应该考虑并行计算。这可能需要更改组织数据和执行计算的方式。它还需要付出大量的努力才能为多核或多台机器正确编程,这是 Python 与其他语言相比落后的领域。

Dask (https://docs.dask.org/)以及其他库(例如 Polars)可以处理复杂性,以充分利用您的核心或机器。如果您熟悉 NumPy 或 pandas,Dask 提供了跨内核并行化的最小学习曲线。即使您不使用 Dask,了解它提供的功能以及如何使用它也很有帮助,可以帮助您做好处理大数据的准备。

理解不同的理解

这是一个常见的 Python 性能技巧:列表理解将比for循环更快。

我的测试得出了这些时间,令人印象深刻。

>python timeit_compressive.py

执行时间(使用 for):5.180072800023481秒

执行时间(使用列表理解):2.5427665999159217秒

但我所做的只是创建一个新列表,其中包含根据原始值计算的值,因为提示通常显示以下内容:

new_list = [val*2 for val in orig_list]

相关问题是:我将如何处理该列表?列表推导式的任何实际性能增益可能来自使用可选谓词(过滤器)、嵌套推导式或生成器表达式而不是列表推导式。

此示例使用嵌套推导式展平矩阵,其性能通常优于嵌套循环:

flattened = [x for row in matrix for x in row]

这个使用嵌套理解和过滤器:

names = [

employee.name

for manager in managers

for employee in employees

    if employee.manager_id == manager.id

]

对列表理解的引用是最常见的,但集合理解、字典理解和生成器表达式的 工作方式相同。为您要创建的数据类型选择正确的类型。

以下示例与上面的矩阵示例类似,但返回一个集合而不是列表,这是从列表中获取唯一值的简单方法。

unique = {x for row in matrix for x in row}

列表理解和集合理解之间的唯一区别是集合理解使用大括号 {} 而不是方括号 []。

使用生成器函数、模式和表达式

生成器是在迭代大型集合时减少内存的好方法。如果您使用大型集合,您应该知道如何编写生成器函数并将生成器模式用于可迭代。

另外,学习如何使用生成器表达式,类似于列表推导式。以下代码对矩阵中所有值的平方求和,而不创建平方列表。

total = sum(x**2 for row in matrix for x in row)

考虑如何处理大数据

大数据和大文件的性能值得进行完整且单独的讨论。也就是说,在开始大数据项目之前需要考虑一些事情。准备做出以下决定:

  • 选择一个数据库。

  • 分块处理数据。

  • 确定是否可以忽略某些数据。

  • 指定数据类型。

  • 使用不同的文件类型。

数据库

对于非常大的数据,您几乎肯定会使用专门的库,例如用于科学计算的 NumPy、用于数据分析的 pandas 或用于并行化和分布式计算的 Dask。搜索互联网,您可能会找到满足任何其他大数据需求的库,以及这些需求的替代方案。

块数据

如果您的数据太大而无法放入 RAM,您可能需要分块处理。这种能力内置于 Pandas 中,如下所示:

chunk_readers = pandas.read_csv("./data.csv", chunksize=2000)

for chunk in chunk_readers:

    for index, record in chunk.iterrows():

            pprint(record)

其他模块(例如 Dask 和 Polars)有自己的数据分块或分区方法。

忽略数据

数据文件中的数据通常比您需要的多得多。Pandas 的read_csv有一个参数usecols,它可以让您指定所需的列,并忽略其余的列:

# 只保留命名列

data_frame = pandas.read_csv("./sales.csv", usecols=["product_id", "zip_code"])
#只保留索引为 1,8 和 20 的列

data_frame = pandas.read_csv("./population.csv", usecols=[1,8,20])

这可以显着减少处理数据所需的内存。.csv 文件对于 RAM 来说可能太大,但如果您只加载所需的列,则可能会避免分块。

推导式是从表中删除列和行的另一种方法,以便您仅使用所需的数据。为此,必须读取整个文件或对整个文件进行分块,并且原始容器和理解容器必须同时存在。如果需要忽略大量行,最好在迭代块时删除行。

指定数据类型

另一种节省内存的方法是指定一次数据类型并使用所需的最小类型。这还可以提高速度,因为它将数据保留为计算最快的格式,并且无需在每次使用数据时转换数据。例如,一个人的年龄适合八位(0-255),因此我们可以告诉 pandas在该列中使用int8而不是int64 :

data_frame = pandas.read_csv("./people.csv", dtype={"age": "int8"})

通常最好在加载期间设置数据类型,但有时这是不可能的——例如,pandas 无法将非整数浮点数(例如 18.5)转换为int8。它可以在加载数据框后转换整个列。Pandas 有多种方法来替换或修改列以及处理数据中的错误:

data_frame['age'] =

            data_frame['age'].astype('int8', errors='ignore')

Dataframe astype和pandas.to_numeric可以执行不同类型的转换。如果这些不适用于您的数据,则可能需要在循环或理解中实现您自己的转换。

使用不同的文件类型

大多数情况要求您使用常见的文件类型,例如 .csv。如果您需要在处理过程中保存中间文件,那么使用其他格式会很有帮助。

Apache Arrow 提供 Python 与PyArrow 模块的绑定。它与 NumPy、pandas 和 Python 对象集成,并提供了以其他文件格式读取和写入数据集的方法。这些格式更小,PyArrow 可以比 Python .csv 函数更快地读取和写入它们。PyArrow 还具有用于数据分析的附加功能,您现在可能不需要这些功能,但至少您知道它可以在将来需要时使用。

如前所述,Pandas 是一个流行的数据分析库。Polars 支持多种文件格式、多核和大于内存的数据文件。Dask 分区处理前面提到的大于内存数据文件的分块,并且 Dask 最佳实践在 Parquet 中使用更高效的文件格式。

运行配置文件以识别有问题的代码

当您遇到性能问题时,最好分析您的代码,而不是猜测优化工作的重点。提高性能的一般方法包括以下步骤:

  1. 创建一个比预期慢的最小的、可重现的用例。

  2. 运行配置文件。

  3. 改进具有最高percall 值的函数。

  4. 重复步骤 2,直到达到所需的性能水平。

  5. 运行一个真实的用例(不是最小的)而不进行分析。

  6. 重复步骤 1,直到达到步骤 5 中所需的性能水平。

凭借经验,您将逐渐了解或至少感受到最佳的聚焦位置。它可能不是具有最高percall值的代码。有时解决方法是修改循环内的单个计算。其他时候,您可能需要消除循环或在循环之外执行计算/理解。也许答案是重新组织数据,或者使用Dask进行并行化。

考虑 CPython 替代品

最常用的 Python 实现是 CPython。所有通用库都可与 CPython 一起使用,并且它完全实现了语言规范。

存在其他实现,但可能会导致以下问题:

  • Python 代码在边缘情况下的行为可能有所不同。

  • C API 模块可能无法工作或工作速度显着变慢。

  • 如果您将来必须运行 CPython,则附加功能将无法使用。

尽管存在这些警告,但在某些情况下,替代实现可能是最好的。如果计算严重依赖于 NumPy 等 C API 模块,那么性能几乎永远不会更好。考虑 CPython 的替代方案,例如:

  • Jython  在 JVM 中运行并允许与 Java 类和对象轻松集成。

  • IronPython 专为 .NET 设计,可轻松与 .NET 生态系统和 C# 库集成。

  • PyPy 使用即时 (JIT) 编译器,除非涉及 C API,否则运行速度明显快于 CPython。

如果您对通用模块(尤其是 C API 模块)的需求非常有限,那么这些和其他 CPython 替代方案值得考虑。如果您的应用程序对现有 Java 或 .NET 功能有很大依赖性,那么 Jython 和 IronPython 是不错的选择。

专注于有意义的改进

还有其他常见建议的提示和技巧可用于提高 Python 性能,例如:

  • 避免点符号,包括Math.sqrt()或myObj.foo()。

  • 使用字符串操作,例如join()方法。

  • 采用多项分配,例如a,b = 1,2。

  • 使用@functools.lru_cache装饰器。

如果您创建timeit测试,当将无效实践与理想实践进行比较时,您会看到巨大的改进。(顺便说一句,您应该学习如何使用timeit。)但是,如果同时满足以下两个条件,这些技巧只会对您的程序产生明显的影响:

  1. 编码已经以“错误的方式”完成,例如在大循环或理解中。

  2. 这些操作每次迭代都会花费大量时间。

如果循环的每次迭代只花费一秒钟,您可能不会注意到在一个语句中执行两个赋值所节省的一小部分时间。

考虑一下您的 Python 优化工作在哪些方面最重要。如果您关注可读性和一致性而不是性能,您可能会获得更多价值。如果你的项目标准是合并作业或消除点,那就去做吧。否则,请坚持使用标准,直到运行性能测试(timeit或profile)并发现问题。

猜你喜欢

转载自blog.csdn.net/qq_29639425/article/details/133673565