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

3
我感到困惑的是为什么这段代码:
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中的元素而不是按顺序进行,这就是我认为第一个实现函数的方式。

有人能给我解释一下吗?我真的很困惑。谢谢!

4个回答

7

你的两个版本代码都是一样的向量化。你创建的数组仅为第二个版本的向量化增加了开销。


NumPy的向量化并不涉及硬件向量化。如果编译器足够智能,它可能会使用硬件向量化,但NumPy并没有显式地使用AVX或任何其他指令集。

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

从那个意义上说,你的两个版本代码都是向量化的,但第二个版本浪费了大量的内存和内存带宽,用于写入和读取一个巨大的全是1的数组。

此外,即使我们谈论硬件级别的向量化,1 -版本也与另一个版本一样适合硬件级别的向量化。你只需要将标量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)

三分之二的时间都花在了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),而且这一切都不需要拷贝。

0

我肯定不是numpy专家,但我的猜测是第一个例子只使用了一个向量,而第二个则实际上先创建了一个长度为1的向量,然后再进行减法运算。后者需要双倍的内存和额外的一步来创建长度为1的向量。

在x86 CPU上,两者可能都是某种AVX指令,可以同时处理4个数字。当然,除非您正在使用具有大于向量长度的SIMD宽度的高级CPU,并且此CPU受到numpy的支持。


-1

情况A在mpu上只运行一个迭代器,而情况B有两个迭代器,涉及到两个与X一样大的向量,如果没有优化,这将需要在线程中进行大量的上下文切换。情况B是情况A的更通用版本...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接