【问题标题】:Performance comparison: Why is it faster to copy an entire numpy Matrix and then change one column than to just use numpy.column_stack?性能比较:为什么复制整个 numpy 矩阵然后更改一列比仅使用 numpy.column_stack 更快?
【发布时间】:2021-02-04 05:27:21
【问题描述】:

我正在尝试提高一些 Python 代码的性能。在该代码中,必须临时更改矩阵的一列(numpy-array)。

给定的代码如下所示:

def get_Ai_copy(A, b, i):
    Ai = A.copy()
    Ai[:,i] = b[:,0]
    return Ai

现在我认为不创建整个矩阵 A 的副本应该是一个很大的改进(在使用的示例中,矩阵是 500x500,所有条目都严格大于 0),而是使用 np.column_stack() 创建我需要的列中的一个新的临时矩阵,如下所示:

def get_Ai(A, b, i):
    return np.column_stack([A[:,:i], b, A[:,i+1:]])

我原以为这会大大提高性能,但事实证明,它实际上比给定的方法要慢:

我双向跑了 100 次并比较了平均运行时间:

number_tests = 100

copy_times = np.empty(number_tests)
stacking_times = np.empty(number_tests)

for j in range(number_tests):
    t0 = time.time()
    for i in range(500):
        Ai = get_Ai_copy(A, b, i)
    t1 = time.time()
    copy_times[j] = t1 - t0
    # print(f'-- Run # {j}: CPU time for copying Ai = %g seconds'%(t1 - t0))

    t0 = time.time()
    for i in range(500):
        Ai = get_Ai(A, b, i)
    t1 = time.time()
    stacking_times[j] = t1 - t0
    # print(f'-- Run # {j}: CPU time for column stacking Ai = %g seconds'%(t1 - t0))
    # print()

print(f'Copying times average: {np.mean(copy_times)}')
print(f'Stacking times average: {np.mean(stacking_times)}')

结果是:

平均复制次数:0.19957998037338257

平均堆叠次数:0.22774386405944824

我不明白为什么会这样。

是否有一些我没有看到的解释?复制整个数组是否比获取 3 个切片对缓存更友好?如果是这样,有谁知道,为什么?

侧面信息: 我在 Jupyter 笔记本上运行它,在带有 Intel i7 10750H(12mb 缓存)和 32GB Ram 的笔记本电脑上。 A 始终是一个非奇异矩阵,如果这很重要的话。

【问题讨论】:

  • column_stack 只是 concatenate 并检查以确保所有输入都是 2d(列)。它不保存任何复制。这是一个新数组,而不是原始数组。
  • 当人们经常对什么是副本和什么是视图感到困惑时,我建议使用.__array_interface__ 属性来查看底层数据的存储位置。对于view,“数据”值将相同或接近,而对于副本(如何生成)则完全不同。 view(例如切片)速度很快,因为它不复制任何底层数据,但从多个切片创建新数组需要一个副本。您可能需要阅读有关如何存储数组的 numpy 基础知识。
  • 哦,这很有意义。谢谢你们!

标签: python performance numpy


【解决方案1】:

numpy 数组的copy方法将触发代码,该代码将在本机代码中以最大 CPU 速度复制所有数组数据 - 如果每个元素是 500x500x8 字节,我们谈论的是大约 2MB 的数据 - 适合即使在 CPU 的缓存中也很舒适。 并且 numpy 只需为单个 Python 对象创建元数据。

另一方面,column_stack 运行一些 Python 代码(尽管不在细粒度对象上,否则会更糟),但最终 复制 数组(它占用了您的当前数组 - 不复制切片,但随后在内部调用 np.concatenate ,这会触发复制)。因此,您只需添加部分复制数据的开销,以及在过程中(在切片、连接等之间)按 10 个 Python 级数组对象的顺序创建一些杂耍 - 这弥补了 10%额外的时间。

【讨论】:

    猜你喜欢
    • 2013-09-26
    • 2023-03-13
    • 2017-06-07
    • 2012-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多