2015-01-04 46 views
3

我在一個2.5GB的文件中迭代超過80米行來創建每行起始位置的偏移量列表。內存緩慢增加,直到我打到40米左右,然後在3-5秒內迅速增加1.5GB,然後由於內存不足而退出。當數字大於Python的sys.maxint時,它們是否需要更多內存?

經過一番調查後,我發現爆炸發生在當前偏移量(curr_offset)大約爲2b的時候,恰好在我的sys.maxint(2^31-1)附近。

我的問題是:

  • 待辦事項數大於所有的sys.maxint基本上需要更多的內存來存儲?如果是這樣,爲什麼?如果沒有,爲什麼我會看到這種行爲?
  • 什麼因素(例如哪個Python,哪個操作系統)決定了sys.maxint?
    • 在我使用64位Python的2010 MacBook Pro上,sys.maxint是2^63-1。
    • 在我使用64位IronPython的Windows 7筆記本電腦上,sys.maxint是較小的2^31-1。與32位Python相同。由於各種原因,我現在無法在Windows計算機上獲得64位Python。
  • 有沒有更好的方法來創建這個偏移量列表?

有問題的代碼:

f = open('some_file', 'rb') 
curr_offset = 0 
offsets = [] 
for line in f: 
    offsets.append(curr_offset) 
    curr_offset += len(line) 
f.close() 
+0

這是關於'梅森素'嗎? –

+0

我並不確定內部是如何工作的,但大於'sys.maxint'的數字會自動存儲爲'long'(理論上)允許數字爲無限數字。那些自動增長的大小,他們似乎是足夠高效的,在Python 3舊'int'被刪除,所有ints變成多頭。 – poke

+0

你確定這是因爲'curr_offset'的大小嗎?對我來說,看起來'偏移量'列表的大小似乎以4000萬個元素滿足了你機器的物理RAM限制。你有多少RAM,以及該程序在那個時候使用了多少? – poke

回答

0

是。高於某個閾值,python將長數字表示爲bignums,並佔用空間。

+1

詞彙要點:「bignums」在Python中被稱爲「longs」。 – EOL

+0

好的,但* bignums *是一個廣泛的(非Python特定的)術語。 –

2

整數大於sys.maxint將需要更多的內存,因爲它們存儲爲longs。如果您的sys.maxint只有2GB,那麼您使用的是32位版本 - 下載,安裝和使用,64位版本,並且您將避免該問題。你的代碼看起來很好!

+1

對於64位Windows,「sys.maxint == 2 ** 31 - 1」。 CPython ['PyIntObject'](https://hg.python.org/cpython/file/648dcafa7e5f/Include/intobject.h#l23)使用C'long',它在Windows上始終爲32位。 – eryksun

+0

@eryksun在我的Windows 7機器上有32位Python解釋器'>>> sys.maxint 2147483647'和'>>> sys.maxint == 2 ** 31-1 真的,但是當我計算時>> > 2 ** 31-1 2147483647L',爲什麼它使它的類型長,因爲它等於'sys.maxint'?請解釋我的這種行爲 –

+1

@TanveerAlam,在所有平臺上,'sys.maxint + 1 - 1'是一個'long' - 因爲顯然是'sys.maxint + 1','long' int'很長。所以,在你的情況下,'2 ** 31'是一個'long',因此表達式'2 ** 31 - 1'。順便說一句,'系統。在任何平臺上,maxint - 1 + 1都是一個'int',因爲減法首先完成,所以中間結果符合'int'。 –

0

如果您確實無法使用64位版本的Python,則可以將計算得到的偏移量保存在numpy.uint64數字(最大值爲2 ** 64-1)的NumPy數組中。這有點不方便,因爲當數組達到容量時必須動態擴展數組,但這會解決您的問題,並且可以在任何版本的Python上運行。

PS:一個更方便的基於NumPy的解決方案,不需要動態更新NumPy偏移數組的大小,在我的其他答案中給出。

1

2.5 GB文件中的偏移量不應超過8個字節。實際上,一個有符號的64位整數最大爲9223372036854775807,遠遠大於2.5G。

如果你有8000萬行,你應該需要不超過640 MB來存儲一個80M偏移量的數組。

我會調查,看看什麼是越野車與您的代碼或Python的,可能使用不同的容器(的64-bit integers也許是一個明確的numpy array),使用preinitialized list,甚至是不同的語言完全存儲和處理您的偏移,如C中的off_t,並帶有適當的編譯標誌。(如果你很好奇,想看看演示代碼,我寫了一個名爲sample的程序,它在輸入文件中存儲64位偏移到新行,以便能夠以更大規模進行油藏採樣比如GNU sort。)

2

下面是一個解決方案,即使在32位Python版本中也是如此:存儲行的長度(它們很小),轉換成一個64位整數的NumPy數組,然後計算補償:

import numpy 
with open('some_file', 'rb') as input_file: 
    lengths = map(len, input_file) 
offsets = numpy.array(lengths, dtype=numpy.uint64).cumsum() 

其中cumsum()計算行長度的累計總和。 80 M行將提供一個可管理的8 * 80 = 640 MB的偏移量陣列。

lengths列表的建築甚至可以通過建立長度的陣列numpy.fromiter()繞過:

import numpy 
with open('some_file', 'rb') as input_file: 
    offsets = numpy.fromiter((len(line) for line in input_file), dtype=numpy.uint64).cumsum() 

我想這應該是很難找到一個更快的方法,因爲使用單一數字類型( 64位整數)使得NumPy數組比Python列表更快。

+0

或者,在numpy數組的多個塊中執行它,然後在最後將它們連接在一起。 Numpy數組比普通列表速度更快,內存更高效。 – Rufflewind

+0

沒錯,但更好的選擇是讓NumPy爲你做這件事:這實際上是'fromiter()'做的,我猜想。您的評論激勵我添加此解決方案。 :) – EOL

0

追加到列表將重新分配列表的緩衝區,一旦它通過當前緩衝區的容量。我不知道Python是怎麼做的,但一個常用的方法是將緩衝區的大小分配爲1.5倍到2x的大小。這是一種指數運算,因此看到內存需求在接近尾聲時快速增加是很正常的。可能是整個清單的規模太大,一個快速測試將取代curr_offset += len(line)curr_offset += 1並看看你是否有相同的行爲。

+1

雖然它可能是指數級的,但它不應該高估內存大小的兩倍以上。 – Rufflewind

+0

內存需求不應該在接近結束時快速增加,因爲吞吐量(補充列表的增加量)保持不變。儘管如此,它確實應該呈指數級的大跳躍,但它們也成爲指數級罕見。 – EOL

+0

@EOL如果你正在觀看內存使用情況,那麼它看起來會突然跳躍,而且你可能沒有注意到跳轉次數減少了。 –

相關問題