wrjc1hod 发表于 2024-8-17 20:53:03

怎么样利用并发性加速你的python程序(三):CPU 绑定程序加速


    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">雷锋网 AI 科技评论按,本文是工程师 Jim Anderson 分享的关于「<span style="color: black;">经过</span>并发性加快 python 程序的速度」的<span style="color: black;">文案</span>的第三部分,<span style="color: black;">重点</span>内容是 CPU 绑定程序加速<span style="color: black;">关联</span>。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">在前面两篇中,<span style="color: black;">咱们</span><span style="color: black;">已然</span>讲过了<span style="color: black;">关联</span>的概念以及I/O 绑定程序的加速,这篇是这一系列<span style="color: black;">文案</span>的最后一篇,讲的是 CPU 程序加速。雷锋网 AI 科技评论编译整理如下:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;"><span style="color: black;">怎样</span>加速 CPU 绑定程序</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">到<span style="color: black;">日前</span>为止,前面的例子都处理了一个 I/O 绑定问题。<span style="color: black;">此刻</span>,你将<span style="color: black;">科研</span> CPU 绑定的问题。如你所见,I/O 绑定的问题大部分时间都在等待<span style="color: black;">外边</span>操作(如网络调用)完成。另一方面,CPU 限制的问题只执行很少的 I/O 操作,它的总体执行时间取决于它处理所需数据的速度。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">在<span style="color: black;">咱们</span>的示例中,<span style="color: black;">咱们</span>将<span style="color: black;">运用</span>一个有点愚蠢的函数来创建<span style="color: black;">有些</span><span style="color: black;">必须</span>在 CPU 上运行很<span style="color: black;">长期</span>的东西。此函数计算从 0 到传入值的<span style="color: black;">每一个</span>数字的平方和:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">你将处理一大批数据,<span style="color: black;">因此</span>这<span style="color: black;">必须</span>一段时间。记住,这只是代码的一个占位符,它<span style="color: black;">实质</span>上做了<span style="color: black;">有些</span>有用的事情,<span style="color: black;">必须</span><span style="color: black;">海量</span>的处理时间,例如计算公式的根或对大型数据结构进行排序。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;">CPU 绑定的同步版本</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">此刻</span>让<span style="color: black;">咱们</span>看一下这个示例的非并发版本:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">import time</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">def cpu_bound(number): return sum(i * i for i in range(number))</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">def find_sums(numbers):</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">for number in numbers:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">cpu_bound(number)</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">if __name__ == "__main__":</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">numbers = </p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">start_time = time.time</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">find_sums(numbers)</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">duration = time.time - start_time</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">print(f"Duration {duration} seconds")</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">此代码调用 cpu_bound 20 次,每次<span style="color: black;">运用</span><span style="color: black;">区别</span>的大数字。它在单个 CPU 上单个进程中的单个线程上完成所有这些工作。执行时序图如下:</p><img src="https://p3-sign.toutiaoimg.com/pgc-image/RGjAe8P8OKWNf8~noop.image?_iz=58558&amp;from=article.pc_detail&amp;lk3s=953192f4&amp;x-expires=1723895526&amp;x-signature=EssdTAe2kNmc1oACQElBp4vuUxw%3D" style="width: 50%; margin-bottom: 20px;">
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">与 I/O 绑定示例<span style="color: black;">区别</span>,CPU 绑定示例的运行时间<span style="color: black;">一般</span>相当一致。这台<span style="color: black;">设备</span>大约<span style="color: black;">必须</span> 7.8 秒:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">显然<span style="color: black;">咱们</span><span style="color: black;">能够</span>做得更好。这都是在<span style="color: black;">无</span>并发性的单个 CPU 上运行的。让<span style="color: black;">咱们</span><span style="color: black;">瞧瞧</span><span style="color: black;">咱们</span>能做些什么来改善它。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;">线程和异步版本</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">你认为<span style="color: black;">运用</span>线程或异步重写此代码会加快速度吗?</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">倘若</span>你回答「一点<span style="color: black;">亦</span>不」,这是有道理的。<span style="color: black;">倘若</span>你回答,「它会减慢速度,」那就更对啦。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">原由</span>如下:在上面的 I/O 绑定示例中,大部分时间都花在等待缓慢的操作完成上。线程和异步<span style="color: black;">经过</span><span style="color: black;">准许</span>你重叠等待的时间而不是按<span style="color: black;">次序</span>执行,这能加快速度。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">然则</span>,在 CPU 绑定的问题上,不<span style="color: black;">必须</span>等待。CPU 会尽可能快速地<span style="color: black;">起步</span>以<span style="color: black;">处理</span>问题。在 python 中,线程和任务都在同一进程中的同一个 CPU 上运行。这<span style="color: black;">寓意</span>着一个 CPU 不仅做了非并发代码的所有工作,还<span style="color: black;">必须</span>做线程或任务的额外工作。它花费的时间超过 10 秒:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">我<span style="color: black;">已然</span>编写了这个代码的线程版本,并将它与其他示例代码放在 Github repo 中,<span style="color: black;">这般</span>你就<span style="color: black;">能够</span>自己测试它了。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;">CPU 绑定的多处理版本</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">此刻</span>,你<span style="color: black;">最终</span>要接触多处理真正与众<span style="color: black;">区别</span>的<span style="color: black;">地区</span>啦。与其他并发库<span style="color: black;">区别</span>,多处理被显式设计为跨多个 CPU <span style="color: black;">一起</span>承担工作负载。它的执行时序图如下所示:</p><img src="https://p3-sign.toutiaoimg.com/pgc-image/RGjAe8zEUJb3xJ~noop.image?_iz=58558&amp;from=article.pc_detail&amp;lk3s=953192f4&amp;x-expires=1723895526&amp;x-signature=c1Avcje2Gt%2Byg5KOZDKTj43DyVU%3D" style="width: 50%; margin-bottom: 20px;">
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">它的代码是<span style="color: black;">这般</span>的:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">import multiprocessing</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">import time</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">def cpu_bound(number): return sum(i * i for i in range(number))</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">def find_sums(numbers):</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">with multiprocessing.Pool as pool:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">pool.map(cpu_bound, numbers)</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">if __name__ == "__main__":</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">numbers = </p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">start_time = time.time</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">find_sums(numbers)</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">duration = time.time - start_time</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">print(f"Duration {duration} seconds")</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">这些代码和非并发版本相比几乎<span style="color: black;">无</span>要更改的。你<span style="color: black;">必要</span>导入多处理,<span style="color: black;">而后</span>把数字循环改为创建多处理.pool 对象,并<span style="color: black;">运用</span>其.map<span style="color: black;">办法</span>在工作进程空闲时将单个数字发送给它们。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">这正是你为 I/O 绑定的多处理代码所做的,<span style="color: black;">然则</span><span style="color: black;">这儿</span>你不<span style="color: black;">必须</span>担心会话对象。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">如上所述,处理 multiprocessing.pool构造函数的可选参数值得<span style="color: black;">重视</span>。<span style="color: black;">能够</span>指定要在池中创建和管理的进程对象的数量。默认<span style="color: black;">状况</span>下,它将确定机器中有多少 CPU,并为<span style="color: black;">每一个</span> CPU 创建一个进程。虽然这<span style="color: black;">针对</span><span style="color: black;">咱们</span>的简单示例<span style="color: black;">来讲</span><span style="color: black;">特别有</span>用,但你可能<span style="color: black;">期盼</span>在生产环境它<span style="color: black;">亦</span>能发挥<span style="color: black;">功效</span>。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">另一</span>,和<span style="color: black;">咱们</span>在<span style="color: black;">第1</span>节中<span style="color: black;">说到</span>的线程<span style="color: black;">同样</span>,multiprocessing.Pool 的代码是<span style="color: black;">创立</span>在 Queue 和 Semaphore 上的,这<span style="color: black;">针对</span><span style="color: black;">运用</span>其他语言执行多线程和多处理代码的人<span style="color: black;">来讲</span>是很<span style="color: black;">熟练</span>的。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;"><span style="color: black;">为何</span>多处理版本很重要</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">这个例子的多处理版本非常好,<span style="color: black;">由于</span>它相对容易设置,并且只<span style="color: black;">必须</span>很少的额外代码。它还充分利用了计算机中的 CPU 资源。在我的<span style="color: black;">设备</span>上,运行它只<span style="color: black;">必须</span> 2.5 秒: </p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">这比<span style="color: black;">咱们</span>看到的其他<span style="color: black;">办法</span>要好得多。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;">多处理版本的问题</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">运用</span>多处理有<span style="color: black;">有些</span>缺点。在这个简单的例子中,这些缺点并<span style="color: black;">无</span><span style="color: black;">透出</span>来,<span style="color: black;">然则</span>将你的问题分解开来,以便<span style="color: black;">每一个</span>处理器都能独立工作有时是很困难的。<span style="color: black;">另外</span>,许多<span style="color: black;">处理</span><span style="color: black;">方法</span><span style="color: black;">必须</span>在流程之间进行<span style="color: black;">更加多</span>的通信,这相比非并发程序<span style="color: black;">来讲</span>会<span style="color: black;">繁杂</span>得多。雷锋网</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;">何时<span style="color: black;">运用</span>并发性</strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">首要</span>,你应该判断<span style="color: black;">是不是</span>应该<span style="color: black;">运用</span>并发模块。虽然<span style="color: black;">这儿</span>的示例使<span style="color: black;">每一个</span>库看起来非常简单,但并发性总是<span style="color: black;">伴同</span>着额外的<span style="color: black;">繁杂</span>性,并且常常会导致难以找到的错误。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">保持</span>添加并发性,直到<span style="color: black;">显现</span>已知的性能问题,<span style="color: black;">而后</span>确定<span style="color: black;">必须</span>哪种类型的并发性。正如 DonaldKnuth 所说,「过早的优化是编程中所有<span style="color: black;">劫难</span>(<span style="color: black;">或</span><span style="color: black;">最少</span>大部分<span style="color: black;">劫难</span>)的根源(Premature optimization is the root of all evil (or at least most of it) in programming)」。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">一旦你决定优化你的程序,弄清楚你的程序是 CPU 绑定的还是 I/O 绑定的,这<span style="color: black;">便是</span>下一步要做的事情。记住,I/O 绑定的程序是<span style="color: black;">哪些</span>花费大部分时间等待事情完成的程序,而 CPU 绑定的程序则尽可能快地处理数据。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">正如你所看到的,CPU 绑定的问题<span style="color: black;">实质</span>上<span style="color: black;">仅有</span>在<span style="color: black;">运用</span>多处理<span style="color: black;">才可</span><span style="color: black;">处理</span>。线程和异步<span style="color: black;">基本</span><span style="color: black;">无</span><span style="color: black;">帮忙</span><span style="color: black;">处理</span>这类问题。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">针对</span> I/O 绑定的问题,python 社区中有一个通用的经验规则:「<span style="color: black;">能够</span><span style="color: black;">运用</span>异步,<span style="color: black;">必要</span><span style="color: black;">运用</span>线程。」异步<span style="color: black;">能够</span>为这种类型的程序<span style="color: black;">供给</span>最佳的速度,但有时<span style="color: black;">必须</span>某些关键库来利用它。记住,任何不放弃对事件循环<span style="color: black;">掌控</span>的任务都将阻塞所有其他任务。</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">CPU 绑定加速的内容就到此为止啦,<span style="color: black;">认识</span><span style="color: black;">更加多</span>请<span style="color: black;">拜访</span>原文!</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;">前面的部分请查看:</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">怎样</span>利用并发性加速你的python程序(一):<span style="color: black;">关联</span>概念</p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">怎样</span>利用并发性加速你的python程序(二):I/O 绑定程序加速</p>




1fy07h 发表于 2024-10-20 04:43:19

感谢楼主分享,祝愿外链论坛越办越好!

nqkk58 发表于 2024-11-1 21:33:58

你的见解独到,让我受益匪浅,非常感谢。

1fy07h 发表于 2024-11-7 03:45:58

外贸论坛是我们的,责任是我们的,荣誉是我们的,成就是我们的,辉煌是我们的。
页: [1]
查看完整版本: 怎么样利用并发性加速你的python程序(三):CPU 绑定程序加速