我目前面临Python代码的性能问题,特别是执行速度问题.我已经发现了代码中的一个瓶颈,并正在寻求有关如何对其进行优化以获得更好性能的建议.

这是代码的简化版本:

import numpy as np

def merge_arrays(arr: list, new: list) -> list:
  if len(arr) != len(new):
    raise ValueError(f'Length of <inds> is {len(arr)} but length of <new> is {len(new)}')
  else:
    return transform_array(zip(arr, new), [])

def transform_array(arr: list, r: list):
  for x in arr:
    if isinstance(x, (list, tuple, np.ndarray)):
        transform_array(x, r)
    else:
        r.append(x)
  return r

COUNT = 100000

pips = [(np.arange(5), np.arange(5)) for _ in range(COUNT)]
b = [np.arange(50, 65) for _ in range(COUNT)]
ang = [np.arange(50, 55) for _ in range(COUNT)]
dist = [np.arange(50, 55) for _ in range(COUNT)]

result = [merge_arrays(y, [np.append(b[i][1:], [dist[i][p], ang[i][p]]) for p, i in enumerate(x)]) for x, y in pips]

当处理大型数据集时,特别是当输入列表包含大量元素时,就会出现这个问题.这会导致执行时间缓慢,这对于我的应用程序来说是不希望的.

我try 通过使用列表解析来优化代码并避免不必要的条件判断,但性能仍然不令人满意.

我正在寻找有关如何重构或优化此代码以提高其性能的建议,尤其是在瓶颈似乎所在的transform_Array函数中.

任何见解或替代方法都将不胜感激.谢谢!

推荐答案

Reducing the overhead of isinstance

一个问题是代码将9_500_000调用到isinstance.对于解释的代码来说,这是太多了.更不用说类型的内省从来都不是很快的.如果我们可以假设某些项目具有众所周知的类型并且不需要测试,那么代码就可以改进.

Numpy数组有一个解决方案,因为它们是类型化的,而不是CPython二元组和CPython列表.请注意,将(list, tuple, np.ndarray)存储在变量中可以使速度提高10%.以下是基于此的更快代码:

T1 = (list, tuple)
T2 = (np.ndarray,)

def transform_array(arr: list, r: list):
  for x in arr:
    if isinstance(x, T1):
        transform_array(x, r)
    elif isinstance(x, T2):
        if x.dtype == object:
            # Generic case
            transform_array(x, r)
        else:
            # No need to check all items: 
            # Push back all the items of the array in the list
            r.extend(x.tolist())
    else:
        r.append(x)
  return r

一旦完成优化,95%的时间都花在result = ...个列表理解上,甚至不是merge_arrays个呼叫上.其中50%的时间花在了np.append上.


减少Numpy通话的费用

列表理解中的麻木调用相当昂贵.这主要是因为Numpy的设计目的不是对非常小的数组执行操作,也不是手动提取许多纯量项.除此之外,np.append旨在将数组(而不是列表)附加到数组,因此它首先将列表转换为一个数组,该数组也相当昂贵(更不用说它需要判断其中所有项的类型).

我们可以通过手动操作来减少管理费用. 这是一个更快的实现:

#import numba as nb
#@nb.njit(['(int32[:], int32[:], int32[:], int64)', '(int64[:], int64[:], int64[:], int64)'])
def build_item(b_i, dist_i, ang_i, p):
    out = np.empty(b_i.size+1, dtype=b_i.dtype)
    out[:-2] = b_i[1:]
    out[-2] = dist_i[p]
    out[-1] = ang_i[p]
    return out

# [...]

result = [merge_arrays(y, [build_item(b[i], dist[i], ang[i], p) for p, i in enumerate(x)]) for x, y in pips]

result = ...行在我的机器上需要1.66秒,而初始代码需要5.92秒.因此是3.6 times faster.

Python相关问答推荐

合并其中一个具有重叠范围的两个框架的最佳方法是什么?

如何在Python中按组应用简单的线性回归?

如何在超时的情况下同步运行Matplolib服务器端?该过程随机挂起

使用Python Cerberus初始化一个循环数据 struct (例如树)(v1.3.5)

使用图片生成PDF Django rest框架

过载功能是否包含Support Int而不是Support Int?

如何在Python中使用ijson解析SON期间检索文件位置?

如何修复使用turtle和tkinter制作的绘画应用程序的撤销功能

Polars比较了两个预设-有没有方法在第一次不匹配时立即失败

大Pandas 胚胎中产生组合

Polars LazyFrame在收集后未返回指定的模式顺序

将输入管道传输到正在运行的Python脚本中

如何使用LangChain和AzureOpenAI在Python中解决AttribeHelp和BadPressMessage错误?

切片包括面具的第一个实例在内的眼镜的最佳方法是什么?

在np数组上实现无重叠的二维滑动窗口

Pre—Commit MyPy无法禁用非错误消息

递归访问嵌套字典中的元素值

Pandas—在数据透视表中占总数的百分比

matplotlib + python foor loop

以逻辑方式获取自己的pyproject.toml依赖项