You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从解释器视角探究NumPy数组覆盖:为何复用数组反而更慢?

为何NumPy中循环重新分配数组比预先分配后覆盖更快?

测试环境与代码重现

测试环境:Python 3.12.1,NumPy 1.26.2(MKL编译,conda默认版本)

函数foo:每次循环重新分配数组

import numpy as np

def foo():
    for i in range(100):
        H = np.random.rand(1000, 1000)

%timeit -r 100 foo()

函数baaz:预先分配后循环覆盖

def baaz():
    H = np.zeros((1000, 1000))
    for i in range(100):
        H[:, :] = np.random.rand(1000, 1000)

%timeit -r 100 baaz()

性能差异的核心原因

  • 内存拷贝开销远大于内存分配/释放
    foo里的np.random.rand直接分配新内存并填充随机数,现代操作系统的内存管理器对这类大块连续内存的分配、释放做了高度优化(比如内存池复用空闲块),额外开销几乎可以忽略。而baaz的H[:, :] = ...操作,本质是先让np.random.rand生成临时数组,再把临时数组的所有元素逐拷贝到H中——1000×1000的数组有100万个float64元素,每次循环要拷贝8MB数据,100次循环就是800MB的拷贝量,这部分开销远超过内存分配的成本。

  • NumPy切片赋值的机制限制
    即使数组形状完全匹配,NumPy的切片赋值也会触发完整的元素级拷贝,无法直接跳过临时数组把数据写入目标内存。而np.random.rand本身没有默认的in-place生成写法,导致baaz的复用方式并没有真正节省开销,反而多了一步冗余的拷贝操作。

  • 与Julia的差异根源
    Julia中复用数组更快,是因为它支持直接将随机数写入预先分配的数组(比如rand!(H)),完全避免临时数组和拷贝。但你当前的baaz写法没有利用NumPy的in-place生成接口,所以没发挥出内存复用的优势。

优化后的复用写法

改用NumPy随机数生成器的out参数,直接在预先分配的数组中生成随机数,就能体现内存复用的性能优势:

def baaz_opt():
    rng = np.random.default_rng()
    H = np.zeros((1000, 1000))
    for i in range(100):
        rng.random(size=(1000,1000), out=H)

%timeit -r 100 baaz_opt()

这个版本没有临时数组,也没有拷贝操作,性能会明显超过foo。

内容的提问来源于stack exchange,提问作者Paradoxy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 09:05:19