我在thinkpad carbon x1gen7上使用ubuntu22.10,它有8个内核。以下代码将使用矢量化:
import numpy as np np.random.rand(10000) + 1
据我所知,10000个数字中的N个将由一个内核并行处理。然后,下一个N,依此类推。这就是矢量化。它只需要一条指令,就可以在一个内核上将其应用于许多数据点(这个内核部分将矢量化与并行化进行了对比)我如何找到我的机器的N?
kxe2p93d1#
TL;DR:np.random.rand是矢量化的,但标量和效率低下,而array + 1是矢量化的,并利用宽SIMD指令。
np.random.rand
array + 1
首先,Numpy矢量化定义如下:NumPy将数组处理交给了C,C中的循环和计算比Python中快得多。为了利用这一点,使用NumPy的程序员消除了Python循环,转而使用数组到数组的操作。向量化可以指C卸载和NumPy代码结构化来利用它。更一般地说,在Python中,向量化意味着Python代码调用本机编译的函数(通常在C/C中),并且 * 不是 * 它特别优化以有效地使用CPU功能(如注解中的@hpaulj所示)。对于使用编译器或C/C等本机语言的人来说,这个术语有不同的含义。在这个不同的上下文中,向量化意味着使用SIMD instructions。
以下分析是在Linux上完成的,其中Numpy 1.24.2(来自标准包)和CPython 3.11.2在x86-64 i5- 9600 KF处理器上运行。
np.random.rand(10000)是矢量化的(它调用C代码来进行实际计算),但它不使用SIMD指令。事实上,它目前在C中也没有得到很好的优化。
np.random.rand(10000)
一个低级的分析表明,很大一部分花费在两个函数上:mt19937_gen(~13%)和random_standard_uniform_fill(~6%)。大部分时间似乎都浪费在Cython检查、开销和许多小函数上(~81%)。mt19937是一个著名的32位Mersenne Twister。代码似乎来自Cython代码(可能调用C++标准库)。几乎在mt19937_gen中花费的大部分时间都位于以下汇编循环中:
mt19937_gen
random_standard_uniform_fill
mt19937
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指令(这可以通过使用硬件性能计数器看到)。
array + 1使用SIMD指令进行了优化。更具体地说,它在x86-64处理器上使用AVX(如果您的机器上可用,否则使用SSE)。AVX能够在每个指令上运行4个双精度数。现代主流处理器通常每个周期可以运行2个AVX指令,因此每个周期可以计算8个双精度数。话虽如此,请注意array + 1会生成一个新的临时数组,这是相当昂贵的。如果可以使用np.add(array, 1, out=array),通常最好执行就地操作。
np.add(array, 1, out=array)
1条答案
按热度按时间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
中花费的大部分时间都位于以下汇编循环中:这是一个纯标量整数循环:有没有SIMD指令.它似乎并没有特别优化乍一看.请注意,处理器可以并行执行许多指令(IPC多达5或6,在代码没有瓶颈或停顿),由于ILP(指令级并行,一个单一的CPU核心可以找到和利用).
另一个函数花费大部分时间调用另一个函数(可能是前一个):
这个循环显然效率低下,根本没有优化。它很可能是在数组上迭代的循环:它调用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)
,通常最好执行就地操作。