[英]multithreading for IO-bound tasks and multiprocessing for CPU-bound tasks
[英]Writing files concurrently with other cpu-bound tasks with multiprocessing or ray
我有一個有 72 個內核的工作站(實際上是 36 個多線程 CPU,通過multiprocessing.cpu_count()
顯示為 72 個內核)。
我嘗試了multiprocessing
和ray
進行並發處理,批量處理數百萬個小文件,我想在該處理過程中同時寫入一些輸出文件。
我對與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]
我重復了幾次, Ray和Multiprocessing之間的差異始終顯示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.