【问题标题】:Why is subtraction faster when doing arithmetic with a Numpy array and a int compared to using vectorization with two Numpy arrays?为什么与使用两个 Numpy 数组的向量化相比,使用 Numpy 数组和 int 进行算术运算时减法更快?
【发布时间】:2019-10-19 02:54:15
【问题描述】:

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

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 中的元素而不是顺序操作,这是我假设的第一个实现函数。

由于我真的很困惑,有人可以为我解释一下吗?谢谢!

【问题讨论】:

    标签: python arrays numpy matrix linear-algebra


    【解决方案1】:

    您的代码的两个版本都是矢量化的。您为尝试向量化第二个版本而创建的数组只是开销。


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

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

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

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

    【讨论】:

      【解决方案2】:

      时间基本相同。正如其他人指出的那样,没有任何类型的硬件或多核并行化,只是解释 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),所有这些都无需复制。

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-11-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-03
            • 1970-01-01
            相关资源
            最近更新 更多