2010-11-16 21 views
12

我們已經有了一個基於Python的web服務器,它在啓動時使用cPickle取消了許多大數據文件。數據文件(使用HIGHEST_PROTOCOL進行醃製)在磁盤上大約爲0.4 GB,並以大約1.2 GB的Python對象的形式加載到內存中 - 大約需要20秒。我們在64位Windows機器上使用Python 2.6。如何將1GB的對象反序列化爲比cPickle更快的Python對象?

瓶頸肯定不是磁盤(只需不到0.5秒,以實際讀取這麼多數據),但內存分配和對象的創建(有幾百萬個對象被創建)。我們希望減少20秒以減少啓動時間。

有什麼辦法來反序列化超過對象成Python的1GB比cPickle快得多(如5到10倍)?因爲執行時間受內存分配和對象創建的約束,所以我認爲使用另一個不帶鉤子的技術,如JSON在這裏沒有幫助。

我知道有些解釋的語言有一種方法來他們的整個存儲器圖像保存爲一個磁盤文件,這樣他們就可以加載它放回內存中的所有一氣呵成,而無需爲每個對象分配/創建。有沒有辦法在Python中做到這一點,或實現類似的東西?

+1

這可能是您獲得固態硬盤的機會。這是爲了加快開發?爲了讓你做快速部署? 閱讀數據或取消數據的滯後性?如果你從一個空實例開始,啓動時間是多少? – Scott 2010-11-16 14:59:53

+0

請注意,我在我的問題中提到的瓶頸不是驅動器/讀取速度,而是取消打開和對象創建速度。對於快速部署來說更重要 - 讓我們的服務器能夠快速重啓。我不太確定這裏的「空實例」是什麼意思。 – 2010-11-16 15:08:28

+0

對於一個750MB的pickle二進制文件,使用gc.disable()/ gc.enable()封裝cPickle加載調用,大幅縮短了所需的總時間20倍左右。見[這裏](http://stackoverflow.com/a/36699998/2385420) – 2016-04-18 17:42:03

回答

16
  1. 嘗試名帥模塊 - 它的內部(使用字節編譯),故意沒有太多宣傳,但它的速度要快得多。請注意,它不會序列化像pickle這樣的任意實例,只是內置類型(不記得確切的約束,請參閱文檔)。另請注意,格式不穩定。

  2. 如果您需要初始化多個進程並且可以容忍一個始終加載的進程,那麼有一個優雅的解決方案:在一個進程中加載​​對象,然後除了按需分流進程之外什麼也不做。分叉速度很快(寫入時複製)並在所有進程之間共享內存。 [免責聲明:未經測試; unlike Ruby,Python的裁判計數將觸發頁面副本,這樣,如果你有巨大對象和/或訪問它們的一小部分,這可能是沒用的。]

  3. 如果你的對象包含很多像numpy的陣列的原始數據,你可以記憶 - 映射他們以更快速啓動。 pytables也適用於這些場景。

  4. 如果你只使用一小部分對象,那麼OO數據庫(如Zope的)可能會幫助你。雖然如果你在記憶中需要它們,你只會浪費很多的開銷以獲得小的收益。 (從來沒有用過,所以這可能是無稽之談)。

  5. 也許其他的python實現可以做到這一點嗎?不知道,只是一個想法...

+0

謝謝,有幫助的東西。僅供參考,在我對包含很多對象的大文件進行快速測試時,marshal.loads()大約是pickle.loads()的兩倍。 – 2010-11-16 16:37:33

+0

在一本巨大的字典中,這裏的經驗相同; marshal.load只需要0.78s,其中cPickle.load需要1.2s。 – unhammer 2011-08-18 12:16:06

+0

選項二會有問題,因爲在您引用這些對象的那一刻,將爲每個分叉的子流程複製對象。這是因爲每次訪問對象時,每個對象都有一個引用計數。這反過來就像改變對象並因此導致內存被複制。當涉及python時,基本上覆制寫入成爲訪問時的副本... – FableBlaze 2013-01-04 23:23:00

3

我還沒有使用cPickle(或Python),但在這種情況下,我認爲最好的策略是 避免不必要的對象加載,直到它們真的需要 - 比如在不同的線程上啓動後加載它通常更好,以避免由於顯而易見的原因在任何時候不必要的加載/初始化Google'延遲加載'或'延遲初始化'。如果你真的需要所有的對象在服務器啓動之前完成一些任務,那麼也許你可以嘗試實現一個手動的自定義反序列化方法,換句話說,如果你對你將要處理的數據有深入的瞭解,就可以自己實現一些東西,這可以幫助你'擠'更好的表現,然後處理它的一般工具。

3

您是否嘗試通過不使用HIGHEST_PROTOCOL來犧牲酸洗效率?目前尚不清楚使用該協議會帶來哪些性能成本,但可能值得一試。

+0

好想法。但是,我們最初使用的是默認(最低)協議,但切換到HIGHEST_PROTOCOL(基於二進制的協議)將其速度提高了兩倍。所以HIGHEST_PROTOCOL肯定更快。 – 2010-11-16 16:00:59

2

不可能回答這個不知道更多關於你正在裝載什麼樣的數據以及如何使用它。

如果是某種商業邏輯的,也許你應該嘗試把它變成一個預編譯的模塊;

如果是結構化的數據,可以將它委託給一個數據庫,只有拉需要什麼?

數據是否有規律的結構?有什麼辦法可以將它分開並決定需要什麼,然後才能加載它?

7

您是否直接從文件加載()醃製數據?怎麼樣嘗試將文件加載到內存中,然後執行加載? 我會開始嘗試cStringIO();或者你也可以嘗試編寫你自己的StringIO版本,它將使用buffer()來分割內存,從而減少所需的copy()操作(cStringIO仍然可能更快,但你必須嘗試)。

特別是在Windows平臺上進行這類操作時,有時會出現巨大的性能瓶頸; Windows系統在某種程度上非常不優化,因爲在處理很多小的讀取時,UNIX可以很好地應對;如果load()會執行很多小的讀操作,或者您多次調用load()來讀取數據,這將有所幫助。

+1

對於給我-1的人:嘗試通過在Win上調用read(1)來加載文件;然後嘗試在Unix上完成。在Windows上讀取幾兆字節需要幾秒鐘的時間;在Unix上它仍然是即時的。如果benhoyt通過從一個文件調用數萬個pickle.load()調用來加載大量對象,這可能是一個因素。 – ondra 2010-11-16 16:05:19

+0

良好的通話。在我們的數據上,將「obj = pickle.load(f)」改爲「s = f.read(); obj = pickle.loads(s)」會使速度增加30%。沒有數量級,但值得了解。 (順便說一句,我不小心按下了,而不是up;隨意對你的答案做一個小的編輯,這樣我就可以upvote了。) – 2010-11-16 16:10:07

+0

我發現這裏有一個相當大的改進,可以用marshal模塊做同樣的過程因爲它是一個Windows問題)。 – 2010-11-16 18:47:22

2

我將添加另一個答案,可能是有益的 - 如果可以的話,你可以嘗試定義_ 插槽 _上是最常見的類產生的?這可能會有一點限制和不可能,但是它似乎已經將測試初始化​​所需的時間縮短了大約一半。