我正在对算法进行时间复杂度分析,需要知道某些numpy操作有哪些复杂性。
对于一些人,我认为它们与基础数学运算相匹配。像np.dot(array1, array2)
一样是O(n)。对于其他人,我不太确定。例如,np.array(my_array)
O(1)?还是O(n)?它只是重新分配一个指针,还是在列表上迭代并复制出每个值?
我想确定每个操作的复杂性。有什么地方我可以找到这些信息吗?或者我应该假设它们与数学运算相匹配?
BigO复杂性通常不用于Python和numpy。它衡量代码如何随问题规模扩展。这在像C这样的编译语言中很有用。但是这里的代码是解释的Python和编译代码的混合。两者都可以具有相同的bigO,但解释版本将慢几个数量级。这就是为什么大多数关于提高numpy速度的SO问题,谈论'去除循环'和'向量化'。
也很少有操作是纯O(n);大多数是混合。有一个设置成本,加上每个元素的成本。如果每元素成本很小,则设置成本占主导地位。
如果从列表开始,则在列表上迭代通常会更快,因为将列表转换为数组会产生大量开销(O(n))。
如果您已经有数组,那么尽可能避免(python级别)迭代。迭代是大多数计算的一部分,但是numpy允许你在更快的编译代码中做很多事情(更快的O(n))。
在某些时候,你必须了解numpy如何存储其数组。 view
和copy
之间的区别很重要。视图实际上是O(1),副本O(n)。
通常你会看到SO答案做timeit
速度比较。我经常添加警告,结果可能会随着问题的大小而变化。更好的答案将解决各种尺寸问题,并在一个漂亮的情节上显示结果。结果通常是直线(O(n))和曲线(O(1)和O(n)组分的不同混合物)的混合物。
你特意问过np.array
。以下是一些示例时间:
In [134]: %%timeit alist = list(range(1000))
...: np.array(alist)
67.9 µs ± 839 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [135]: %%timeit alist = list(range(10))
...: np.array(alist)
2.19 µs ± 9.88 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [136]: %%timeit alist = list(range(2000))
...: np.array(alist)
134 µs ± 1.98 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
复制一个数组:
In [137]: %%timeit alist = list(range(2000)); arr=np.array(alist)
...: np.array(arr)
1.77 µs ± 24.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
没有副本:
In [138]: %%timeit alist = list(range(2000)); arr=np.array(alist)
...: np.array(arr, copy=False)
237 ns ± 1.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
从字符串列表:
In [139]: %%timeit alist = [str(i) for i in range(2000)]
...: np.array(alist, dtype=int)
286 µs ± 4.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
numpy
中的几乎所有计算都是O(n)。如果它涉及数组的每个元素,速度将取决于数组的大小。一些数组操作是O(1),例如重新整形,因为它们实际上并没有对数据做任何事情;它们改变了形状和步幅等属性。
搜索问题通常比O(n)增长得快;通常numpy
不是那种问题的最佳选择。智能使用Python列表和词典可以更快。
对于具体示例np.array(my_array)
,因为它需要遍历my_array的所有元素,分配内存并初始化值,它以线性时间发生。
有一个python模块big_O,可用于从执行时间分析函数的复杂性。
有关更多信息,请参阅此link