并行-并行性的困扰:当更快的代码变得更慢

目录

并行性的困扰:当更快的代码变得更慢

当你使用NumPy进行计算密集型操作时,你可能会希望使用计算机的所有CPU。你的计算机有2个、4个甚至更多的CPU核心,如果你能充分利用它们,你的代码会运行得更快。

当然,除了并行性有时会让你的代码变得更慢。

事实证明,对于某些操作,NumPy会透明地进行并行化处理。如果你不小心,这实际上可能会减慢你的代码。


并行性让代码更快

考虑以下程序:

import numpy as np
a = np.ones((4096, 4096))
a.dot(a)

如果我们使用time工具运行这段代码,结果如下:

$ time python dot.py 
real    0m1.546s
user    0m4.171s
sys     0m0.537s

real时间是挂钟时间,user时间是CPU时间。在这个例子中,user时间比real时间高,这意味着操作使用了多个CPU,总共消耗了4.17秒的CPU时间。

这很棒!如果我们只使用一个CPU,这个操作将花费约4.2秒,但由于使用了多个CPU,它只花费了约1.5秒。


并行性让代码更慢

让我们验证这个假设。

我们将通过使用threadpoolctl库告诉NumPy只使用一个CPU:

import numpy as np
from threadpoolctl import threadpool_limits

with threadpool_limits(limits=1, user_api='blas'):
    a = np.ones((4096, 4096))
    a.dot(a)

现在当我们运行它时:

$ time python dot_onecpu.py 
real    0m3.654s
user    0m3.652s
sys     0m0.403s

当我们使用多个CPU时,它花费了约4.2秒的CPU时间,但使用单个CPU时,它只花费了约3.7秒的CPU时间。从CPU时间的角度来看,代码现在更快了!

这重要吗?难道不是更短的挂钟时间更重要吗?

如果你只在计算机上运行这一个程序,并且你没有为你的程序实现任何其他形式的并行性,那么是的,这没问题。但如果你自己实现了某种形式的并行性,例如使用multiprocessingjoblib,或者我个人最喜欢的Dask,默认的并行性会使你的程序整体变慢。

在这个例子中,每次dot()调用将占用你整体CPU容量的13%。


BLAS!

你会注意到上面的代码中,线程池限制提到了BLAS。BLAS是一个用于线性代数的API,NumPy在某些操作中使用它,比如这里的dot()

有不同的BLAS实现可用,在上面的例子中,我使用的是OpenBLAS。另一个选择是mkl,它由Intel提供,因此针对Intel处理器进行了优化;你不希望在AMD上使用它。

至少对于这个操作,mkl似乎也有同样的问题:它在单个CPU上运行得比在多个CPU上并行化时更快。一般来说,值得看看切换是否会给你的性能带来一些提升。

如果你使用的是Conda Forge,你可以通过在environment.yml中包含blas=*=openblasblas=*=mkl包来实现这一点。


如果这只是这个基准测试呢?

有人可能会说这只是一个基准测试,我可能以多种方式搞砸了。虽然这是事实,但这只是一个例子:

因此,如果使用N个线程实际上能带来×N的性能,那将是非常令人惊讶的。

所以,是的,你通常确实需要并行性,但你需要思考在哪里、如何以及何时使用它。

减少并行性以获得更多并行性

如果你使用Dask等工具实现了高层次的并行性,你可能希望禁用NumPy中的多线程。 单个操作将占用更少的CPU时间,而你自己实现的并行性将确保多个CPU的利用率。

此外,在性能分析时要小心:如果你使用自动并行化,你分析的内容可能与具有不同CPU数量的计算机上的行为不匹配。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李星星BruceL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值