您現在的位置是:首頁 > 手機遊戲首頁手機遊戲

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

簡介正如你所看到的,CPU 繫結的問題實際上只有在使用多處理才能解決

如何新增時間加速

雷鋒網 AI 科技評論按,本文是工程師 Jim Anderson 分享的關於「透過併發性加快 python 程式的速度」的文章的第三部分,主要內容是 CPU 繫結程式加速相關。

在前面兩篇中,我們已經講過了相關的概念以及 I/O 繫結程式的加速,這篇是這一系列文章的最後一篇,講的是 CPU 程式加速。雷鋒網 AI 科技評論編譯整理如下:

如何加速 CPU 繫結程式

到目前為止,前面的例子都處理了一個 I/O 繫結問題。現在,你將研究 CPU 繫結的問題。如你所見,I/O 繫結的問題大部分時間都在等待外部操作(如網路呼叫)完成。另一方面,CPU 限制的問題只執行很少的 I/O 操作,它的總體執行時間取決於它處理所需資料的速度。

在我們的示例中,我們將使用一個有點愚蠢的函式來建立一些需要在 CPU 上執行很長時間的東西。此函式計算從 0 到傳入值的每個數字的平方和:

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

你將處理一大批資料,所以這需要一段時間。記住,這只是程式碼的一個佔位符,它實際上做了一些有用的事情,需要大量的處理時間,例如計算公式的根或對大型資料結構進行排序。

CPU 繫結的同步版本

現在讓我們看一下這個示例的非併發版本:

import time

def cpu_bound(number):

return sum(i * i for i in range(number))

def find_sums(numbers):

for number in numbers:

cpu_bound(number)

if __name__ == “__main__”:

numbers = [5_000_000 + x for x in range(20)]

start_time = time。time()

find_sums(numbers)

duration = time。time() - start_time

print(f“Duration {duration} seconds”)

此程式碼呼叫 cpu_bound() 20 次,每次使用不同的大數字。它在單個 CPU 上單個程序中的單個執行緒上完成所有這些工作。執行時序圖如下:

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

與 I/O 繫結示例不同,CPU 繫結示例的執行時間通常相當一致。這臺機器大約需要 7。8 秒:

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

顯然我們可以做得更好。這都是在沒有併發性的單個 CPU 上執行的。讓我們看看我們能做些什麼來改善它。

執行緒和非同步版本

你認為使用執行緒或非同步重寫此程式碼會加快速度嗎?

如果你回答「一點也不」,這是有道理的。如果你回答,「它會減慢速度,」那就更對啦。

原因如下:在上面的 I/O 繫結示例中,大部分時間都花在等待緩慢的操作完成上。執行緒和非同步透過允許你重疊等待的時間而不是按順序執行,這能加快速度。

但是,在 CPU 繫結的問題上,不需要等待。CPU 會盡可能快速地啟動以解決問題。在 python 中,執行緒和任務都在同一程序中的同一個 CPU 上執行。這意味著一個 CPU 不僅做了非併發程式碼的所有工作,還需要做執行緒或任務的額外工作。它花費的時間超過 10 秒:

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

我已經編寫了這個程式碼的執行緒版本,並將它與其他示例程式碼放在 Github repo 中,這樣你就可以自己測試它了。

CPU 繫結的多處理版本

現在,你終於要接觸多處理真正與眾不同的地方啦。與其他併發庫不同,多處理被顯式設計為跨多個 CPU 共同承擔工作負載。它的執行時序圖如下所示:

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

它的程式碼是這樣的:

import multiprocessing

import time

def cpu_bound(number):

return sum(i * i for i inrange(number))

def find_sums(numbers):

with multiprocessing。Pool() as pool:

pool。map(cpu_bound, numbers)

if __name__ == “__main__”:

numbers = [5_000_000 + x for x in range(20)]

start_time = time。time()

find_sums(numbers)

duration = time。time() - start_time

print(f“Duration {duration} seconds”)

這些程式碼和非併發版本相比幾乎沒有要更改的。你必須匯入多處理,然後把數字迴圈改為建立多處理。pool 物件,並使用其。map()方法在工作程序空閒時將單個數字傳送給它們。

這正是你為 I/O 繫結的多處理程式碼所做的,但是這裡你不需要擔心會話物件。

如上所述,處理 multiprocessing。pool()建構函式的可選引數值得注意。可以指定要在池中建立和管理的程序物件的數量。預設情況下,它將確定機器中有多少 CPU,併為每個 CPU 建立一個程序。雖然這對於我們的簡單示例來說很有用,但你可能希望在生產環境它也能發揮作用。

另外,和我們在第一節中提到的執行緒一樣,multiprocessing。Pool 的程式碼是建立在 Queue 和 Semaphore 上的,這對於使用其他語言執行多執行緒和多處理程式碼的人來說是很熟悉的。

為什麼多處理版本很重要

這個例子的多處理版本非常好,因為它相對容易設定,並且只需要很少的額外程式碼。它還充分利用了計算機中的 CPU 資源。在我的機器上,執行它只需要 2。5 秒:

如何利用併發性加速你的python程式(三):CPU 繫結程式加速

這比我們看到的其他方法要好得多。

多處理版本的問題

使用多處理有一些缺點。在這個簡單的例子中,這些缺點並沒有顯露出來,但是將你的問題分解開來,以便每個處理器都能獨立工作有時是很困難的。此外,許多解決方案需要在流程之間進行更多的通訊,這相比非併發程式來說會複雜得多。雷鋒網

何時使用併發性

首先,你應該判斷是否應該使用併發模組。雖然這裡的示例使每個庫看起來非常簡單,但併發性總是伴隨著額外的複雜性,並且常常會導致難以找到的錯誤。

堅持新增併發性,直到出現已知的效能問題,然後確定需要哪種型別的併發性。正如 DonaldKnuth 所說,「過早的最佳化是程式設計中所有災難(或者至少大部分災難)的根源(Premature optimization is the root of all evil (or at least most of it) in programming)」。

一旦你決定最佳化你的程式,弄清楚你的程式是 CPU 繫結的還是 I/O 繫結的,這就是下一步要做的事情。記住,I/O 繫結的程式是那些花費大部分時間等待事情完成的程式,而 CPU 繫結的程式則儘可能快地處理資料。

正如你所看到的,CPU 繫結的問題實際上只有在使用多處理才能解決。執行緒和非同步根本沒有幫助解決這類問題。

對於 I/O 繫結的問題,python 社群中有一個通用的經驗規則:「可以使用非同步,必須使用執行緒。」非同步可以為這種型別的程式提供最佳的速度,但有時需要某些關鍵庫來利用它。記住,任何不放棄對事件迴圈控制的任務都將阻塞所有其他任務。

CPU 繫結加速的內容就到此為止啦,瞭解更多請訪問原文!

前面的部分請檢視:

如何利用併發性加速你的python程式(一):相關概念

如何利用併發性加速你的python程式(二):I/O 繫結程式加速

Top