用C寫Python擴展時哪些地方容易導致內存泄露?

最近想把公司一個演算法庫用C改寫,但是在和Python對象交互時擔心內存泄露。因為項目比較小,而且僅僅是實現一些文本相關的演算法,所以打算用原生的C擴展。


這種與腳本引擎交互的內存問題,通常存在於這種情況:一個native對象,既可能被C系統持有,也可能被腳本系統持有。

你最好將你的對象,至少是複雜對象,全部引用計數化。這樣可以非常直觀地解決這個問題:當它被一個腳本引擎的var持有時,所持對象的引用計數加一;當持有它的腳本引擎var被銷毀時,所持對象的引用計數減一。


用py寫,cython編譯,單純演算法庫不用考慮泄露,而且還有各種工具,沒什麼好擔心的。

不過這麼搗鼓下來,還未必有pypy快,所以寫程序最怕想多了……


如果是使用swig進行封裝的話,在介面文件中定義 `const * char` 可能會導致內存泄漏。

正常在swig的介面文件中聲明全局變數中const * char 是指向一個字面量的。但是swig仍會為這個變數創建對應的函數來修改和獲取這個變數的值。

例如我在`*.i`文件中聲明了一個變數

// file : *.i
const char * s = "Hello
";

但是swig仍會為s創建類似`s_set(char * value)`函數來另s指向其他地方。但是由於本身const是指向字面量的釋放其指向的內存並不合適,因此默認是不會釋放之前指向的內存的。因此如果你在python中改變了這個值,也就是另s指針指向了其他地方,那麼之前的內存並沒有釋放,在這裡則可能會導致內存泄漏。

舉個例子:

%module temp

%inline %{
const char * s = "original";
%}

使用swig對這個介面文件處理會生成temp_wrap.c文件,我只對這個文件進行編譯,也就等於我的擴展模塊只包含一個全局變數s。

$ swig -python temp.i

$ gcc -shared -fPIC temp_wrap.c -o _temp.so

這樣我在python中就可以:

&>&>&> from temp import *

&>&>&> cvar.s
original

&>&>&> cvar.s = "new" # 這個時候original的內存並沒有釋放

&>&>&> cvar.s
new


可能泄露有兩部分,一部分是 C 本身泄露,一部分是 C 與 Python 交互時引用計數沒做對造成泄露(或懸掛指針)。前者是 C 的問題,後者可以藉助 Cython、SWIG 這類工具生成介面代碼來盡量避免。C 內存泄露當然也有一些檢測工具,Valgrind 之類。


跑大規模數據量 出segfault的地方 +1 -1refcount 並沒有想像的那麼容易錯


C和Python介面的地方應該沒什麼泄露。

關鍵是你現在是用C,那所有內存分配的地方都有可能泄露唄。 實在不行可以Jython,或者上面提到的Cython


我的觀點是:用繼承/引用計數寫實現(在基類中檢查內存是否釋放),封裝成平板的C函數,對象用handle表示,用ctypes與dll/so交互。凡是分配內存的函數名添加_unsafe,且只能用在python class的構造函數中,並在析構函數中使用對應的析構函數

如:

class Message(object):

 def __init__(self):

  self._message_handle = create_message_unsafe(...)

 def __del__(self):

  release_message(self._message_handle)


推薦閱讀:

2017年,Web 後端出現了哪些新的思想和技術?
(做生物信息的)你們是怎麼知道Python裡面sys.argv和getopt這種函數的?
想入生物信息學這個行業,python學習要達到什麼程度???
怎麼評價新發布的odoo 11 ?
2017,再來聊一聊Python,未來發展怎樣?

TAG:Python | C編程語言 | 內存泄露 |