2015-10-16 52 views
3

我有一個python腳本,它可以在以下方案中工作:讀取一個大文件(例如電影) - 將選定的信息組合成一些小的臨時文件 - 在子進程中產生一個C++應用程序執行文件處理/計算(分別爲每個文件) - 讀取應用程序輸出。爲了加速我使用多處理的腳本。但是,它有一個主要缺點:每個進程必須在RAM中保留大型輸入文件的整個副本,因此我只能運行很少的進程,因爲內存不足。因此,我決定嘗試使用多線程(或多處理和多線程的某種組合),因爲線程共享地址空間。由於python部分大部分時間都適用於文件I/O或等待C++應用程序完成,因此我認爲GIL在這裏一定不是問題。儘管如此,我並未看到業績有所增長,主要歸功於I/O部分。python中的多線程I/O放緩

我說明用下面的代碼(保存爲test.py)的問題:

import sys, threading, tempfile, time 

nthreads = int(sys.argv[1]) 

class IOThread (threading.Thread): 
    def __init__(self, thread_id, obj): 
     threading.Thread.__init__(self) 
     self.thread_id = thread_id 
     self.obj = obj 
    def run(self): 
     run_io(self.thread_id, self.obj) 

def gen_object(nlines): 
    obj = [] 
    for i in range(nlines): 
     obj.append(str(i) + '\n') 
    return obj 

def run_io(thread_id, obj): 
    ntasks = 100 // nthreads + (1 if thread_id < 100 % nthreads else 0) 
    for i in range(ntasks): 
     tmpfile = tempfile.NamedTemporaryFile('w+') 
     with open(tmpfile.name, 'w') as ofile: 
      for elem in obj: 
       ofile.write(elem) 
     with open(tmpfile.name, 'r') as ifile: 
      content = ifile.readlines() 
     tmpfile.close() 

obj = gen_object(100000) 
starttime = time.time() 
threads = [] 
for thread_id in range(nthreads): 
    threads.append(IOThread(thread_id, obj)) 
    threads[thread_id].start() 
for thread in threads: 
    thread.join() 
runtime = time.time() - starttime 
print('Runtime: {:.2f} s'.format(runtime)) 

當我與不同數量的線程運行它,我得到這個:

$ python3 test.py 1 
Runtime: 2.84 s 
$ python3 test.py 1 
Runtime: 2.77 s 
$ python3 test.py 1 
Runtime: 3.34 s 
$ python3 test.py 2 
Runtime: 6.54 s 
$ python3 test.py 2 
Runtime: 6.76 s 
$ python3 test.py 2 
Runtime: 6.33 s 

能有人給我解釋一下結果,以及給出一些建議,如何有效地使用多線程並行I/O

編輯:

放緩並不是由於硬盤的性能,這是因爲:

1)中的文件越來越緩存反正到RAM

2)多相同的操作(不多線程)確實變得越來越快(幾乎是CPU數量的因素)

+0

阿里納斯 - 我總是喜歡用'multiprocessing'和'multiprocessing.dummy'輕鬆測試多處理與多線程問題。它提供了一個簡單的API和流程和線程之間的無障礙切換。 – arvindch

+0

你有沒有考慮過使用內存映射?這將映射文件到內存中,但在進程之間共享。然後操作系統將在必要時執行實際的IO。當它不再被使用時它也將釋放RAM,即它不交換。 –

回答

0

正如我鑽研更深的問題,我爲4種不同的並行化方法制定了比較基準,其中3個使用python,1個使用java(測試的目的不是比較不同語言之間的I/O機制,但看看多線程是否可以增強I/O操作)。測試是在Ubuntu 14.04.3上執行的,所有文件都放在RAM磁盤上。雖然數據相當嘈雜,但明顯的趨勢是明顯的(參見圖表;每條柱線n = 5,誤差條表示SD):python多線程無法提升I/O性能。最可能的原因是GIL,因此無法繞過它。

enter image description here

-1

我認爲你的性能指標並不是謊言:你問你的硬盤在sa上做很多事情我的時間。在關閉文件時讀取,寫入fsync,...以及同時處理多個文件。它觸發了很多硬件物理操作。而你同時寫入的文件越多,爭用得越多。

所以CPU在等待磁盤操作完成...

而且,也許你沒有SSD硬盤,所以同步實際上意味着一些身體動作。

編輯:它可能是一個GIL問題。當您在run_io中的obj中迭代elem時,您會在每次寫入之間執行python代碼。 ofile.write可能會釋放GIL,以便IO不會阻塞其他線程,但是每次迭代都會釋放/獲取鎖。所以也許你的寫作並不真正「同時」運行。

EDIT2:測試你可以嘗試更換假設:

for elem in obj: 
    ofile.write(elem) 

有:

ofile.write("".join(obj)) 

,看看是否PERF變得更好

+0

請參閱編輯 – Roman

+0

即使在最糟糕的情況下使用GIL,我也會期望具有可比較的運行時間,而不是2倍速度下降 – Roman

+0

替換後效果並不理想 – Roman