gpt4 book ai didi

python - 了解 Numpy 中的矢量化与通过 Numexpr 进行矢量化表达式的多线程之间的区别

转载 作者:太空狗 更新时间:2023-10-29 21:49:59 24 4
gpt4 key购买 nike

我对 NumPy 据说是对其算术数组操作进行矢量化的概念感到有点挣扎:它是否克服了 Python 的 GIL,因为 NumPy 的一部分是用 C 实现的?另外,Numexpr 是如何工作的呢?如果我理解正确的话,它通过优化的 JIT 运行代码并启用多线程,从而克服了 Python 的 GIL。

“真正的”矢量化不是更像是多进程而不是多线程吗?

最佳答案

在某些情况下,NumPy 可能会使用一个库,该库使用多个进程来进行处理,从而将负担分散到多个内核上。然而,这取决于库,与 NumPy 中的 python 代码没有太大关系。所以,是的,如果不是用 python 编写的,NumPy 和任何其他库都可以克服这些限制。甚至有一些库提供 GPU 加速功能。

NumExpr 使用相同的方法来绕过 GIL。从他们的主页:

此外,numexpr 直接在其内部虚拟机中实现了对多线程计算的支持,该虚拟机是用 C 语言编写的。这允许绕过 Python 中的 GIL

但是,NumPy 和 NumExpr 之间存在一些根本差异。 Numpy 专注于为数组操作创建一个良好的 Pythonic 接口(interface),NumExpr 具有更窄的范围和自己的语言。当 NumPy 执行操作数为数组的计算 c = 3*a + 4*b 时,会在过程中创建两个临时数组(3*a4 *b).在这种情况下,NumExpr 可能会优化计算,以便在不使用任何中间结果的情况下逐个元素地执行乘法和加法。

这会导致 NumPy 发生一些有趣的事情。下面的测试是在4核8线程的i7处理器上进行的,使用iPython的%timeit进行了计时:

import numpy as np
import numexpr as ne

def addtest_np(a, b): a + b
def addtest_ne(a, b): ne.evaluate("a+b")

def addtest_np_inplace(a, b): a += b
def addtest_ne_inplace(a, b): ne.evaluate("a+b", out=a)

def addtest_np_constant(a): a + 3
def addtest_ne_constant(a): ne.evaluate("a+3")

def addtest_np_constant_inplace(a): a += 3
def addtest_ne_constant_inplace(a): ne.evaluate("a+3", out=a)

a_small = np.random.random((100,10))
b_small = np.random.random((100,10))

a_large = np.random.random((100000, 1000))
b_large = np.random.random((100000, 1000))

# results: (time given is in nanoseconds per element with small/large array)
# np: NumPy
# ne8: NumExpr with 8 threads
# ne1: NumExpr with 1 thread
#
# a+b:
# np: 2.25 / 4.01
# ne8: 22.6 / 3.22
# ne1: 22.6 / 4.21
# a += b:
# np: 1.5 / 1.26
# ne8: 36.8 / 1.18
# ne1: 36.8 / 1.48

# a+3:
# np: 4.8 / 3.62
# ne8: 10.9 / 3.09
# ne1: 20.2 / 4.04
# a += 3:
# np: 3.6 / 0.79
# ne8: 34.9 / 0.81
# ne1: 34.4 / 1.06

当然,对于所使用的计时方法来说,这不是很准确,但是有一定的总体趋势:

  • NumPy 使用更少的 cloc 周期 (np < ne1)
  • 并行性对非常大的数组 (10-20 %) 有一点帮助
  • NumExpr 对于小数组要慢得多
  • NumPy 在就地操作方面非常强大

NumPy 并没有让简单的算术运算并行化,但是从上面可以看出,这其实无关紧要。速度主要受内存带宽限制,而不是处理能力。

如果我们做一些更复杂的事情,事情就会改变。

np.sin(a_large)               # 19.4 ns/element
ne.evaluate("sin(a_large)") # 5.5 ns/element

速度不再受内存带宽限制。要查看这是否真的是由于线程(而不是由于 NumExpr 有时使用一些快速库):

ne.set_num_threads(1)
ne.evaluate("sin(a_large)") # 34.3 ns/element

在这里,并行性真的很有帮助。

NumPy 可以使用并行处理更复杂的线性运算,例如矩阵求逆。 NumExpr 不支持这些操作,所以没有有意义的比较。实际速度取决于所使用的库 (BLAS/Atlas/LAPACK)。此外,在执行 FFT 等复杂运算时,性能取决于库。 (据我所知,NumPy/SciPy 还没有 fftw 支持。)

总而言之,似乎在某些情况下 NumExpr 非常快速且有用。然后在某些情况下 NumPy 是最快的。如果你有愤怒的数组和逐元素操作,NumExpr 非常强大。然而,应该注意的是,一些并行性(甚至跨计算机传播计算)通常很容易通过 multiprocessing 或类似的东西合并到代码中。


关于“多处理”和“多线程”的问题有点棘手,因为术语有点不稳定。在python中“线程”是运行在同一个GIL下的东西,但是如果我们谈论操作系统线程和进程,两者之间可能没有任何区别。例如,在 Linux 中,两者之间没有区别。

关于python - 了解 Numpy 中的矢量化与通过 Numexpr 进行矢量化表达式的多线程之间的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24498178/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com