快速版粘貼
paste
是一個很棒的工具,但它非常慢:執行時我的伺服器上大約 50 MB/s:paste -d, file1 file2 ... file10000 | pv >/dev/null
paste
正在使用 100% CPUtop
,因此它不受例如慢速磁碟的限制。查看原始碼可能是因為它使用
getc
:while (chr != EOF) { sometodo = true; if (chr == line_delim) break; xputchar (chr); chr = getc (fileptr[i]); err = errno; }
是否有另一種工具可以做同樣的事情,但哪個更快?也許一次讀取 4k-64k 塊?也許通過使用向量指令並行查找換行符而不是一次查看單個字節?也許使用
awk
或類似?輸入文件是 UTF8 格式,因此無法放入 RAM,因此無法將所有內容讀入記憶體。
編輯:
thanasisp 建議並行執行作業。這稍微提高了吞吐量,但它仍然比 pure 慢一個數量級
pv
:# Baseline $ pv file* | head -c 10G >/dev/null 10.0GiB 0:00:11 [ 897MiB/s] [> ] 3% # Paste all files at once $ paste -d, file* | pv | head -c 1G >/dev/null 1.00GiB 0:00:21 [48.5MiB/s] [ <=> ] # Paste 11% at a time in parallel, and finally paste these $ paste -d, <(paste -d, file1*) <(paste -d, file2*) <(paste -d, file3*) \ <(paste -d, file4*) <(paste -d, file5*) <(paste -d, file6*) \ <(paste -d, file7*) <(paste -d, file8*) <(paste -d, file9*) | pv | head -c 1G > /dev/null 1.00GiB 0:00:14 [69.2MiB/s] [ <=> ]
top
仍然表明paste
瓶頸是外部。我測試了增加緩衝區是否有影響:
$ stdbuf -i8191 -o8191 paste -d, <(paste -d, file1?) <(paste -d, file2?) <(paste -d, file3?) <(paste -d, file4?) <(paste -d, file5?) <(paste -d, file6?) <(paste -d, file7?) <(paste -d, file8?) <(paste -d, file9?) | pv | head -c 1G > /dev/null 1.00GiB 0:00:12 [80.8MiB/s] [ <=> ]
這增加了 10% 的吞吐量。進一步增加緩衝區並沒有改善。這可能與硬體有關(即,可能是由於 1 級 CPU 記憶體的大小)。
測試在 RAM 磁碟中執行,以避免與磁碟子系統相關的限制。
用不同的替代方案和場景做了一些進一步的測試
(編輯:補充編譯版本的截止值)
tl;博士:
- 是的,coreutils
paste
遠慢於cat
- 似乎沒有比 coreutils 更快的容易獲得的替代方案,
paste
尤其是對於很多短線paste
在行長、行數和文件數的不同組合中,吞吐量驚人地穩定- 對於更長的線路,下面提供了更快的替代方案
詳細地:
我測試了很多場景。吞吐量測量是使用
pv
原始文章中的方法完成的。比較程序:
cat
(來自用作基線的 GNU coreutils 8.25)paste
(也來自 GNU coreutils 8.25)- 來自上面答案的python腳本
- 替代python腳本(替換列表理解以通過正常循環收集行片段)
- nim 程序(類似於 4。但編譯後的執行檔)
文件/行號組合:
每次測試的數據總量相同(1.3GB)。每列由 6 位數字組成(例如 000'001 到 200'000)。上述組合盡可能分佈在 1、10、100、1'000 和 10'000 個相同大小的文件中。
生成的文件如下:
yes {000001..200000} | head -1000 > 1
粘貼是這樣完成的:
for i in cat paste ./paste ./paste2 ./paste3; do $i {00001..1000} | pv > /dev/null; done
但是,粘貼的文件實際上都是指向同一個原始文件的連結,所以無論如何所有數據都應該在記憶體中(在粘貼之前直接創建並
cat
首先讀取;系統記憶體為 128GB,記憶體大小為 34GB)執行了一個額外的集合,其中數據是動態創建的,而不是從預先創建的文件中讀取並通過管道傳輸到
paste
(下面用文件數 = 0 表示)。最後一組命令就像
for i in cat paste ./paste ./paste2 ./paste3; do $i <(yes {000001..200000} | head -1000) | pv > /dev/null; done
發現:
paste
比慢一個數量級cat
paste
的吞吐量在各種線寬和所涉及的文件數量上都非常一致(~300MB/s)。- 一旦平均輸入文件行長度超過某個限制(在我的測試機器上約為 1400 個字元),國產 python 替代品就可以顯示出一些優勢。
- 與 python 腳本相比,編譯後的 nim 版本的吞吐量大約是兩倍。與一個輸入文件相比,收支平衡點
paste
約為 500 個字元。這會隨著輸入文件數量的增加而減少,只要涉及至少 10 個輸入文件,每行輸入文件的字元就會減少到約 150 個字元。- python 和 nim 版本都遭受許多短行的處理成本(懷疑的原因:在使用的兩個 stdlib 函式中都嘗試檢測行結尾並將它們轉換為特定於平台的結尾)。但是coreutils
paste
不受影響。- 似乎同時即時數據生成過程是限制因素
cat
,對於具有較長行數的 nim 版本也是如此,並且也在一定程度上影響了處理速度。- 在某些時候,大量打開的文件句柄似乎甚至對 coreutils 都有不利影響
paste
。(只是推測:也許這甚至會影響並行版本?)結論(至少對於測試機)
- 如果輸入文件很窄,請使用 coreutils 粘貼,特別是當文件很長時。
- 如果輸入文件相當寬,請選擇替代(python 版本的輸入文件行長度 > 1400 個字元,nim 版本的輸入文件行長度 > 150-500 個字元,具體取決於輸入文件的數量)。
- 通常比 python 腳本更喜歡編譯的 nim 版本。
- 小心太多的小碎片。在這種情況下,一個程序的 1024 個打開文件的預設軟限制似乎很合理。
對OP情況的建議(並行處理)
如果輸入文件很窄,
paste
請在內部作業中使用 coreutils,並為最外層程序使用編譯替代方案。如果所有文件都有長行,通常使用 nim 版本。Cave:連結的程序是臨時版本,按原樣提供,沒有任何保證,也沒有明確的錯誤處理。在所有三個實現中,分隔符也是硬編碼的。