为什么numpy数组方法/属性比对应的numpy全局函数运行速度更快?
1. 全局函数的泛用性适配开销
所有np.xxx形式的全局函数都是为兼容任意类数组输入设计的:不管你传的是列表、元组、自定义可转数组的对象,还是numpy数组,它都能处理。所以调用时第一步必然会执行:
- 入参类型校验
- 调用
np.asarray()尝试将输入转为标准ndarray - 处理多维度、多dtype的兼容逻辑
而数组本身的方法/属性是直接挂载在ndarray实例上的,完全不需要执行上面这几步校验和转换,天然省了这部分开销。对于real、imag这类本身执行成本极低的操作,这部分开销的占比会被无限放大,也就是你测试中出现十几到几十倍性能差的核心原因。
2. 属性访问是直接内存操作,零额外成本
x.real、x.imag这类不是函数调用,是numpy数组底层内存布局里预存的视图指针:复数数组的实部、虚部在内存中是连续分段存储的,访问这两个属性时直接返回对应偏移的内存视图,不需要任何计算、也不需要拷贝数据。
而对应的np.real(x)要先走完上面说的入参校验流程,再去访问数组的real属性,中间多了好几层函数调用,速度自然慢很多。
3. 部分方法默认行为和全局函数不同
你测试中sort有5倍左右的性能差,核心原因是二者默认行为不一致:
a.sort()是原地排序,直接修改原数组的内存顺序,不需要申请新的内存空间,也不需要做数据拷贝np.sort(a)默认返回排序后的新数组,需要申请和原数组一样大的内存,还要做全量数据拷贝,开销自然大很多
如果你给np.sort指定相同的排序算法、或者让a.sort()也同步做数据拷贝,二者的性能差会收窄很多。
4. 全局函数调用链更长
即使是np.sum、np.max这类逻辑上和数组方法完全一致的操作,全局函数的内部调用链也更长:比如np.max内部会先调用np.asanyarray转换输入,再调用np.amax,最后才调度到数组实例的max方法,中间多了2~3层的函数调用、参数解析开销。
这部分开销对于小数组来说占比很高,对于超大数组来说,因为本身计算耗时很长,这部分开销的占比会被稀释,所以你会观察到提速幅度受数组大小影响。
关于你提到的「是否和原地操作与新数组创建有关」:只有
sort这类操作的差异来源于这个原因,sum、real、conj这类不会修改原数组的操作,性能差异和原地操作无关,核心就是前面说的校验和调用链开销。
内容的提问来源于stack exchange,提问作者silence_of_the_lambdas

