2014-10-01 44 views
0

標記這個作爲回答,並開始圍繞在速度問題真正出現一個較簡單的主題是Python的多文本提取性能問題VS Perl的等效

Python slow read performance issue

感謝迄今爲止的所有意見,非常有用

我有大約40M XML文件傳播(不均勻)跨約。 60K子目錄,該結構是基於一個10位數字分割這樣:

三十四分之一十二/ 56 /九十〇分之七十八/ files.xml

我有這是違背牽引的值的文件perl腳本單個字段輸出並打印值和文件名。 Perl腳本包裝在一個bash腳本中,該腳本在深度爲2的所有目錄的列表中運行最多12個並行實例,然後遍歷每個目錄並在發現它們時處理底層的文件。

以磁盤緩存了來自多個運行過程的UNIX時間返回約:

real 37m47.993s 
user 49m50.143s 
sys  54m57.570s 

我想這個遷移到python腳本(作爲練習和測試),所以產生以下(後很多Python的方法不同的東西)閱讀了:

import glob, os, re 
    from multiprocessing import Pool 

    regex = re.compile(r'<field name="FIELDNAME">([^<]+)<', re.S) 

    def extractField(root, dataFile): 
      line = '' 
      filesGlob = root + '/*.xml' 
      global regex 
      for file in glob.glob(filesGlob): 
        with open(file) as x: 
          f = x.read() 
        match = regex.search(f) 
        line += file + '\t' + match.group(1) + '\n' 

      dataFile.write(line) 

    def processDir(top): 
      topName = top.replace("/", "") 
      dataFile = open('data/' + topName + '.data', 'w') 
      extractField(top, dataFile) 
      dataFile.close() 

    filesDepth5 = glob.glob('??/??/??/??/??') 
    dirsDepth5 = filter(lambda f: os.path.isdir(f), filesDepth5) 
    processPool = Pool(12) 
    processPool.map(processDir, dirsDepth5) 
    processPool.close() 
    processPool.join() 

但無論我如何切的內容,當我運行UNIX時間給了我這樣的結果:

real 131m48.731s 
user 35m37.102s 
sys  48m11.797s 

如果我在單個線程中針對一個小子集運行python和perl腳本(最終獲得完全緩存),因此沒有磁盤io(根據iotop),那麼腳本幾乎在相同時間運行。

到目前爲止我能想到的唯一結論是,文件io在python腳本中比在perl腳本中效率低得多,因爲它似乎是造成問題的io。

所以希望這是足夠的背景,我的問題是我做了一些愚蠢的事情或錯過了一個竅門,因爲我正在用盡想法,但不能相信io正在造成處理時間上的這種差異。

欣賞任何指針,並會根據需要提供更多信息。

感謝

供參考的Perl腳本如下:

use File::Find; 

my $cwd = `pwd`; 
chomp $cwd; 
find(\&hasxml, shift); 

sub hasxml { 
    if (-d) { 
     my @files = <$_/*.xml>; 
     if (scalar(@files) > 0) { 
      process("$cwd/${File::Find::dir}/$_"); 
     } 
    } 
} 

sub process { 
    my $dir = shift; 

    my @files = <$dir/*.xml>; 

    foreach my $file (@files) { 
     my $fh; 
     open($fh, "< $file") or die "Could not read file <$file>"; 
     my $contents = do { local $/; <$fh> }; 
     close($fh); 
     my ($id) = $contents =~ /<field name="FIELDNAME">([^<]+)<\/field>/s; 
     print "$file\t<$id>\n"; 
    } 
} 
+0

也許這將幫助你:http://stackoverflow.com/questions/14863224/efficient-reading-of-800-gb-xml-file-in-python-2-7 我認爲你應該剖析你的應用程序,看看它花費的最多時間,否則這是一個猜測... – 2014-10-01 22:44:35

+0

我會看看謝謝,啊應該添加我正在閱讀的XML文件是每個至多4K大小,但我會嘗試緩衝提到看到。我試圖分析,但是當我運行 - python -m cProfile script.py它barf,我懷疑它不知道如何處理multiprocessing元素,但我承認我沒有詳細查看錯誤。 – Simon 2014-10-01 22:47:45

+0

user/sys較低(提示python更快)但實際更高(暗示我們沒有並行化)...父/子協議很重要。嘗試'processPool.map(processDir,dirsDepth5,chunksize = 16)',它可以大批量發送作業,看看它是否有所作爲。 – tdelaney 2014-10-01 23:02:54

回答

1

編輯

忘了感謝添加到貢獻者在此線程:

Python slow read performance issue

誰幫我解決這個問題。

編輯

這一切到底目錄中讀取的順序歸結,這適用於我的主要應用以及測試。

基本上Perl默認使用詞法(即1,11,2,22)排序,Python按目錄順序排序(ls -U),文件按自然順序(1,2,3,4)創建我把原來的Python啜食,有些搜索#1之後創建一個slurpNatural一個簡單的自然排序:

import glob, sys, re 

def natural_sort_key(s, _nsre=re.compile('([0-9]+)')): 
    return [int(text) if text.isdigit() else text.lower() 
      for text in re.split(_nsre, s)] 

for file in sorted(glob.iglob(sys.argv[1] + '/*.xml'), key=natural_sort_key): 
    with open(file) as x: 
     f = x.read() 

然後我跑了所有的3對50K文檔並獲得:

$ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 
$ /usr/bin/time perl slurp.pl 1 
1.21user 2.17system 0:12.70elapsed 26%CPU (0avgtext+0avgdata 9140maxresident)k 
1234192inputs+0outputs (22major+2466minor)pagefaults 0swaps 

$ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 
$ /usr/bin/time python slurp.py 1 
2.88user 6.13system 4:48.00elapsed 3%CPU (0avgtext+0avgdata 8020maxresident)k 
1236304inputs+0outputs (35major+52252minor)pagefaults 0swaps 

$ sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches' 
$ /usr/bin/time python slurpNatural.py 1 
1.25user 2.82system 0:10.70elapsed 38%CPU (0avgtext+0avgdata 22408maxresident)k 
1237360inputs+0outputs (35major+56531minor)pagefaults 0swaps 

的自然排序,其反映創作順序顯然是最快的,在這種情況下反映了我的實際情況ata被創建,所以現在已經改變了Python以在處理之前對目錄內容進行排序。

感謝所有的幫助,我真的不認爲閱讀文件的順序會產生如此大的差異!

+0

我知道這個答案已經有一段時間了,但我現在才注意到它。我認爲你應該考慮接受你自己的答案,而不是我的答案。看起來它在解決您的問題中的實際問題方面做得更好。我從閱讀中學到了一些新東西,並且認爲別人也會這樣。接受它會將它撞到頂部以保證它被看到(現在可能不正確,因爲我的被接受並擁有更多的選票)。 – skrrgwasme 2015-08-12 17:46:22

4

根據您的XML文件的結構,你可以通過使用mmap得到一些節省時間。目前,您正在閱讀整個文件,即使您只對一個條目感興趣。如果您的數據傾向於出現在文件頂部附近,那麼您可以將文件映射到內存中,而不是實際讀入文件,執行完全相同的正則表達式搜索並完成它。

下面是兩種方法的比較:

我有一個名爲「tmp_large.txt」,這在其百萬行的文本文件。每行都有小寫字母。在短短的一行約在文件中途,我把它換成了字母「M」與「X」,而我在尋找該字符串:

import re 
import mmap 

from timeit import timeit 
from datetime import timedelta 

c_regex = re.compile('defghijklxnopqrstuvwx') 

def read_file(): 
    with open('tmp_large.txt', 'r') as fi: 
     f = fi.read() 
     match = c_regex.search(f) 

def mmap_file(): 
    with open('tmp_large.txt', 'r+b') as fi: # must open as binary for mmap 
     mm = mmap.mmap(fi.fileno(), 0) 
     match = c_regex.search(mm) 
     mm.close() 

t1 = timedelta(seconds=timeit(read_file, setup='gc.enable()', number=1)) 
t2 = timedelta(seconds=timeit(mmap_file, setup='gc.enable()', number=1)) 

print(t1) 
print(t2) 

這種情況產生這樣的輸出:

0:00:00.036021
0:00:00.028974

我們看到了不到三分之一的節約執行時間。但是,如果我把我在輸入文件的頂部尋找的字符串,我們看到這樣的結果:

0:00:00.009327
0:00:00.000338

顯然,兩種方法都更快,但對於內存映射方法來說,節省時間更爲重要。

由於我不知道數據的結構或文件的大小,因此您可能會看到不太顯着的結果。但只要你所尋找的數據不在你的目標文件的末尾,你可能會發現內存映射文件有一點改進,因爲它會避免將數據帶入內存中實際上最終會使用。

作爲一個方面說明,我也嘗試迭代文件中的行,直到找到與正則表達式匹配的行,但是它的速度太慢而無法打擾。另外,我也證實,正則表達式實際上是在我的例子匹配,但我取出的打印代碼,爲簡潔

正如意見建議,使用迭代iglob,並與一些替代的地圖就像apply_async可能有助於速度的東西結果漲得,因爲他們都幫助您降低內存佔用:

processPool = Pool(12) 

for dir_or_file in glob.iglob('??/??/??/??/??'): 
    if os.path.isdir(dir_or_file): 
     processPool.apply_async(processDir, (dir_or_file,)) 

processPool.close() 
processPool.join() 

這種方法也可以讓你的子過程,以便開始在處理第一個文件,而你還在確定的其餘部分。

一些其他的代碼說明中要:

  1. 你並不需要在你的正則表達式的re.S標誌,因爲你實際上並沒有任何「」在正則表達式模式。
  2. 除非您有一些令人信服的理由,否則您應該使用with open()構造打開輸出文件,就像打開輸入文件一樣,以防止在您有異常時打開文件描述符。
  3. 當您計算dataFile和filesGlob時,請考慮使用os.path.join()而不是手動添加路徑分隔符。從長遠來看,這將不太容易出錯。
  4. 你不需要你的global regex線。您可以隨時閱讀並調用全局對象的方法,就像我的示例一樣。當你要修改全局時你只需要它。
  5. 如果您不知道,mutliprocessing pools默認情況下只會啓動儘可能多的工作站,因爲您擁有CPU內核。如果您已經知道,請忽略此評論。爲您的游泳池指定12個進程對我來說似乎有點奇怪。
+0

這是一些很好的信息謝謝,我已經完成了你所提到的,它已經改進了一些東西,但它看起來磁盤讀取速度仍然是一個問題,因爲即使Perl的版本大大超出了執行Python的變化,一組大約25萬個文件總共約1GB的Python花了35s和Perl 17s。我已經開始了一個關於這個https://stackoverflow.com/questions/26178038/python-slow-read-performance-issue的新的減少問題。謝謝你的建議,在這個過程中教會了我很多:-) – Simon 2014-10-03 11:23:35

+0

@Simon:你可以用'iglob()'來得到pool.imap_unordered(process_dir,(d for d in iglob(...)if isdir (d)),chunksize = 1000):......以避免一次排隊所有文件。確保你捕捉並記錄(或返回)'process_dir()'中的所有異常。 – jfs 2014-10-03 16:48:39

+0

@Simon:據我所知'mmap()'不能提高Linux的性能,但它可能會在Windows上運行。但是,如果需要將可能的大文件作爲字符串處理,就像@SLawson用正則表達式顯示的那樣,'mmap()'很方便。如果是xml文件,你可以使用'etree.iterparse()'[避免使用正則表達式來解析xml/html](http://stackoverflow.com/a/1732454/4279):) – jfs 2014-10-03 16:56:19