numpy 在我的机器上,一次向量运算可以处理多少数据?

8i9zcol2  于 2023-03-23  发布在  其他
关注(0)|答案(1)|浏览(93)

我在thinkpad carbon x1gen7上使用ubuntu22.10,它有8个内核。
以下代码将使用矢量化:

import numpy as np
np.random.rand(10000) + 1

据我所知,10000个数字中的N个将由一个内核并行处理。然后,下一个N,依此类推。这就是矢量化。它只需要一条指令,就可以在一个内核上将其应用于许多数据点(这个内核部分将矢量化与并行化进行了对比)
我如何找到我的机器的N?

kxe2p93d

kxe2p93d1#

TL;DR:np.random.rand是矢量化的,但标量和效率低下,而array + 1是矢量化的,并利用宽SIMD指令。

矢量化的含义

首先,Numpy矢量化定义如下:
NumPy将数组处理交给了C,C中的循环和计算比Python中快得多。为了利用这一点,使用NumPy的程序员消除了Python循环,转而使用数组到数组的操作。向量化可以指C卸载和NumPy代码结构化来利用它。
更一般地说,在Python中,向量化意味着Python代码调用本机编译的函数(通常在C/C中),并且 * 不是 * 它特别优化以有效地使用CPU功能(如注解中的@hpaulj所示)。
对于使用编译器或C/C
等本机语言的人来说,这个术语有不同的含义。在这个不同的上下文中,向量化意味着使用SIMD instructions

np.random.rand使用标量未优化代码

以下分析是在Linux上完成的,其中Numpy 1.24.2(来自标准包)和CPython 3.11.2在x86-64 i5- 9600 KF处理器上运行。

np.random.rand(10000)是矢量化的(它调用C代码来进行实际计算),但它不使用SIMD指令。事实上,它目前在C中也没有得到很好的优化。

一个低级的分析表明,很大一部分花费在两个函数上:mt19937_gen(~13%)和random_standard_uniform_fill(~6%)。大部分时间似乎都浪费在Cython检查、开销和许多小函数上(~81%)。mt19937是一个著名的32位Mersenne Twister。代码似乎来自Cython代码(可能调用C++标准库)。
几乎在mt19937_gen中花费的大部分时间都位于以下汇编循环中:

loop:
    and    esi,0x80000000
    add    rdx,0x4
    mov    ecx,esi
    mov    esi,DWORD PTR [rdx]
    mov    eax,esi
    and    eax,0x7fffffff
    or     eax,ecx
    mov    ecx,eax
    and    eax,0x1
    neg    eax
    shr    ecx,1
    xor    ecx,DWORD PTR [rdx+0x630]
    and    eax,0x9908b0df
    xor    eax,ecx
    mov    DWORD PTR [rdx-0x4],eax
    cmp    rdx,rdi
    jne    loop

这是一个纯标量整数循环:有没有SIMD指令.它似乎并没有特别优化乍一看.请注意,处理器可以并行执行许多指令(IPC多达5或6,在代码没有瓶颈或停顿),由于ILP(指令级并行,一个单一的CPU核心可以找到和利用).
另一个函数花费大部分时间调用另一个函数(可能是前一个):

loop:
    mov   rdi,QWORD PTR [rbp+0x0]
    call  QWORD PTR [rbp+0x18]            <---  very slow
    movsd QWORD PTR [r13+rbx*8+0x0],xmm0
    add   rbx,0x1
    cmp   r12,rbx
    jne   loop                            <---  quite slow

这个循环显然效率低下,根本没有优化。它很可能是在数组上迭代的循环:它调用10_000次mt19937_gen,以便生成存储在数组的每个项中的随机数(如注解中的@hpaulj所示)。
请注意,Cython调用的其他函数实际上并没有大量使用任何SIMD指令(这可以通过使用硬件性能计数器看到)。

使用SIMD指令优化基本元素操作

array + 1使用SIMD指令进行了优化。更具体地说,它在x86-64处理器上使用AVX(如果您的机器上可用,否则使用SSE)。AVX能够在每个指令上运行4个双精度数。现代主流处理器通常每个周期可以运行2个AVX指令,因此每个周期可以计算8个双精度数。
话虽如此,请注意array + 1会生成一个新的临时数组,这是相当昂贵的。如果可以使用np.add(array, 1, out=array),通常最好执行就地操作。

相关问题