Text-Processing

快速版粘貼

  • January 20, 2022

paste是一個很棒的工具,但它非常慢:執行時我的伺服器上大約 50 MB/s:

paste -d, file1 file2 ... file10000 | pv >/dev/null

paste正在使用 100% CPU top,因此它不受例如慢速磁碟的限制。

查看原始碼可能是因為它使用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;博士:

  1. 是的,coreutilspaste遠慢於cat
  2. 似乎沒有比 coreutils 更快的容易獲得的替代方案,paste尤其是對於很多短線
  3. paste在行長、行數和文件數的不同組合中,吞吐量驚人地穩定
  4. 對於更長的線路,下面提供了更快的替代方案

詳細地:

我測試了很多場景。吞吐量測量是使用pv原始文章中的方法完成的。

比較程序:

  1. cat(來自用作基線的 GNU coreutils 8.25)
  2. paste(也來自 GNU coreutils 8.25)
  3. 來自上面答案的python腳本
  4. 替代python腳本(替換列表理解以通過正常循環收集行片段)
  5. 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

發現:

  1. paste比慢一個數量級cat
  2. paste的吞吐量在各種線寬和所涉及的文件數量上都非常一致(~300MB/s)。
  3. 一旦平均輸入文件行長度超過某個限制(在我的測試機器上約為 1400 個字元),國產 python 替代品就可以顯示出一些優勢。
  4. 與 python 腳本相比,編譯後的 nim 版本的吞吐量大約是兩倍。與一個輸入文件相比,收支平衡點paste約為 500 個字元。這會隨著輸入文件數量的增加而減少,只要涉及至少 10 個輸入文件,每行輸入文件的字元就會減少到約 150 個字元。
  5. python 和 nim 版本都遭受許多短行的處理成本(懷疑的原因:在使用的兩個 stdlib 函式中都嘗試檢測行結尾並將它們轉換為特定於平台的結尾)。但是coreutilspaste不受影響。
  6. 似乎同時即時數據生成過程是限制因素cat,對於具有較長行數的 nim 版本也是如此,並且也在一定程度上影響了處理速度。
  7. 在某些時候,大量打開的文件句柄似乎甚至對 coreutils 都有不利影響paste。(只是推測:也許這甚至會影響並行版本?)

在此處輸入圖像描述

結論(至少對於測試機)

  1. 如果輸入文件很窄,請使用 coreutils 粘貼,特別是當文件很長時。
  2. 如果輸入文件相當寬,請選擇替代(python 版本的輸入文件行長度 > 1400 個字元,nim 版本的輸入文件行長度 > 150-500 個字元,具體取決於輸入文件的數量)。
  3. 通常比 python 腳本更喜歡編譯的 nim 版本。
  4. 小心太多的小碎片。在這種情況下,一個程序的 1024 個打開文件的預設軟限制似乎很合理。

對OP情況的建議(並行處理)

如果輸入文件很窄,paste請在內部作業中使用 coreutils,並為最外層程序使用編譯替代方案。如果所有文件都有長行,通常使用 nim 版本。

Cave:連結的程序是臨時版本,按原樣提供,沒有任何保證,也沒有明確的錯誤處理。在所有三個實現中,分隔符也是硬編碼的。

引用自:https://unix.stackexchange.com/questions/621234