numpy ndarray 是均匀的和矩形的(子数组必须具有相同的长度)因为它在引擎盖下使用 C 数组吗?
Is numpy ndarray homogeneous and rectangular (sub-arrays must be the same length) because it uses C array under the hood?
Numpy ndarray 必须具有相同类型的所有元素,并且同一级别上的所有子数组必须具有相同的长度。这些属性也是 C 多维数组的属性。 numpy ndarray 是否具有这些属性纯粹是因为它是在 C 数组之上实现的?创建快速多维数组实现是否真的需要这些属性?
Is it the case that numpy ndarray have those properties purely because it is implemented on top of C array?
没有。在内部使用 C 并不是真正导致 Numpy 做出此选择的原因。事实上,Numpy 数组只是一个原始的 contiguous 内部内存缓冲区(动态分配)。 Numpy 实际上并没有在其自己的实现中使用 C 数组。它只使用 C 指针。视图是 Python 对象,引用缓冲区并处理一些元信息,如步幅、形状、类型等。Python 用户总是在视图上操作,因为原始缓冲区不能直接 read/written。
事实上,在 C 中创建锯齿状数组(即包含可变大小数组的数组)并不是很难,但需要手动创建(即标准不直接支持)。对于 2D 锯齿状数组,这通常是通过分配 T* 项的数组然后为 N sub-arrays 执行 N 分配来完成的。 sub-arrays 不保证连续存储。
关键是交错数组效率不高,因为内存 fragmentation/diffusion 和 non-contiguity。此外,Numpy 提供的许多功能在锯齿状数组 中无法(有效地)实现。例如,为具有跨步的其他视图创建 sub-view 会很棘手。在多轴上工作的操作(例如 np.sum(2D_array, axis=0)
)必须重新定义,以便它对锯齿状数组有意义。它还会使实施变得更加复杂。
因此,他们选择不实施交错数组,而只选择 ND-array。请注意,Numpy 最初是为科学家特别是物理学家创建的,他们很少需要锯齿状数组但关心 high-performance。通过分配 2 个 Numpy 数组可以相对有效地实现锯齿状数组:1 个数组连接所有行和一个包含 start/end 偏移量的 slice-based 数组。
Are those properties really required to create a fast multidimensional array implementation?
拥有同类类型对性能至关重要。动态类型强制对每个昂贵的项目进行类型检查。此外,动态类型通常需要一个额外的昂贵的间接寻址(例如,数组只存储 pointers/references 而不是对象本身)并且对对象的访问会导致内存 fragmentation/diffusion。与 addition/subtraction/multiplication 这样的基本数值运算相比,这样的运算非常昂贵。此外,对象的生命周期肯定必须小心控制(例如垃圾收集),就像 CPython 那样。事实上,一个 CPython list of list 的行为就像那样,而且效率很低。您可以创建 Numpy 数组对象的 Numpy 数组,但这也是低效的。
至于矩形数组,它依赖于 use-case,但这至少对矩阵乘法和 matrix-vector 乘积至关重要(如 BLAS 对 连续 数组进行操作,行与行之间可能有一个跨度),以及不在最连续维度上工作的操作(编译器可以使用 恒定跨度进行更积极的优化)。更不用说上面指定的额外开销(例如额外检查和内存fragmentation/diffusion)。
Numpy ndarray 必须具有相同类型的所有元素,并且同一级别上的所有子数组必须具有相同的长度。这些属性也是 C 多维数组的属性。 numpy ndarray 是否具有这些属性纯粹是因为它是在 C 数组之上实现的?创建快速多维数组实现是否真的需要这些属性?
Is it the case that numpy ndarray have those properties purely because it is implemented on top of C array?
没有。在内部使用 C 并不是真正导致 Numpy 做出此选择的原因。事实上,Numpy 数组只是一个原始的 contiguous 内部内存缓冲区(动态分配)。 Numpy 实际上并没有在其自己的实现中使用 C 数组。它只使用 C 指针。视图是 Python 对象,引用缓冲区并处理一些元信息,如步幅、形状、类型等。Python 用户总是在视图上操作,因为原始缓冲区不能直接 read/written。
事实上,在 C 中创建锯齿状数组(即包含可变大小数组的数组)并不是很难,但需要手动创建(即标准不直接支持)。对于 2D 锯齿状数组,这通常是通过分配 T* 项的数组然后为 N sub-arrays 执行 N 分配来完成的。 sub-arrays 不保证连续存储。
关键是交错数组效率不高,因为内存 fragmentation/diffusion 和 non-contiguity。此外,Numpy 提供的许多功能在锯齿状数组 中无法(有效地)实现。例如,为具有跨步的其他视图创建 sub-view 会很棘手。在多轴上工作的操作(例如 np.sum(2D_array, axis=0)
)必须重新定义,以便它对锯齿状数组有意义。它还会使实施变得更加复杂。
因此,他们选择不实施交错数组,而只选择 ND-array。请注意,Numpy 最初是为科学家特别是物理学家创建的,他们很少需要锯齿状数组但关心 high-performance。通过分配 2 个 Numpy 数组可以相对有效地实现锯齿状数组:1 个数组连接所有行和一个包含 start/end 偏移量的 slice-based 数组。
Are those properties really required to create a fast multidimensional array implementation?
拥有同类类型对性能至关重要。动态类型强制对每个昂贵的项目进行类型检查。此外,动态类型通常需要一个额外的昂贵的间接寻址(例如,数组只存储 pointers/references 而不是对象本身)并且对对象的访问会导致内存 fragmentation/diffusion。与 addition/subtraction/multiplication 这样的基本数值运算相比,这样的运算非常昂贵。此外,对象的生命周期肯定必须小心控制(例如垃圾收集),就像 CPython 那样。事实上,一个 CPython list of list 的行为就像那样,而且效率很低。您可以创建 Numpy 数组对象的 Numpy 数组,但这也是低效的。
至于矩形数组,它依赖于 use-case,但这至少对矩阵乘法和 matrix-vector 乘积至关重要(如 BLAS 对 连续 数组进行操作,行与行之间可能有一个跨度),以及不在最连续维度上工作的操作(编译器可以使用 恒定跨度进行更积极的优化)。更不用说上面指定的额外开销(例如额外检查和内存fragmentation/diffusion)。