簡體   English   中英

使用 multiprocessing 或 ray 與其他 cpu 綁定任務同時寫入文件

[英]Writing files concurrently with other cpu-bound tasks with multiprocessing or ray

我有一個有 72 個內核的工作站(實際上是 36 個多線程 CPU,通過multiprocessing.cpu_count()顯示為 72 個內核)。

我嘗試了multiprocessingray進行並發處理,批量處理數百萬個小文件,我想在該處理過程中同時寫入一些輸出文件。

我對與apply_async() (在multiprocessing )和ray.get()相關聯的.get()方法的阻塞感到困惑。

使用ray ,我有一個遠程函數( process_group() )可以在循環內並行處理數據組。 在下文中,使用multiprocessing模塊的代碼版本也作為注釋給出。

import ray
import pandas as pd
# from multiprocessing import Pool

ray.init(num_cpus=60)
# with Pool(processes=n_workers) as pool:
for data_list in many_data_lists:
   ##-----------------------
   ## With ray :
   df_list = ray.get([process_group.remote(data) for data in data_list])
   ##-----------------------
   ## With multiprocessing :
   #f_list = pool.map(process_group, list_of_indices_into_data_list)
   ##
   ##      data are both known from the parent process
   ##      and I use copy-on-write semantic to avoid having 60 copies.
   ##      All the function needs are a list of indices
   ##      of where to fetch slices of the read-only data.  
   ##
   very_big_df = pd.concatenate(df_list)
   ##-----------------------
   ## Write to file :
   very_big_df.to_parquet(outputfile)

因此,在每次循環迭代中,我必須收集許多同時計算的process_group()的輸出,作為數據幀df_list的列表,用於連接成一個更大的very_big_df數據幀。 后者需要寫入磁盤(通常大小為 ~1 到 ~3 GB)。 編寫一個這樣的文件大約需要10-30 [s]process_group遠程處理需要大約180 [s] 有數千次循環迭代。 所以這需要幾天時間才能完成。

是否可以以非阻塞方式將文件寫入磁盤,同時循環繼續以節省大約 10% 的時間(這將節省大約一天的計算時間)?

到下一次循環迭代的並發進程完成時,有足夠的時間來寫入前一次迭代的輸出。 這里涉及的內核似乎都以接近 100% 的速度運行,因此也可能不推薦使用Threading模塊。 multiprocessing.apply_async()更令人沮喪,因為它不希望我的不可選擇的輸出very_big_df數據幀,我將不得不分享一些更復雜的數據,這可能會花費我試圖節省的時間,我希望ray能夠處理像這樣有效的東西。

[更新] 為了簡單起見,我沒有提到所有進程之間有一個很大的共享變量(這就是為什么我稱它為並行進程,以及文件的並發寫入)。 結果我的標題問題被編輯了。 所以實際上,在光線並行作業之前有這么一段代碼:

shared_array_id = ray.put(shared_array)
df_list = ray.get([process_group.remote(shared_array, data) for data in data_list])

不確定這是否使它更像是“並行”執行而不僅僅是並發操作。

[更新 2] 共享數組是一個查找表,即就並行工作人員而言是只讀的。

[UPDATE 3] 我嘗試了兩種建議的解決方案:Threading 和 Ray/compute() 對於后者,建議將寫入函數用作遠程並在 for 循環內異步發送寫入操作,我最初認為這是唯一可能的通過 .get() 這將是阻塞的。

因此,對於 Ray,這顯示了兩種解決方案:

@ray.remote
def write_to_parquet(df_list, filename):
    df = pd.concat(df_list)
    df.to_parquet(filename, engine='pyarrow', compression=None)

# Share array created outside the loop, read-only (big lookup table). 
# About 600 MB
shared_array_id = ray.put(shared_array)

for data_list in many_data_lists:

   new_df_list = ray.get([process_group.remote(shared_array_id, data) for data in data_list])
   write_to_parquet.remote(df_list, my_filename)

   ## Using threading, one would remove the ray decorator:
   # write_thread = threading.Thread(target=write_to_parquet, args=(new_df_list, tinterval.left))
   # write_thread.start()

對於 RAY 解決方案,這需要增加 object_store_memory,默認值是不夠的:節點內存的 10% ~ 37 GB(我有 376 GB 的內存),然后上限為 20 GB,唯一存儲的對象總計約 22 GB:一個數據幀列表df_list (大約 11 GB),以及它們在寫入函數中的連接結果(大約 11 GB),假設在連接期間有一個副本。 如果沒有,那么這個內存問題就沒有意義,我想知道我是否可以傳遞 numpy 視圖,我認為默認情況下會發生這種情況。 這是 RAY 相當令人沮喪的方面,因為我無法真正預測每個df_list將有多少內存,它可以從 1 倍到 3 倍不等......

最后,堅持使用線程進行multiprocessing是最有效的解決方案,因為處理部分(沒有 I/O)更快:

from multiprocessing import Pool

# Create the shared array in the parent process & exploit copy-on-write (fork) semantics
shared_array = create_lookup_table(my_inputs)

def process_group(my_data):
   # Process a new dataframe here using my_data and some other data inside shared_array
   ...
   return my_df


n_workers = 60
with Pool(processes=n_workers) as pool:
   for data_list in many_data_lists:
      # data_list contains thousands of elements. I choose a chunksize of 10
      df_list = pool.map(process_group, data_list, 10)
      write_thread = threading.Thread(target=write_to_parquet, args=(group_df_list, tinterval.left))
            write_thread.start()

在每次循環迭代中,通常len(many_data_lists) = 7000並且每個列表包含 7 個大小為 (3, 9092) 的 numpy 數組。 所以這 7000 個列表被發送到 60 個工人:

每個循環迭代的所有並行process_group時間:

射線: 250 [s]

多處理: 233 [s]

I/O:5GB的parquet文件寫入外置USB 3轉盤大約需要35s。 在內部旋轉磁盤上大約 10 秒。

Ray :使用write_to_parquet.remote()創建未來需要大約 5 秒的開銷,這會阻止循環。 這仍然是在旋轉磁盤上寫入所需時間的 50%。 這並不理想。

多處理:測量的開銷為 0 秒。

總牆時間:

射線: 486 [s]

多處理436 [s]

我重復了幾次, RayMultiprocessing之間的差異始終顯示Multiprocessing快了約 50 秒。 這是一個顯着的差異,也令人費解,因為Ray宣傳更高的效率。

我將運行更多次迭代並報告穩定性(內存,垃圾收集的潛在問題,......)

您是否考慮過將 1 個內核分配給將數據寫入文件的 ray 任務?

[更新] 原型

import ray
import pandas as pd
# from multiprocessing import Pool

ray.init(num_cpus=60)

@ray.remote
def write_to_parquet(data, filename):
    # write it until succeed.
    # record failed write somewhere. 
    # I assume failure to write is uncommon. You can probably just 
    # write ray.put() and have one background process that keeps failed 
    # write again.

# with Pool(processes=n_workers) as pool:
for data_list in many_data_lists:
   ##-----------------------
   ## With ray :
   df_list = ray.get([process_group.remote(data) for data in data_list])
   ##-----------------------
   ## With multiprocessing :
   #f_list = pool.map(process_group, list_of_indices_into_data_list)
   ##
   ##      data are both known from the parent process
   ##      and I use copy-on-write semantic to avoid having 60 copies.
   ##      All the function needs are a list of indices
   ##      of where to fetch slices of the read-only data.  
   ##
   very_big_df = pd.concatenate(df_list)
   ##-----------------------
   ## Write to file :

   write_to_parquet.remote(very_big_df, filename)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM