np.array行提取耗时疑问:为何子数组访问耗时更长?
为什么
x的访问耗时远小于x[a]? 这是个特别有意思的反直觉问题,拆解开numpy的底层操作逻辑就一目了然了:
1. %timeit x其实没做“实际工作”
当你执行%timeit x时,Python只是返回了变量x的内存引用——说白了就是告诉解释器“这个变量在内存里的位置是啥”,完全没有触碰数组里的实际数据,也没有任何计算、内存分配或复制操作。这种纯引用操作的耗时几乎可以忽略,所以才会只有23纳秒。
2. x[a]是完整的“数组索引+数据复制”流程
而x[a]就不一样了,numpy要完成一整套实打实的操作:
- 首先校验索引
a的合法性:确认它符合numpy索引规则,检查索引的维度、取值范围是否正确; - 然后计算结果数组的形状:根据原数组
x的形状和a的长度,确定新数组的维度(这里x是(100,20),a是长度20的数组,所以x[a]会生成一个(20,20)的新数组); - 最后也是最耗时的步骤:分配新内存+复制数据。numpy会为新数组开辟独立的内存空间,然后把原数组中对应索引位置的元素逐一复制过去。内存读写的开销远大于单纯的CPU运算,这就是1.7微秒耗时的核心原因。
3. 矩阵乘法的类似现象也源于同一逻辑
你提到的x.T.dot(x)和x[a].T.dot(x[a])的差异,本质也是一样的:
x.T.dot(x)直接基于原数组的连续内存做运算,numpy可以调用BLAS等高效线性代数库做深度优化,没有额外的前置开销;- 而
x[a].T.dot(x[a])首先要完成上面说的索引+数据复制步骤,生成新数组后再做矩阵乘法,自然总耗时会更长。
额外提个小技巧:如果你的索引是连续的(比如这里的a=np.arange(20)),可以用切片x[:20]代替数组索引——切片返回的是原数组的视图(view),不会复制数据,耗时会和x的引用操作接近很多!
内容的提问来源于stack exchange,提问作者user8648470
相关产品推荐
相关产品推荐

