2011-07-04 22 views
3

我試圖從一些巨大的文件中讀取數據,並將其寫回,但我意識到,主要的成本從數據分配到一個列表而不是讀或從/到文件中寫入數據出來....是列表在python中表現不佳?

rows = [None] * 1446311 
    begin = datetime.datetime.now() 
    for i in range(1446311): 
     row = csvReader.next() 
     rows[i] = row 
    print datetime.datetime.now() - begin 

上面的代碼是18秒但5秒,如果我註釋掉第5行(rows[i] = row),我已經預先建立列表(即保留內存),但爲什麼它仍然如此緩慢?我能做的任何事情都能讓它變得更快?我試圖row for row in csvReader,但它執行得很差......

問候, 約翰

+0

我看到L5在那裏和不在那裏的運行時間沒有太大的區別。 (不過我不得不僞造csvReader.next()調用,這可能會產生影響)。 – 2011-07-04 11:55:08

+0

正如Gareth解釋的那樣,您沒有爲所有實際行預先分配內存,並且這種分配是花費時間的。如果你不要求所有的行同時在內存中,你可以通過構建你的代碼來使用生成器/生成器表達式來提高性能 –

回答

6

我得到了類似的結果,但沒有這麼戲劇化你的。 (注意使用timeit模塊代碼執行計時的,請注意,我已經排除,因爲其常見的兩種測試案例列表創建。)

import csv 
from timeit import Timer 

def write_csv(f, n): 
    """Write n records to the file named f.""" 
    w = csv.writer(open(f, 'wb')) 
    for i in xrange(n): 
     w.writerow((i, "squared", "equals", i**2)) 

def test1(rows, f, n): 
    for i, r in enumerate(csv.reader(open(f))): 
     rows[i] = r 

def test2(rows, f, n): 
    for i, r in enumerate(csv.reader(open(f))): 
     pass 

def test(t): 
    return (Timer('test%d(rows, F, N)' % t, 
        'from __main__ import test%d, F, N; rows = [None] * N' % t) 
      .timeit(number=1)) 

>>> N = 1446311 
>>> F = "test.csv" 
>>> write_csv(F, N) 
>>> test(1) 
2.2321770191192627 
>>> test(2) 
1.7048690319061279 

這是我的猜測,到底是怎麼回事。在這兩個測試中,CSV閱讀器都從該文件中讀取一條記錄,並在代表該記錄的內存中創建一個數據結構。

test2中,沒有存儲記錄,數據結構立即被刪除或更多(在循環的下一次迭代中,row變量被更新,因此前一記錄的引用計數減少,並且所以內存被回收)。這使得用於上一個記錄的內存可以重複使用:這個內存已經在計算機的虛擬內存表中,並且可能仍然在緩存中,所以它(相對)很快。

test1中,每條記錄都必須分配到一個新的內存區域,該區域必須由操作系統分配並複製到緩存中,因此速度相對較慢。

所以時間不被列表分配,而是由內存分配


這裏有另一對夫婦的那說明這是怎麼回事測試,而不csv模塊的複雜因素。在test3中,我們爲每一行創建一個新的100個元素的列表,並存儲它。在test4中,我們爲每一行創建一個新的100個元素的列表,但我們不存儲它,我們把它扔掉,這樣內存可以在下一次循環時重新使用。

def test3(rows, f, n): 
    for i in xrange(n): 
     rows[i] = [i] * 100 

def test4(rows, f, n): 
    for i in xrange(n): 
     temp = [i] * 100 
     rows[i] = None 

>>> test(3) 
9.2103338241577148 
>>> test(4) 
1.5666921138763428 

所以我覺得教訓是,如果你不需要在內存中的所有行存儲在同一時間,不這樣做!如果可以的話,一次只讀一個,逐個處理它們,然後忘記它們,以便Python可以釋放它們。

+0

你提到的時間是從內存分配中獲得的,你是否意味着內存列表內容或列表的引用(指針)? test3和test4都只爲列表內容分配內存,即我認爲是[i] * 100? – John

+0

我的意思是所有行的內存。另外,測試3和4每次循環分配一個新的100個元素的列表*。 –

+0

以及你說下一個循環可以從我們從前一個循環中分配的內存中獲益,爲什麼這些內存會與其他「狂野」但空閒內存塊不同?因爲我們已經取消了之前分配的內存,對吧? – John

0

編輯:第一部分是不那麼有效(見下面的評論)

你做一個嘗試這樣的:

rows = [None] * 1446311 
for i in range(1446311): 
    rows[i] = csvReader.next() 

因爲從我在你的代碼中看到,您正在複製數據兩次:一個從文件到內存使用row = ...,一次從rowrows[i]。由於你在這裏有非易變的東西(字符串),所以我們真的在談論數據的副本,而不是副本的引用。此外,即使您之前創建了一個空列表,您也會在其中放入大量數據;因爲您在開始時只放置了None,因此沒有保留實際的內存空間。所以也許你可以直接寫一個這樣的簡單東西:

rows = [] 
for i in range(1446311): 
    rows.append(csvReader.next()) 

或者甚至可能甚至直接使用生成器語法!

rows = list(csvReader) 

編輯 閱讀加雷思的答案後,我做了我的建議了一段時間的測試。順便說一句,小心地把從迭代器讀取時,一些保護,以阻止很好如果迭代比預期的短:

>>> from timeit import Timer 
>>> import csv 
>>> # building some timing framework: 
>>> def test(n): 
    return min(Timer('test%d(F, N)' % t, 
        'from __main__ import test%d, F, N' % t) 
      .repeat(repeat=10, number=1)) 

>>> F = r"some\big\csvfile.csv" 
>>> N = 200000 
>>> def test1(file_in, number_of_lines): 
    csvReader = csv.reader(open(file_in, 'rb')) 
    rows = [None] * number_of_lines 
    for i, c in enumerate(csvReader): # using iterator syntax 
     if i > number_of_lines: # and limiting the number of lines 
      break 
     row = c 
     rows[i] = row 
    return rows 

>>> test(1) 
0.31833305864660133 

>>> def test2(file_in, number_of_lines): 
    csvReader = csv.reader(open(file_in, 'rb')) 
    rows = [None] * number_of_lines 
    for i, c in enumerate(csvReader): 
     if i > number_of_lines: 
      break 
     row = c 
    return rows 

>>> test(2) 
0.25134269758603978 # remember that only last line is stored! 

>>> def test3(file_in, number_of_lines): 
    csvReader = csv.reader(open(file_in, 'rb')) 
    rows = [None] * number_of_lines 
    for i, c in enumerate(csvReader): 
     if i > number_of_lines: 
      break 
     rows[i] = c 
    return rows 

>>> test(3) 
0.30860502255637812 

>>> def test4(file_in, number_of_lines): 
    csvReader = csv.reader(open(file_in, 'rb')) 
    rows = [] 
    for i, c in enumerate(csvReader): 
     if i > number_of_lines: 
      break 
     rows.append(c) 
    return rows 

>>> test(4) 
0.32001576256431008 

>>> def test5(file_in, number_of_lines): 
    csvReader = csv.reader(open(file_in, 'rb')) 
    rows = list(csvReader) 
    # problem: there's no way to limit the number of lines to parse! 
    return rows 

>>> test(5) 
0.30347613834584308 

我們可以看到,對於A N比的行數較大在文件中,時間沒有太大的差異。在我的機器上,test2無疑只有一點點不同。 test5更優雅,但不能限制解析的行數,這可能令人討厭。

所以,如果你一次需要所有的線路,我的建議是去最優雅的解決方案,即使有點長:test4。但也許,正如Gareth所問,你並不需要一次性完成任何事情,這是獲得速度和記憶力的最佳途徑。

+0

您是否嘗試過這些建議以查看它們是否對運行時間產生影響? –

+0

我在閱讀Gareth的回答後做到了。請參閱我的答案中的編輯。 –

+0

@Gareth哦,對不起,我錯過了你是在尋求更多的材料。抱歉,答案延遲了,我花時間格式化了。 –