在資料科學領域,經常需要處理大量資料或執行重複性高的任務。傳統的序列處理方式效率低下,平行處理技術則能有效利用多核心 CPU 資源,大幅縮短處理時間。GNU Parallel 作為一款強大的命令列工具,簡化了平行處理的流程,讓使用者無需複雜的程式碼即可實作任務的平行化執行。本文將介紹如何使用 GNU Parallel 進行平行處理,並探討其在實際應用中的技巧和注意事項。

平行化管線處理

在之前的章節中,我們處理的命令和管線都是用來一次性完成整個任務的。然而,在實際應用中,你可能會面臨需要多次執行相同命令或管線的任務。例如:

  • 抓取數百個網頁
  • 傳送數十個API請求並轉換其輸出
  • 為一系列引數值訓練分類別器
  • 為資料集中的每一對特徵生成散佈圖

在這些例子中,都存在某種形式的重複。使用你最喜歡的指令碼語言或程式語言,你可以透過for迴圈或while迴圈來處理這種情況。在命令列中,你可能會想按下向上箭頭鍵來調出前一個命令,根據需要進行修改,然後按下Enter鍵再次執行該命令。這種方法在重複兩三次時還可以,但如果需要重複數十次,就會變得繁瑣、低效且容易出錯。好訊息是,你也可以在命令列上編寫這樣的迴圈,這就是本章的重點。

使用迴圈處理重複任務

有時,連續重複執行一個快速命令(以序列方式)就足夠了。當你有多個核心(甚至多台機器)可用時,利用它們可以顯著減少總執行時間,尤其是在面對資料密集型任務時。在本章中,我將介紹一個非常強大的工具。

rush plot 的限制與解決方案

雖然 rush plot 適合用於在探索資料時建立基本的圖表,但它確實有一些限制。有時你需要更多的彈性和更複雜的功能,例如多重幾何形狀、座標轉換和主題設定。在這種情況下,學習更多關於 ggplot2 套件(rush plot 的基礎)的知識可能是值得的。如果你更喜歡 Python 而不是 R,那麼還有 plotnine 套件,它是 ggplot2 的 Python 實作版本。

圖表標籤與註解的重要性

使用適當的標籤和標題來註解你的視覺化圖表尤其有用,尤其是當你想要與他人(或未來的自己)分享時,因為它們會使理解圖表內容變得更加容易。

$ rush plot --x 'factor(size)' --y bill --geom violin --title 'Distribution of bill amount per party size' --xlab 'Party size' --ylab 'Bill (USD)' tips.csv > plot-labels.png
$ display plot-labels.png

圖表範例:小提琴圖

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 圖表範例:小提琴圖

rectangle "1" as node1
rectangle "2" as node2
rectangle "3" as node3
rectangle "4" as node4
rectangle "5" as node5
rectangle "6" as node6

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5
node5 --> node6

@enduml

此圖示展示了不同聚會人數下的帳單金額分佈情況。

平行化管線的優勢

使用多個核心或機器可以顯著減少總執行時間。在本章中,我們將探討如何利用這些資源來加速你的命令和管線。

重點回顧:
  • 使用迴圈處理重複任務
  • rush plot 的限制與解決方案
  • 圖表標籤與註解的重要性
  • 平行化管線的優勢

命令列工具的擴充套件應用

學習如何在命令列中使用迴圈和平行處理,可以顯著提高你的工作效率。接下來的章節將繼續探討更多相關主題。

平行管線處理:提升命令列資料處理效率

在資料科學的命令列工具應用中,經常需要對多個資料集或大量資料執行相同的命令或管線處理。為了提升處理效率,本章節將探討如何使用 parallel 工具來平行化這些任務。

序列處理:基礎但必要

在深入平行處理之前,瞭解如何在序列中執行命令是基礎。序列處理總是可用,其語法與其他程式語言中的迴圈相似。

對數字進行迴圈處理

假設需要計算 0 到 100 之間所有偶數的平方。可以使用 bc 這個基本計算器工具來實作:

for i in {0..100..2}
do
  echo "$i^2" | bc
done | trim

Z shell 的大括號擴充套件功能將 {0..100..2} 轉換為一個由空格分隔的列表:0 2 4 ... 98 100。變數 i 在每次迭代中被賦予列表中的下一個值。

對行進行迴圈處理

第二種迴圈是對行進行處理。這些行可以來自檔案或標準輸入。使用 while 迴圈可以實作:

while read line
do
  echo "Sending invitation to ${line}."
done < emails

這種方法適用於處理未知行數的輸入。

對檔案進行迴圈處理

第三種常見的迴圈是對檔案進行處理。使用 for 迴圈和 globbing(路徑名擴充套件)可以安全地處理特殊字元:

for chapter in /data/*
do
  echo "Processing Chapter ${chapter}."
done

或者,可以使用 find 命令來遞迴地搜尋檔案並根據屬性(如大小、存取時間、許可權)進行篩選:

find /data -type f -name '*.csv' -size -2k

使用 parallel 提升效率

parallel 工具允許使用者平行執行命令或管線,從而顯著提升處理效率。它可以與任何命令列工具結合使用,為資料科學工作帶來革命性的變化。

基本用法

cat emails | parallel echo "Sending invitation to {}."

這裡,parallelemails 檔案中的每一行作為輸入,並平行執行 echo 命令。

分發任務到多台機器

parallel 不僅可以在本地機器上平行執行任務,還可以將任務分發到多台機器上執行:

parallel -S machine1,machine2 echo "Running on {}" ::: "task1" "task2"
內容解密:
  1. parallel 的基本用法:使用 cat emails | parallel echo "Sending invitation to {}." 命令,將 emails 檔案中的每一行作為輸入,並平行執行 echo 命令。
  2. parallel 的分散式處理:使用 parallel -S machine1,machine2 echo "Running on {}" ::: "task1" "task2" 命令,將任務分發到多台機器上執行。
  3. forwhile 迴圈的使用:分別用於對數字、行和檔案進行序列處理,是理解平行處理的基礎。
  4. find 命令的使用:用於遞迴地搜尋檔案並根據屬性進行篩選,是檔案處理中的強大工具。

透過這些技術的結合使用,可以顯著提升資料科學工作的效率和生產力。

平行處理技術的應用與 GNU Parallel 工具介紹

在處理大量資料或執行耗時任務時,如何有效利用系統資源、提高處理效率是個重要的課題。傳統的序列處理方式(Serial Processing)按照順序逐一執行任務,這在處理大量任務時可能導致效率低下。本篇文章將探討如何透過平行處理(Parallel Processing)技術來提升效率,並介紹強大的 GNU Parallel 工具。

為何需要平行處理?

假設有一個執行時間較長的指令碼 slow.sh,其內容如下:

#!/bin/bash
echo "Starting job $1" | ts
duration=$((1+RANDOM%5))
sleep $duration
echo "Job $1 took ${duration} seconds" | ts

這個指令碼模擬了一個耗時任務,執行時間隨機介於 1 至 5 秒之間。當需要多次執行此指令碼時,序列執行的效率非常低。

簡單的平行處理嘗試

一種簡單的平行處理方法是將任務送到背景執行,例如:

for i in {A..C}; do
  ./slow.sh $i &
done

這裡使用 & 將命令送到背景執行,使得迴圈可以立即繼續下一次迭代。然而,這種方法有兩個主要問題:

  1. 無法控制平行執行的數量,可能導致資源競爭。
  2. 輸出結果難以與輸入對應。

內容解密:

  1. for i in {A..C}; do 開始一個迴圈,遍歷 A、B、C 三個字元。
  2. ./slow.sh $i & 執行 slow.sh 指令碼並傳入當前字元,將任務送到背景執行。
  3. done 結束迴圈。

GNU Parallel 的引入

GNU Parallel 是一個強大的命令列工具,用於平行化執行命令和管線。它無需修改現有的工具即可使用。透過 GNU Parallel,可以輕鬆取代前述的迴圈:

$ seq 0 2 100 | parallel "echo {}^2 | bc" | trim

這裡,parallel 命令接收標準輸入的數字,計算其平方並輸出結果。

內容解密:

  1. seq 0 2 100 生成從 0 到 100 的數字序列,步長為 2。
  2. parallel "echo {}^2 | bc" 對每個輸入數字計算平方,{} 是輸入的佔位符。
  3. trim 用於去除輸出的多餘空白行。

使用 GNU Parallel 的優勢

GNU Parallel 不僅能有效控制平行執行的數量,還能確保輸出結果與輸入對應。例如:

$ parallel --jobs 2 ./slow.sh ::: {A..C}

這裡使用 --jobs 2 指定最多平行執行兩個任務。

內容解密:

  1. parallel --jobs 2 ./slow.sh ::: {A..C} 使用 GNU Parallel 執行 slow.sh,平行數量限制為 2,輸入引數為 A、B、C。

指定輸入與佔位符的使用

在使用 GNU Parallel 時,可以透過佔位符 {} 明確指定輸入項在命令中的位置。例如:

$ seq 3 | parallel cowsay {}

這等同於直接執行 cowsay 1cowsay 2cowsay 3

內容解密:

  1. seq 3 生成從 1 到 3 的數字序列。
  2. parallel cowsay {} 對每個數字執行 cowsay 命令,{} 代表輸入的數字。

平行處理的藝術:GNU Parallel 的應用與實踐

在現代的資料科學和系統管理領域中,平行處理是一項不可或缺的技術。GNU Parallel 是一個強大的命令列工具,能夠幫助我們輕鬆地將任務分配到多個 CPU 核心上平行執行,從而大幅提高工作效率。本文將探討 GNU Parallel 的基本用法、進階功能以及實際應用案例。

基本用法:輸入與命令執行

使用 GNU Parallel 時,首先需要了解如何提供輸入和指定命令。最簡單的方式是透過管道(pipe)傳遞輸入給 Parallel:

$ seq 3 | parallel cowsay {} > /dev/null

在這個例子中,seq 3 生成數字 1 到 3,然後透過管道傳遞給 parallelcowsay {} 是要執行的命令,其中 {} 是輸入項的佔位符。

內容解密:

  1. seq 3:生成數字序列 1 到 3。
  2. parallel:將輸入項分配到多個命令中平行執行。
  3. cowsay {}:對每個輸入項執行 cowsay 命令,輸出一幅 ASCII 藝術影像。
  4. > /dev/null:將輸出重定向到 /dev/null,即丟棄輸出結果。

檔案名稱處理與佔位符修改器

當輸入項是檔案名稱時,可以使用修改器來提取檔案名稱的不同部分。例如,使用 {/} 可以取得檔案名稱的基名(basename):

$ find /data/ch03 -type f | parallel echo '{#}\) \"{}\" has basename \"{/}\"'

內容解密:

  1. find /data/ch03 -type f:在 /data/ch03 目錄下查詢所有檔案。
  2. parallel echo '{#}\) \"{}\" has basename \"{/\"}':對每個找到的檔案執行 echo 命令。
    • {#}:代表輸入項的序號。
    • {}:代表完整的檔案路徑。
    • {/}:代表檔案名稱的基名。

控制平行作業數量

預設情況下,Parallel 會根據 CPU 核心數量來決定平行執行的作業數量。可以使用 --jobs-j 選項來控制平行作業的數量:

$ seq 5 | parallel -j0 "echo Hi {}"
$ seq 5 | parallel -j200% "echo Hi {}"

內容解密:

  1. -j0:讓 Parallel 盡可能地平行執行作業。
  2. -j200%:讓 Parallel 使用兩倍於 CPU 核心數量的作業平行執行。

輸出記錄與管理

Parallel 提供了 --results 選項,用於將每個作業的輸出儲存到單獨的檔案中:

$ seq 10 | parallel --results outdir "curl 'https://anapioficeandfire.com/api/characters/{}' | jq -r '.aliases[0]'"

內容解密:

  1. --results outdir:指定輸出結果儲存的目錄。
  2. curljq 命令組合:用於從 API 取得資料並提取所需的資訊。

建立平行工具

透過將現有的工具與 Parallel 結合,可以創造出新的平行工具。例如,pbc 是一個用於平行執行 bc 命令的工具:

$ bat $(which pbc)
#!/bin/bash
# pbc: parallel bc. First column of input CSV is mapped to {1}, second to {2}, and so forth.
parallel -C, -k -j100% "echo '$1' | bc -l"

內容解密:

  1. parallel -C, -k -j100%:使用逗號作為分隔符,保持輸出順序,並使用預設的 CPU 核心數量進行平行處理。
  2. echo '$1' | bc -l:對輸入的每個運算式執行 bc 命令進行計算。

分散式處理:釋放遠端機器的威力

在某些情況下,本地機器的處理能力可能不足以滿足需求,即使充分利用所有核心也無濟於事。幸運的是,parallel 工具不僅能夠利用本地機器的多核心處理能力,還能夠藉助遠端機器的力量來加速處理流程。

為何選擇分散式處理?

當本地資源不足時,parallel 可以透過 SSH(Secure Shell)協定連線到遠端機器,將任務分配到這些機器上執行。這種分散式處理方式大大提高了處理效率,尤其是在處理大型資料集或需要大量運算資源的任務時。

分散式處理的三種模式

  1. 在遠端機器上執行普通命令:將命令傳送到遠端機器執行,無需在遠端機器上安裝 parallel
  2. 直接將本地資料分配到遠端機器:將資料直接分配到遠端機器進行處理。
  3. 將檔案傳送到遠端機器,處理後取回結果:將檔案傳送到遠端機器進行處理,然後取回處理結果。

取得正在執行的 AWS EC2 例項列表

首先,我們需要取得一組正在執行的 AWS EC2 例項的主機名稱,並將它們儲存到一個名為 hostnames 的檔案中。可以使用 AWS CLI 工具 aws 來取得 EC2 例項的資訊。

$ aws ec2 describe-instances | jq '.Reservations[].Instances[] | {public_dns: .PublicDnsName, state: .State.Name}'

內容解密:

  • aws ec2 describe-instances:使用 AWS CLI 取得所有 EC2 例項的詳細資訊。
  • jq:用於解析 JSON 輸出,並提取 PublicDnsNameState.Name 欄位。

接下來,篩選出狀態為 running 的例項,並提取其 PublicDnsName

$ aws ec2 describe-instances | jq -r '.Reservations[].Instances[] | select(.State.Name=="running") | .PublicDnsName' | tee hostnames
ec2-54-88-122-140.compute-1.amazonaws.com
ec2-54-88-89-208.compute-1.amazonaws.com

內容解密:

  • select(.State.Name=="running"):篩選出狀態為 running 的例項。
  • .PublicDnsName:提取例項的公網 DNS 名稱。
  • tee hostnames:將輸出儲存到 hostnames 檔案中。

在遠端機器上執行命令

有了 hostnames 檔案後,就可以使用 parallel 在這些遠端機器上執行命令。例如,執行 hostname 命令:

$ parallel --nonall --sshloginfile hostnames hostname
ip-172-31-23-204
ip-172-31-23-205

內容解密:

  • --nonall:指示 parallel 在所有遠端機器上執行相同的命令,而不傳遞任何引數。
  • --sshloginfile hostnames:指定包含遠端主機名稱的檔案。

如果沒有遠端機器,可以使用 --sshlogin : 將命令執行在本機上:

$ parallel --nonall --sshlogin : hostname
data-science-toolbox

內容解密:

  • --sshlogin ::指示 parallel 在本機上執行命令。

處理分散式任務時的注意事項

如果 parallel 未安裝在遠端機器上,它可能無法正確偵測到遠端機器的核心數量,此時會預設使用一個核心。可以透過以下幾種方式解決此問題:

  1. 忽略警告,使用一個核心
  2. 使用 --jobs-j 選項指定每個遠端機器的任務數量
  3. hostnames 檔案中指定每個主機要使用的核心數量,例如在主機名稱前加上 2/ 表示使用兩個核心。
  4. 在遠端機器上安裝 parallel

圖表說明

以下 Plantuml 圖表展示了分散式處理的流程:

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 圖表說明

rectangle "SSH 連線" as node1
rectangle "資料分配" as node2
rectangle "處理結果" as node3

node1 --> node2
node2 --> node3

@enduml

此圖示說明瞭本地機器如何透過 SSH 連線到多台遠端機器,並將資料分配給它們進行處理,最後取回處理結果。