为什么使用 Numpy 数组和 int 进行算术比使用两个 Numpy 数组进行矢量化时减法更快?



我很困惑为什么这段代码:

start = time.time()
for i in range(1000000):
_ = 1 - np.log(X)
print(time.time()-start)

执行速度比此实现快:

start = time.time()
for i in range(1000000):
_ = np.subtract(np.ones_like(X), np.log(X))
print(time.time()-start)

我的理解是,它应该是相反的,因为在第二个实现中,我正在利用矢量化提供的加速,因为它能够同时操作 X 中的元素,而不是按顺序操作,这就是我假设第一个实现函数的方式。

有人可以为我阐明这一点,因为我真的很困惑?谢谢!

两个版本的代码都经过了相同的矢量化处理。您为尝试矢量化第二个版本而创建的数组只是开销。


NumPy 矢量化不是指硬件矢量化。如果编译器足够聪明,它最终可能会使用硬件矢量化,但 NumPy 没有明确使用 AVX 或任何东西。

NumPy 矢量化是指编写一次对整个数组进行操作的 Python 级代码,而不是使用一次对多个操作数进行操作的硬件指令。它是Python级别的矢量化,而不是机器语言级别的矢量化。与编写显式循环相比,这样做的好处是 NumPy 可以在 C 级循环而不是 Python 中执行工作,避免了大量的动态调度、装箱、拆箱、通过字节码评估循环的跳闸等。

从这个意义上说,两个版本的代码都是矢量化的,但第二个版本在编写和读取大量代码时浪费了大量内存和内存带宽。

此外,即使我们谈论的是硬件级矢量化,1 -版本也与其他版本一样适合硬件级矢量化。您只需将标量1加载到矢量寄存器的所有位置,然后照常进行。与第二个版本相比,它涉及的内存传输要少得多,因此仍然可能比第二个版本运行得更快。

时间基本相同。 正如其他人指出的那样,没有任何类型的硬件或多核并行化,只是解释型Python和编译numpy函数的混合。

In [289]: x = np.ones((1000,1000))
In [290]: timeit 1-np.log(x)                                                    
15 ms ± 1.94 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [291]: timeit np.subtract(np.ones_like(x), np.log(x))                        
18.6 ms ± 1.89 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

np.ones_like从定时循环中取出:

In [292]: %%timeit y = np.ones_like(x) 
...: np.subtract(y,np.log(x)) 
...:  
...:                                                                       
15.7 ms ± 441 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

2/3 的时间花在log功能上:

In [303]: timeit np.log(x)                                                      
10.7 ms ± 211 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [306]: %%timeit y=np.log(x) 
...: np.subtract(1, y)                                                                  
3.77 ms ± 5.16 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

1生成方式的变化是时间的一小部分。

使用"广播",使用标量和数组或数组和数组进行数学运算同样容易。

1,无论是标量(实际上是形状为()的数组(,都广播到(1,1(然后广播到(1000,1000(,所有这些都无需复制。

我当然不是 numpy 专家,但我的猜测是第一个示例只使用一个向量,第二个示例首先创建一个 1 向量,然后减去。后者需要双倍的内存量和一个额外的步骤来创建向量 1。

在x86 CPU上,两者都可能是某种AVX指令,一次处理4个数字。当然,除非您使用的是 SIMD 宽度大于矢量长度的花哨 CPU,并且 numpy 支持此 CPU。

案例 A 在 mpu 上只运行一个迭代器,而案例 B 在两个与 X 一样大的向量上有两个迭代器,如果不优化,则需要在线程中加载上下文切换。案例 B 是案例 A 的更通用版本...

相关内容

  • 没有找到相关文章

最新更新