2011-06-08 89 views
6

我想了解發電機功能的性能。我已經使用cProfile和pstats模塊來收集和檢查分析數據。有問題的功能是這樣的:發電機功能性能

def __iter__(self): 
    delimiter = None 
    inData  = self.inData 
    lenData = len(inData) 
    cursor  = 0 
    while cursor < lenData: 
     if delimiter: 
      mo = self.stringEnd[delimiter].search(inData[cursor:]) 
     else: 
      mo = self.patt.match(inData[cursor:]) 
     if mo: 
      mo_lastgroup = mo.lastgroup 
      mstart  = cursor 
      mend   = mo.end() 
      cursor  += mend 
      delimiter = (yield (mo_lastgroup, mo.group(mo_lastgroup), mstart, mend)) 
     else: 
      raise SyntaxError("Unable to tokenize text starting with: \"%s\"" % inData[cursor:cursor+200]) 

self.inData是一個unicode字符串,self.stringEnd是4層簡單的正則表達式的一個字典,self.patt是一個大的正則表達式。整個事情就是將大字符串逐個分成更小的字符串。

剖析使用它,我發現該程序的運行時間最重要的部分在這個函數是花的程序:

In [800]: st.print_stats("Scanner.py:124") 

     463263 function calls (448688 primitive calls) in 13.091 CPU seconds 

    Ordered by: cumulative time 
    List reduced from 231 to 1 due to restriction <'Scanner.py:124'> 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    10835 11.465 0.001 11.534 0.001 Scanner.py:124(__iter__) 

但看函數本身的輪廓,沒有太多的時間花在在子函數中調用:

In [799]: st.print_callees("Scanner.py:124") 
    Ordered by: cumulative time 
    List reduced from 231 to 1 due to restriction <'Scanner.py:124'> 

Function     called... 
           ncalls tottime cumtime 
Scanner.py:124(__iter__) -> 10834 0.006 0.006 {built-in method end} 
           10834 0.009 0.009 {built-in method group} 
           8028 0.030 0.030 {built-in method match} 
           2806 0.025 0.025 {built-in method search} 
            1 0.000 0.000 {len} 

函數的其餘部分除了while,assignments和if-else之外沒有多少東西。即使在我使用發電機的send方法是快速:

ncalls tottime percall cumtime percall filename:lineno(function) 
13643/10835 0.007 0.000 11.552 0.001 {method 'send' of 'generator' objects} 

是否有可能在yield,傳值傳回給消費者,走的是大部分的時間?還有什麼我不知道的?

編輯

我也許應該提到的是,發電機功能__iter__是一個小類的方法,所以self指的是這個類的一個實例。

+3

inData有多大?反覆切片可能效率不高。也許如果你嘗試在itertools中使用islice。看看這是否有所作爲。 – Dunes 2011-06-08 20:45:20

+0

@Dunes謝謝,會嘗試。性能數據採用大約1MB的字符串。 - 如果你把這個答案放在答案中,我可以放棄它。 – ThomasH 2011-06-08 21:01:59

+0

你有沒有試過[this](http://stackoverflow.com/questions/4295799/how-to-improve-performance-of-this-code/4299378#4299378)? – 2011-06-09 04:13:22

回答

2

這實際上是Dunes的答案,不幸的是,它只是將它作爲註釋給出,似乎並不傾向於將它置於正確的答案中。

主要表現罪魁禍首是字符串切片。一些時間測量顯示,切片性能會隨着大切片而降低(意味着從已經很大的一串中切下一大片)。要解決,我現在使用pos參數爲正則表達式對象的方法:

if delimiter: 
     mo = self.stringEnd[delimiter].search(inData, pos=cursor) 
    else: 
     mo = self.patt.match(inData, pos=cursor) 

感謝all誰幫助。

+1

啊,對不起。這幾天我工作很忙。我只知道問題所在,因爲我的解決方案不夠充分。所以請相信你找到解決方案。 – Dunes 2011-06-11 13:24:57

+0

@Dunes我在問這個問題,所以你的評論是相當充分的。下次 :-) 。 – ThomasH 2011-06-11 19:53:47

1

如果正確讀取您的示例,那麼您正在生成一個生成器對象,將其放入delimiter中,並將其用於數組查找。這可能不是你的速度問題,但我很確定這是一個錯誤。

+1

如果你參考'delimiter =(yield ...)'部分,不需要。這個函數是一個**協程**,它允許用戶執行'co.send(x)',它恢復執行(像'next(generator)')並且使得(yield)...評估爲' (如果你只是用它作爲一個迭代器,它將評估爲「無」IIRC)。 – delnan 2011-06-08 18:28:08

+0

是的,正如delnan寫道的,我有時會使用短字符串傳遞給生成器(使用外部的.send),以使其切換爲爲下一個塊使用不同的正則表達式。 – ThomasH 2011-06-08 18:54:14