Python优化提速

前言

Python 相对于其他编程语言被认为较慢,主要有以下几个原因:

  1. 解释型语言:Python是一种解释型语言,而不是编译型语言。在程序执行时,Python解释器会逐行解释和执行代码,这会引入一定的开销。相比之下,编译型语言如C++在运行之前会将代码编译成机器码,因此执行速度更快。

  1. 动态类型和动态内存管理:Python是一种动态类型语言,变量的类型可以在运行时动态改变。这导致在运行时需要进行类型检查和转换,增加了运行时的开销。此外,Python还使用了自动的内存管理机制,即垃圾回收机制,用于自动分配和释放内存。垃圾回收的开销也会影响Python的性能。

  2. 全局解释锁(GIL):Python的标准解释器(CPython)中存在一个全局解释锁(GIL),它限制了同一时间只能有一个线程执行Python字节码。这意味着在多线程的情况下,Python无法有效地利用多核处理器的优势,导致在处理计算密集型任务时性能受限。但是,值得注意的是,在I/O密集型任务中,由于GIL会在I/O操作时释放,因此多线程可以提高性能。

  3. 部分库的实现方式:Python的一些常用库和模块使用了底层用CC++编写的扩展模块,这些模块的性能比纯Python代码高。然而,并非所有的库都使用这种方式,一些纯Python实现的库性能可能较低。

常规小技巧

  1. 列表推导(List Comprehension):使用列表推导可以将for循环转化为更高效的单行表达式。它可以通过在一个列表中使用for循环来创建另一个列表。列表推导通常比显式的for循环更快,因为它利用了底层的优化机制。

    numbers = [1, 2, 3, 4, 5]
    squared = [x**2 for x in numbers]
  2. map() 函数:使用map()函数可以将一个函数应用到一个可迭代对象的所有元素上,并返回一个新的可迭代对象。这种方式可以比显式的for循环更快。

    numbers = [1, 2, 3, 4, 5]
    squared = list(map(lambda x: x**2, numbers))
  3. 使用numpy库:如果你在处理大量的数值数据,使用numpy库可以显著加快for循环的运算。numpy是一个高性能的科学计算库,提供了多维数组和向量化操作。

    import numpy as np

    numbers = np.array([1, 2, 3, 4, 5])
    squared = numbers**2
  4. 并行计算:如果你需要对一个较大的数据集进行计算,并且这些计算是相互独立的,你可以考虑使用并行计算来加速for循环。Python中有一些库,如multiprocessingconcurrent.futures,可以方便地实现并行计算。

    from multiprocessing import Pool

    def square(x):
    return x**2

    numbers = [1, 2, 3, 4, 5]

    with Pool() as pool:
    squared = pool.map(square, numbers)

Pandas加速循环

  1. Iterrows()为每一行返回一个 Series,因此它以索引对的形式遍历DataFrame,以Series的形式遍历目标列,这使得它比标准循环更快。

import pandas as pd

# 创建示例DataFrame
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})

# 使用iterrows()遍历DataFrame
for index, row in df.iterrows():
# 访问每一行的数值
a_value = row['A']
b_value = row['B']

# 进行操作
result = a_value + b_value
print(result)
  1. apply本身并不快,但与DataFrame结合使用时,它具有很大的优势。这取决于apply表达式的内容。 如果它可以在Cython中执行,那么apply要快得多。

# 创建示例DataFrame
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})

# 使用apply()遍历DataFrame的每一行
def add_values(row):
# 访问每一行的数值
a_value = row['A']
b_value = row['B']

# 进行操作
return a_value + b_value

# 使用apply()进行循环操作
result = df.apply(add_values, axis=1)
print(result)
  1. 利用向量化Pandas Vectorization的优势来创建真正高效的代码

import numpy as np

bins = [0, 80, 90, np.inf]
labels = ['C', 'B', 'A']
df['grade'] = pd.cut(df['score'], bins=bins, labels=labels)

以上示例中,通过直接对整个DataFrameSeries进行操作,避免了显式的循环,并且利用了底层的C实现来提高运算效率。向量化操作通常比迭代方式更高效,并且能够简化代码,提高可读性。

itertools加速循环

itertools 是一个强大的 Python 模块,提供了许多用于迭代和组合的工具函数。虽然 itertools 本身并不会直接加速循环,但可以通过它提供的函数来优化循环的效率。

下面是一些使用 itertools 加速循环的示例:

  1. itertools.chain(): 如果你有多个可迭代对象,可以使用 itertools.chain() 将它们连接起来,从而避免多次嵌套循环。

import itertools

# 传统方式
for i in range(10):
for j in range(5):
print(i, j)

# 使用 itertools.chain()
for i, j in itertools.product(range(10), range(5)):
print(i, j)
  1. itertools.islice(): 当你只需要迭代可迭代对象的一部分时,可以使用 itertools.islice() 来切片迭代。

import itertools

# 传统方式
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for item in my_list[3:7]:
print(item)

# 使用 itertools.islice()
for item in itertools.islice(my_list, 3, 7):
print(item)
  1. itertools.compress(): 如果你想根据某个条件过滤迭代对象,可以使用 itertools.compress() 来实现。

import itertools

# 传统方式
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
my_condition = [True, False, True, False, True, False, True, False, True, False]

for item, condition in zip(my_list, my_condition):
if condition:
print(item)

# 使用 itertools.compress()
for item in itertools.compress(my_list, my_condition):
print(item)

这只是一些使用 itertools 加速循环的示例。根据具体的需求,你还可以探索其他 itertools 函数,如 itertools.filterfalse()itertools.groupby() 等。

Numba makes Python code fast

Numba是一个适用于使用NumPy数组、函数和循环的Python即时编译器。使用Numba的常见方式是通过应用修饰符来编译函数。当调用使用Numba修饰的函数时,它会被即时编译为机器码,从而使代码的全部或部分以本地机器码速度运行。

安装

Numba 可用作Anaconda Python 发行版的conda包 :

$ conda install numba

Numba 也有可用的pip:

$ pip install numba

装饰器@jit来尝试和加速一些功能

from numba import jit
import numpy as np

x = np.arange(100).reshape(10, 10)

@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def go_fast(a): # Function is compiled to machine code when called the first time
trace = 0.0
for i in range(a.shape[0]): # Numba likes loops
trace += np.tanh(a[i, i]) # Numba likes NumPy functions
return a + trace # Numba likes NumPy broadcasting

print(go_fast(x))

其他感兴趣的事情:

Numba 有很多装饰器,我们已经看到了@jit,但也有:

  • @njit- 这是一个别名,因为@jit(nopython=True)它很常用!

  • @vectorize- 生成 NumPy ufunc(支持所有方法ufunc)。文档在这里

  • @guvectorize- 生成 NumPy 广义ufuncs。 文档在这里

  • @stencil- 声明一个函数作为类似模板操作的内核。 文档在这里

  • @jitclass- 对于 jit 感知类。文档在这里

  • @cfunc- 声明一个用作本机回调的函数(从 C/C++ 等调用)。文档在这里

  • @overload- 注册您自己的函数实现以在 nopython 模式下使用,例如@overload(scipy.special.j0). 文档在这里

一些装饰器中可用的额外选项:

ctypes/cffi/cython 互操作性:

  • cffi- 模式支持CFFI函数的调用nopython

  • ctypes-模式支持调用ctypesnopython包装函数。

  • Cython 导出的函数是可调用的

GPU 目标:

Numba 可以针对Nvidia CUDA GPU。您可以用纯 Python 编写内核并让 Numba 处理计算和数据移动(或明确地执行此操作)。单击以获取有关 CUDA的 Numba 文档。

------------------------------- 本文结束啦❤感谢您阅读-------------------------------
赞赏一杯咖啡