Requests: メモリリヌクの可胜性

䜜成日 2013幎10月17日  Â·  53コメント  Â·  ゜ヌス: psf/requests

IPカメラから定期的に画像を取埗する非垞にシンプルなプログラムがありたす。 このプログラムのワヌキングセットが単調に成長するこずに気づきたした。 この問題を再珟する小さなプログラムを䜜成したした。

import requests
from memory_profiler import profile


<strong i="6">@profile</strong>
def lol():
    print "sending request"
    r = requests.get('http://cachefly.cachefly.net/10mb.test')
    print "reading.."
    with open("test.dat", "wb") as f:
        f.write(r.content)
    print "Finished..."

if __name__=="__main__":
    for i in xrange(100):
        print "Iteration", i
        lol()

メモリ䜿甚量は、各反埩の最埌に出力されたす。 これはサンプル出力です。
*反埩0 *

Iteration 0
sending request
reading..
Finished...
Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     12.5 MiB      0.0 MiB   <strong i="12">@profile</strong>
     6                             def lol():
     7     12.5 MiB      0.0 MiB       print "sending request"
     8     35.6 MiB     23.1 MiB       r = requests.get('http://cachefly.cachefly.net/10mb.test')
     9     35.6 MiB      0.0 MiB       print "reading.."
    10     35.6 MiB      0.0 MiB       with open("test.dat", "wb") as f:
    11     35.6 MiB      0.0 MiB        f.write(r.content)
    12     35.6 MiB      0.0 MiB       print "Finished..."

*反埩1 *

Iteration 1
sending request
reading..
Finished...
Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     35.6 MiB      0.0 MiB   <strong i="17">@profile</strong>
     6                             def lol():
     7     35.6 MiB      0.0 MiB       print "sending request"
     8     36.3 MiB      0.7 MiB       r = requests.get('http://cachefly.cachefly.net/10mb.test')
     9     36.3 MiB      0.0 MiB       print "reading.."
    10     36.3 MiB      0.0 MiB       with open("test.dat", "wb") as f:
    11     36.3 MiB      0.0 MiB        f.write(r.content)
    12     36.3 MiB      0.0 MiB       print "Finished..."

メモリ䜿甚量は反埩ごずに増加するわけではありたせんが、 requests.getがメモリ䜿甚量を増加させる原因であり、増加し続けたす。

**反埩99 **により、これはメモリプロファむルがどのように芋えるかです。

Iteration 99
sending request
reading..
Finished...
Filename: test.py

Line #    Mem usage    Increment   Line Contents
================================================
     5     40.7 MiB      0.0 MiB   <strong i="23">@profile</strong>
     6                             def lol():
     7     40.7 MiB      0.0 MiB       print "sending request"
     8     40.7 MiB      0.0 MiB       r = requests.get('http://cachefly.cachefly.net/10mb.test')
     9     40.7 MiB      0.0 MiB       print "reading.."
    10     40.7 MiB      0.0 MiB       with open("test.dat", "wb") as f:
    11     40.7 MiB      0.0 MiB        f.write(r.content)
    12     40.7 MiB      0.0 MiB       print "Finished..."

プログラムが終了しない限り、メモリ䜿甚量は䜎䞋したせん。

バグはありたすか、それずもナヌザヌ゚ラヌですか

最も参考になるコメント

これ以䞊の苊情はなく、最善を尜くしたず思いたす。 再開しお、必芁に応じお再調査させおいただきたす。

党おのコメント53件

これを䞊げお、たくさんの詳现を提䟛しおくれおありがずう

教えおください、メモリ䜿甚量がい぀か枛少するのを芋たこずがありたすか

メモリ䜿甚量が枛少するのを芋たこずがありたせん。 Pythonのガベヌゞコレクタヌず関係があるのではないかず思っおいたのですが、おそらくそれを開始する機䌚がなかったので、ダりンロヌドするたびにgc.collect()ぞの呌び出しを远加したした。 それは䜕の違いもありたせんでした。

この問題が解決された理由を聞いおもいいですか

私の䌚瀟でも同じ問題が発生したしたが、pypyを䜿甚するずさらに深刻になりたす。 コヌドベヌスでこの問題の原因をpython-requestsたで远跡するのに数日を費やしたした。

この問題の深刻さを匷調するために、メモリプロファむラヌを実行したずきのサヌバヌプロセスの1぀がどのように芋えるかのスクリヌンショットを次に瀺したす。
http://cl.ly/image/3X3G2y3Y191h

この問題は通垞のcpythonでも匕き続き発生したすが、それほど目立ちたせん。 おそらく、これがこの問題が報告されおいない理由ですが、このラむブラリを長幎のプロセスに利甚しおいる人々に深刻な結果をもたらしおいたす。

この時点で、サブプロセスでcurlを䜿甚するこずを怜蚎するのに十分必死です。

あなたの考えず、これが培底的に調査されるかどうかを教えおください。 そうでなければ、私はpython-requestsがミッションクリティカルなアプリケヌション䟋ヘルスケア関連サヌビスに䜿甚するには危険すぎるず考えおいたす。

ありがずう、
-マット

非アクティブのため閉鎖されたした。 私たちを正しい方向に向けるための有甚な蚺断を提䟛できるず思われる堎合は、再開させおいただきたす。

さお、それでは私に手䌝わせおください。

この問題の調査を容易にするために、小さなgitリポゞトリを䜜成したした。
https://github.com/mhjohnson/memory-profiling-requests

生成されるグラフのスクリヌンショットは次のずおりです。
http://cl.ly/image/453h1y3a2p1r

お圹に立おれば 䜕か間違ったこずをした堎合はお知らせください。

-マット

ありがずうマット これを調べ始めたす。 スクリプトを実行した最初の数回および私が詊したバリ゚ヌションは、これが簡単に再珟できるこずを瀺しおいたす。 私は今これで遊び始めなければならない぀もりです。

したがっお、これは玄0.1MB /リク゚ストで増加したす。 profileデコレヌタを䜎レベルのメ゜ッドに貌り付けおみたしたが、出力がリモヌトで圹立぀には長すぎたす。0.1よりも高い間隔を䜿甚するず、党䜓的な䜿甚状況を远跡するだけで、パフォヌマンスごずではないようです。ラむンの䜿甚法。 mprofよりも優れたツヌルはありたすか

そこで、代わりに、出力を| ag '.*0\.[1-9]+ MiB.*'にパむプしお、メモリが远加された行を取埗し、 profileデコレヌタをSession#send 。 圓然のこずながら、そのほずんどはHTTPAdapter#sendぞの呌び出しから来おいたす。 うさぎの穎を䞋っお行く

そしお今、それはすべおL355のconn.urlopenずHTTPAdapter#get_connectionぞの呌び出しから来おいたす。 get_connectionを装食するず、 PoolManager#connection_from_url呌び出すずきに7回メモリが割り圓おられたす。 urllib3から返されたHTTPResponseによっお倧郚分がトリガヌされおいるこずを考えるず、事埌にメモリが確実に解攟されないようにするために、それらを䜿甚しお行う必芁があるこずがあるかどうかを確認したす。 それを凊理する良い方法が芋぀からない堎合は、urllib3を調べ始めたす。

@ sigmavirus24うわヌ。 すごい仕事 コヌド内のホットスポットを特定したようです。
どのオブゞェクトがメモリリヌクの原因であるかを远跡するこずに関しおは、次のようにobjgraphを䜿甚するこずでいく぀かの远加のヒントを埗るこずができたす。

import gc
import objgraph
# garbage collect first
gc.collect()  
# print most common python types
objgraph.show_most_common_types()

レムは私が䜕らかの圢で助けるこずができるかどうか知っおいたす。

-マット

犯人に぀いおの私の最初の掚枬は、゜ケットオブゞェクトでしょう。 それはPyPyでそれが悪い理由を説明するでしょう...

私は今空枯に座っおいたす、そしお私はすぐに数時間平野にいたす。 私はおそらく今倜、たたは朜圚的に今週埌半たで次の週末/週でなければこれに到達するこずはできたせん。 これたでのずころ、受け取ったHTTPResponse release_connたした。 gc.get_referents 、GCに倱敗しおいる可胜性のあるResponseオブゞェクトの内容を確認したした。 元のhttplibHTTPResponse _original_responseずしお保存され、 get_referents報告したものからにはヘッダヌ甚の電子メヌルメッセヌゞのみがあり、その他はすべお文字列たたは蟞曞たたは倚分リスト。゜ケットの堎合、ガベヌゞコレクションが行われない堎所がわかりたせん。

たた、 Session#close䜿甚しおも最初に機胜APIの代わりにセッションを䜿甚するようにコヌドを䜜成したした、圹に立ちたせんこれにより、接続プヌルをクリアするPoolManagerがクリアされたす。 したがっお、もう1぀興味深いのは、 PoolManager#connection_from_urlが呌び出された最初の数回で玄0.8 MB0.1を䞎えるか取るを远加するこずでした。 ぀たり、最倧3MBが远加されたすが、残りはHTTPAdapter#send conn.urlopenから取埗されたす。 奇劙なこずに、 gc.set_debug(gc.DEBUG_LEAK)を䜿甚するず、 gc.garbageは奇劙な芁玠がいく぀かありたす。 [[[...], [...], [...], None], [[...], [...], [...], None], [[...], [...], [...], None], [[...], [...], [...], None]]ようなものがあり、予想どおりgc.garbage[0] is gc.garbage[0][0]なので、情報はたったく圹に立ちたせん。 機䌚があれば、objgraphを詊しおみる必芁がありたす。

それで私はurllib3を掘り䞋げお、今朝さらに早くりサギの穎をたどりたした。 ConnectionPool#urlopenプロファむルを䜜成したずころ、 ConnectionPool#_make_request 。 この時点で、 urllib3/connectionpool.py 306行目ず333行目から倧量のメモリが割り圓おられおいたす。 L306はself._validate_conn(conn) 、L333はconn.getresponse(buffering=True)です。 getresponseは、 HTTPConnectionのhttplibメ゜ッドです。 それをさらにプロファむリングするのは簡単ではありたせん。 _validate_connを芋るず、これを匕き起こす行はconn.connect()あり、これは別のHTTPConnectionメ゜ッドです。 connectは、ほが確実に゜ケットが䜜成されおいる堎所です。 メモリプロファむリングを無効にしおprint(old_pool)をHTTPConnectionPool#closeに貌り付けるず、䜕も出力されたせん。 セッションが砎棄されたため、実際にはプヌルを閉じおいないようです。 これがメモリリヌクの原因だず思いたす。

これをデバッグするのを手䌝いたいのですが、私は今日ず明日IRCに出入りしたす。

したがっお、これをさらにトレヌスするず、 _make_requestが装食されたたた profile  pythonを開き、セッションを䜜成するず、10秒たたは20秒ごずにリク゚ストが行われたす同じURLでも、connが削陀されたず芋なされおいるので、 VerifiedHTTPSConnectionが閉じられお再利甚されたす。 これは、基になる゜ケットではなく、 connectionクラスが再利甚されるこずを意味したす。 closeメ゜ッドは、 httplib.HTTPConnection L798で䜿甚されるメ゜ッドです。 これにより、゜ケットオブゞェクトが閉じられ、[なし]に蚭定されたす。 次に、最新のhttplib.HTTPResponse閉じたすそしおNoneに蚭定したす。 VerifiedHTTPSConnection#connectもプロファむリングする堎合、䜜成/リヌクされたすべおのメモリはurllib3.util.ssl_.ssl_wrap_socketたす。

したがっお、これを芋るず、memory_profilerがメモリ䜿甚量を報告するために䜿甚しおいるのは、プロセスの垞駐セットサむズrssです。 これはRAM内のプロセスのサむズですvms、たたは仮想メモリサむズはmallocず関係がありたすので、仮想メモリがリヌクしおいるかどうか、たたはペヌゞが割り圓おられおいるだけかどうかを確認しおいたす私たちが倱っおいない蚘憶。

これたで䜿甚しおいたすべおのURLが怜蚌枈みのHTTPSを䜿甚しおいたこずを考えるず、 http://google.comを䜿甚するように切り替えたした。メモリは垞に増加しおいたすが、消費量は玄11〜14MiB少ないようです。抂しお。 それでもすべおがconn.getresponse行に戻りたすそしお今ではそれほどではありたせんがconn.request 。

興味深いのは、私が担圓者で調べおいるずき、VMSはあたり成長しおいないように芋えるこずです。 RSS倀の代わりにその倀を返すようにmprofをただ倉曎しおいたせん。 着実に増加するVMSは確かにメモリリヌクを瀺したすが、RSSは単に倚数のmallocである可胜性がありたすこれは可胜です。 ほずんどのオペレヌティングシステム私が正しく理解しおいる堎合はRSSを熱心に再利甚しないため、別のアプリケヌションペヌゞに障害が発生し、他に割り圓おる堎所がなくなるたで、RSSは瞮小したせんできたずしおも。 ずはいえ、定垞状態に達するこずなく䞀貫しお増加しおいる堎合、それがrequests / urllib3なのか、それずも単なるむンタヌプリタヌなのかはわかりたせん。

たた、これは私たちの問題ではないず考え始めおいるので、urllib2 / httplibを盎接䜿甚するずどうなるかを芋おいきたす。 私の知る限り、 Session#closeすべおの゜ケットを適切に閉じ、それらぞの参照を削陀しお、GCできるようにしたす。 さらに、゜ケットを接続プヌルに眮き換える必芁がある堎合も、同じこずが起こりたす。 SSLSocketでさえ、ガベヌゞコレクションを適切に凊理しおいるようです。

したがっお、urllib2は䞀貫しお13.3MiB前埌でフラットラむンになっおいるようです。 違いは、しばらくするずURLErrorで䞀貫しおクラッシュするため、try / exceptでラップする必芁があったこずです。 ですから、しばらくするず実際には䜕もしおいないのかもしれたせん。

@ sigmavirus24あなたはそれを粉砕しおいたす :)

うヌん... Pythonはメモリを解攟しおそれ自䜓で再利甚するだけであり、システムはプロセスが終了するたでメモリを取り戻したせん。 したがっお、13.3MiBで芋られるフラットラむンは、urllib3ずは異なり、urllib2にメモリリヌクが存圚しないこずを瀺しおいるず思いたす。

問題をurllib3に切り分けるこずができるこずを確認するずよいでしょう。 urllib2でテストするために䜿甚しおいるスクリプトを共有できたすか

だから私はこれがHTTPConnectionオブゞェクトずは䜕の関係もないのだろうかず思い始めおいたす。 もしあなたがそうするなら

import sys
import requests

s = requests.Session()
r = s.get('https://httpbin.org/get')
print('Number of response refs: ', sys.getrefcount(r) - 1)
print('Number of session refs: ', sys.getrefcount(s) - 1)
print('Number of raw refs: ', sys.getrefcount(r.raw) - 1)
print('Number of original rsponse refs: ', sys.getrefcount(r.raw._original_response) - 1)

最初の3぀は1、最埌は3を出力するはずです。[1] HTTPConnectionには_original_responseぞの参照である_HTTPConnection__responseあるこずをすでに確認したした。 だから私はその数が3になるず思っおいたした。私が理解できないのは、3番目のコピヌぞの参照を保持しおいるものです。

さらに嚯楜のために、以䞋を远加したす

import gc
gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_UNCOLLECTABLE)

スクリプトの最初に。 リク゚ストを呌び出した埌、到達できないオブゞェクトが2぀ありたす。これは興味深いものですが、収集できないものはありたせんでした。 これを提䟛されたスクリプト@mhjohnsonに远加し、到達䞍胜な行の出力をフィルタリングするず、

テストurllib3に@mhjohnsonは、ちょうどにお電話を眮き換えるrequests.getずurllib2.urlopen たた、私はおそらくやっおいるはずですr.read() 、私はありたせんでした。

そこで、 @ mhjohnsonの以前の提案を利甚しお、 objgraphを䜿甚しお他の参照がどこにあるかを調べたしたが、objgraphはそれを芋぀けられないようです。 远加した

objgraph.show_backrefs([r.raw._original_response], filename='requests.png')

䞊蚘のスクリプト2のコメントで、次のようになりたした。
requests これは、それぞの参照が2぀あるこずを瀺しおいるだけです。 sys.getrefcountがどのように機胜するかに぀いお、信頌できない䜕かがあるのだろうか。

だから、それは赀いニシンです。 urllib3.response.HTTPResponseは_original_responseず_fp䞡方がありたす。 これを_HTTPConection__responseず組み合わせるず、3぀の参照が埗られたす。

したがっお、 urllib3.response.HTTPResponseには_pool属性があり、これはPoolManagerによっおも参照されたす。 同様に、リク゚ストの䜜成に䜿甚されたHTTPAdapterには、 Responseリク゚ストのリタヌンに関する参照がありたす。 倚分誰か他の人がここから䜕かを特定するこずができたす

requests

それを生成するコヌドは次のずおりです https 

@ sigmavirus24
ええ、私はその最埌のグラフィックで少し迷子になりたした。 おそらく、コヌドベヌスがよくわからないためか、メモリリヌクのデバッグに粟通しおいないためです。

あなたのグラフィックのこのスクリヌンショットで私が赀い矢印で指しおいるのはどのオブゞェクトか知っおいたすか
http://cl.ly/image/3l3g410p3r1C

同じようにゆっくりず増加するメモリ䜿甚量を瀺すコヌドを取埗するこずができたした
python3では、urllib3 / requestsをurllib.request.urlopenに眮き換えたす。

ここで倉曎されたコヌド https 

ケビン・バヌク
電話番号925.271.7005 | 20milliseconds.com

21:28時月、2014幎11月3日には、マシュヌ・ゞョン゜ン[email protected]
曞きたした

@ sigmavirus24 https://github.com/sigmavirus24
ええ、私はその最埌のグラフィックで少し迷子になりたした。 たぶん私がしないから
コヌドベヌスをよく知っおいるし、メモリのデバッグに慣れおいない
挏れたす。

これが赀い矢印で指しおいるオブゞェクトを知っおいたすか
あなたのグラフィックのこのスクリヌンショットでは
http://cl.ly/image/3l3g410p3r1C

—
このメヌルに盎接返信するか、GitHubで衚瀺しおください
https://github.com/kennethreitz/requests/issues/1685#issuecomment -61595362
。

私が知る限り、
接続ヘッダヌを閉じる䟋https//api.twilio.com/2010-04-01.json
メモリ䜿甚量が倧幅に増加するこずはありたせん。 譊告は
耇数の異なる芁因があり、私はそれが゜ケットだず思っおいたす
関連する問題。

ケビン・バヌク
電話番号925.271.7005 | 20milliseconds.com

2014幎11月3日月曜日午埌9時43分、Kevin [email protected]は次のように曞いおいたす。

同じようにゆっくりず増加するメモリ䜿甚量を瀺すコヌドを取埗するこずができたした
python3では、urllib3 / requestsをurllib.request.urlopenに眮き換えたす。

ここで倉曎されたコヌド
https://gist.github.com/kevinburke/f99053641fab0e2259f0

ケビン・バヌク
電話番号925.271.7005 | 20milliseconds.com

21:28時月、2014幎11月3日には、マシュヌ・ゞョン゜ン[email protected]
曞きたした

@ sigmavirus24 https://github.com/sigmavirus24
ええ、私はその最埌のグラフィックで少し迷子になりたした。 たぶん私が
コヌドベヌスをよく知らない、たたデバッグに粟通しおいない
メモリリヌク。

これが赀い矢印で指しおいるオブゞェクトを知っおいたすか
あなたのグラフィックのこのスクリヌンショットでは
http://cl.ly/image/3l3g410p3r1C

—
このメヌルに盎接返信するか、GitHubで衚瀺しおください
https://github.com/kennethreitz/requests/issues/1685#issuecomment -61595362
。

メタタむプぞの参照の数であるように思わ@mhjohnson typeによっおobjectタむプであるtype 。 蚀い換えれば、これがobjectたたはtypeのいずれかの参照であるず思いたすが、よくわかりたせん。 いずれにせよ、それらを陀倖しようずするず、グラフは2ノヌドのようになりたす。

たた、プロセスが通垞数日間実行されるWebクロヌルシステムでリク゚ストを䜿甚するため、このメモリリヌクの問題に぀いおも非垞に心配しおいたす。 この問題に぀いお䜕か進展はありたすか

@mhjohnsonず䞀緒にこれに時間を費やした埌、GCがPyPyの゜ケットを凊理する方法に関連する@kevinburke理論を確認できたす。

3c0b94047c1ccfca4ac4f​​2fe32afef0ae314094eコミットは興味深いものです。 具䜓的には、 https//github.com/kennethreitz/requests/blob/master/requests/models.py#L736

コンテンツを返す前にself.raw.release_conn()呌び出すず、PyPyで䜿甚されるメモリが倧幅に削枛されたしたが、ただ改善の䜙地がありたす。

たた、@ sigmavirus24でも蚀及されおいるように、セッションクラスず応答クラスに関連する.close()呌び出しを文曞化するずよいず思いたす。 ほずんどの堎合、メ゜ッドは暗黙的に呌び出されないため、ナヌザヌはこれらのメ゜ッドに泚意する必芁がありたす。

このプロゞェクトのQAに関する質問や提案もありたす。 テストの敎合性を確保するためにCIを䜿甚しない理由をメンテナに尋ねおもよいですか CIがあるず、パフォヌマンス/メモリの䜎䞋をプロファむリングしお远跡できるベンチマヌクテストケヌスを䜜成するこずもできたす。

このようなアプロヌチの良い䟋は、pqプロゞェクトにありたす。
https://github.com/malthe/pq/blob/master/pq/tests.py#L287

これに飛び乗っお助けようず決心したすべおの人に感謝したす
これを匕き起こしおいる他の理論を調査し続けたす。

@stas私は1぀のこずを

ほずんどの堎合、メ゜ッドは暗黙的に呌び出されないため、ナヌザヌはこれらのメ゜ッドに泚意する必芁がありたす。

PyPyをしばらく脇に眮いおおくず、これらのメ゜ッドを明瀺的に呌び出す必芁はありたせん。 ゜ケットオブゞェクトがCPythonで到達䞍胜になるず、ファむルハンドルを閉じるこずを含む自動gcが取埗されたす。 これは、これらのメ゜ッドを文曞化しないずいう䞻匵ではありたせんが、過床に焊点を圓おないように譊告しおいたす。

CIを䜿甚するこずを意図しおいたすが、珟時点では問題があるようで、

PyPyをしばらく脇に眮いおおくず、これらのメ゜ッドを明瀺的に呌び出す必芁はありたせん。 ゜ケットオブゞェクトがCPythonで到達䞍胜になるず、ファむルハンドルを閉じるこずを含む自動gcが取埗されたす。 これは、これらのメ゜ッドを文曞化しないずいう䞻匵ではありたせんが、過床に焊点を圓おないように譊告しおいたす。

ここでPythonに぀いお説明する郚分を陀いお、私はあなたの蚀うこずに同意したす。 議論を始めたくはありたせんが、_The Zen of Python_を読むず、Pythonの方法は_Explicit_のアプロヌチよりも_Explicit_の方が優れおいたす。 私もこのプロゞェクトの哲孊に粟通しおいないので、これがrequests圓おはたらない堎合は、私の考えを無芖しおください。

機䌚があればい぀でもCIたたはベンチマヌクテストをお手䌝いさせおいただきたす。 珟圚の状況を説明しおいただきありがずうございたす。

だから、機胜的なAPIを䜿っおいるずきに問題の原因を芋぀けたず思いたす。 もしあなたがそうするなら

import requests
r = requests.get('https://httpbin.org/get')
print(r.raw._pool.pool.queue[-1].sock)

゜ケットはただ開いおいるようです。 私が_appears_ず蚀う理由は、それがただ_sock属性を持っおいるからです。

r.raw._pool.queue[-1].close()
print(repr(r.raw._pool.queue[-1].sock))

None印刷されたす。 ぀たり、 urllib3は、すべおのHTTPResponseに、元の接続プヌルを指す属性が含たれおいるずいうこずです。 接続プヌルのキュヌには、閉じられおいない゜ケットを持぀接続がありたす。 機胜APIの問題は、 requests/api.pyようにするず修正されたす。

def request(...):
    """..."""
    s = Session()
    response = s.request(...)
    s.close()
    return s

その堎合、 r.raw._poolは匕き続き接続プヌルになりたすが、 r.raw._pool.poolはNoneたす。

トリッキヌな郚分は、人々がセッションを䜿甚しおいるずきに起こるこずです。 すべおのリク゚ストの埌にセッションにcloseを蚭定するこずは無意味であり、セッションの目的を損ないたす。 実際には、セッションスレッドなしを䜿甚し、セッションを䜿甚しお同じドメむンおよび同じスキヌム、たずえばhttps に100の芁求を行う堎合、メモリリヌクを確認するのははるかに困難です。新しい゜ケットが䜜成されるたで玄30秒埅ちたす。 問題は、すでに芋おきたように、 r.raw._poolは非垞に可倉なオブゞェクトであるずいうこずです。 これは、リク゚ストでプヌルマネヌゞャによっお管理される接続プヌルぞの参照です。 したがっお、゜ケットが眮き換えられるず、スコヌプ内でただ到達可胜なすべおの応答からの゜ケットぞの参照に眮き換えられたす。 私がもっずやらなければならないこずは、接続プヌルを閉じた埌も゜ケットぞの参照がただ保持されおいるかどうかを把握するこずです。 参照を保持しおいるものを芋぀けるこずができれば、_実際の_メモリリヌクが芋぀かるず思いたす。

だから私は持っおいたこずを1぀のアむデアが実際に参照するかを把握するobjgraph䜿甚しおいたSSLSocketを呌び出した埌でrequests.getず私はこれを埗たした

socket

興味深いのは、 SSLSocketぞの参照が7぀あるようですが、objgraphが芋぀けるこずができる埌方参照は2぀だけであるずいうこずです。 参照の1぀はobjgraphに枡されたものであり、もう1぀はこれを生成するスクリプトで䜜成したバむンディングですが、それでも3぀たたは4぀は、どこから来たのかわからない参照ずしお考慮されおいたせん。

これを生成するための私のスクリプトは次のずおりです。

import objgraph
import requests

r = requests.get('https://httpbin.org/get')
s = r.raw._pool.pool.queue[-1].sock
objgraph.show_backrefs(s, filename='socket.png', max_depth=15, refcounts=True)

䜿甚する

import objgraph
import requests

r = requests.get('https://httpbin.org/get')
s = r.raw._pool.pool.queue[-1].sock
objgraph.show_backrefs(s, filename='socket-before.png', max_depth=15,
                       refcounts=True)
r.raw._pool.close()
objgraph.show_backrefs(s, filename='socket-after.png', max_depth=15,
                       refcounts=True)

socket-after.pngはこれを瀺しおいたす

socket-after

したがっお、ssl゜ケットぞの参照を1぀削陀したす。 そうは蚀っおも、 s._sock 、基瀎ずなるsocket.socketは閉じおいたす。

倚数の長期実行ベンチマヌクを実行した埌、次のこずがわかりたした。

  • close()呌び出すず、明瀺的に圹立ちたす。
  • 耇数のリク゚ストを実行しおいるナヌザヌは、 Sessionを䜿甚し、完了埌に適切に閉じる必芁がありたす。 2326をマヌゞしおください
  • PyPyナヌザヌはJITなしの方が優れおいたす たたは、 gc.collect()明瀺的に呌び出す必芁がありたす。

TL; DR; requestsは良さそうです。以䞋に、このスニペットを実行しおいるグラフをいく぀か瀺したす。

import requests
from memory_profiler import profile

<strong i="15">@profile</strong>
def get(session, i):
    return session.get('http://stas.nerd.ro/?{0}'.format(i))

<strong i="16">@profile</strong>
def multi_get(session, count):
    for x in xrange(count):
        resp = get(session, count+1)
        print resp, len(resp.content), x
        resp.close()

<strong i="17">@profile</strong>
def run():
    session = requests.Session()
    print 'Starting...'
    multi_get(session, 3000)
    print("Finished first round...")
    session.close()
    print 'Done.'

if __name__ == '__main__':
    run()

CPython

Line #    Mem usage    Increment   Line Contents
================================================
    15      9.1 MiB      0.0 MiB   <strong i="23">@profile</strong>
    16                             def run():
    17      9.1 MiB      0.0 MiB       session = requests.Session()
    18      9.1 MiB      0.0 MiB       print 'Starting...'
    19      9.7 MiB      0.6 MiB       multi_get(session, 3000)
    20      9.7 MiB      0.0 MiB       print("Finished first round...")
    21      9.7 MiB      0.0 MiB       session.close()
    22      9.7 MiB      0.0 MiB       print 'Done.'

JITなしのPyPy

Line #    Mem usage    Increment   Line Contents
================================================
    15     15.0 MiB      0.0 MiB   <strong i="29">@profile</strong>
    16                             def run():
    17     15.4 MiB      0.5 MiB       session = requests.Session()
    18     15.5 MiB      0.0 MiB       print 'Starting...'
    19     31.0 MiB     15.5 MiB       multi_get(session, 3000)
    20     31.0 MiB      0.0 MiB       print("Finished first round...")
    21     31.0 MiB      0.0 MiB       session.close()
    22     31.0 MiB      0.0 MiB       print 'Done.'

JITを䜿甚したPyPy

Line #    Mem usage    Increment   Line Contents
================================================
    15     22.0 MiB      0.0 MiB   <strong i="35">@profile</strong>
    16                             def run():
    17     22.5 MiB      0.5 MiB       session = requests.Session()
    18     22.5 MiB      0.0 MiB       print 'Starting...'
    19    219.0 MiB    196.5 MiB       multi_get(session, 3000)
    20    219.0 MiB      0.0 MiB       print("Finished first round...")
    21    219.0 MiB      0.0 MiB       session.close()
    22    219.0 MiB      0.0 MiB       print 'Done.'

私たち党員が最初に混乱した理由の1぀は、ベンチマヌクを実行するには、ある実装から別の実装ぞのGCの動䜜を陀倖するために、より倧きなセットが必芁になるためだず思いたす。

たた、スレッド環境でリク゚ストを実行するには、スレッドの動䜜方法により、より倚くの呌び出しセットが必芁になりたす耇数のスレッドプヌルを実行した埌、メモリ䜿甚量に倧きな倉化は芋られたせんでした。

JITを䜿甚したPyPyに関しおは、同じ回数の呌び出しでgc.collect()を呌び出すず、メモリが最倧30節玄されたした。 そのため、JITの結果は、誰もがVMを埮調敎し、JITのコヌドを最適化する方法の䞻題であるため、この説明から陀倖する必芁があるず思いたす。

了解したした。問題は、PyPyJITず盞互䜜甚するメモリの凊理方法にあるように芋えたす。 PyPyの専門家に召喚するのは良い考えかもしれたせん@alex

私は本圓に、このようなこずを匕き起こす可胜性のある芁求および䌚瀟が䜕をしおいるのか想像できたせん。 環境内のPYPYLOG=jit-summary:-しおテストを実行し、結果を貌り付けるこずができたすかプロセスが終了するずいく぀かのものが出力されたす

お圹に立おれば

Line #    Mem usage    Increment   Line Contents
================================================
    15     23.7 MiB      0.0 MiB   <strong i="6">@profile</strong>
    16                             def run():
    17     24.1 MiB      0.4 MiB       session = requests.Session()
    18     24.1 MiB      0.0 MiB       print 'Starting...'
    19    215.1 MiB    191.0 MiB       multi_get(session, 3000)
    20    215.1 MiB      0.0 MiB       print("Finished first round...")
    21    215.1 MiB      0.0 MiB       session.close()
    22    215.1 MiB      0.0 MiB       print 'Done.'


[2cbb7c1bbbb8] {jit-summary
Tracing:        41  0.290082
Backend:        30  0.029096
TOTAL:              1612.933400
ops:                79116
recorded ops:       23091
  calls:            2567
guards:             7081
opt ops:            5530
opt guards:         1400
forcings:           198
abort: trace too long:  2
abort: compiling:   0
abort: vable escape:    9
abort: bad loop:    0
abort: force quasi-immut:   0
nvirtuals:          9318
nvholes:            1113
nvreused:           6666
Total # of loops:   23
Total # of bridges: 8
Freed # of loops:   0
Freed # of bridges: 0
[2cbb7c242e8b] jit-summary}

https://launchpad.net/~pypy/+archive/ubuntu/ppaの最新のPyPyを䜿甚しお信頌できる32ビットを䜿甚しおい

31のコンパむル枈みパスは、䜿甚䞭の200MB以䞊のRAMを説明しおいたせん。

プログラムに䜕かを入れお実行できたすか
非垞に高いメモリにある間gc.dump_rpy_heap('filename.txt')
䜿甚法 䞀床実行する必芁がありたす。これにより、すべおのダンプが生成されたす。
GCが知っおいるメモリ。

次に、PyPy゜ヌスツリヌをチェックアりトしお、 ./pypy/tool/gcdump.py filename.txtを実行し、結果を衚瀺したす。

ありがずう

3時20分52秒PMスタヌスSuşcovの土2014幎11月8日には[email protected]
曞きたした

お圹に立おれば

行番号メモリ䜿甚量増分行の内容

15     23.7 MiB      0.0 MiB   <strong i="20">@profile</strong>
16                             def run():
17     24.1 MiB      0.4 MiB       session = requests.Session()
18     24.1 MiB      0.0 MiB       print 'Starting...'
19    215.1 MiB    191.0 MiB       multi_get(session, 3000)
20    215.1 MiB      0.0 MiB       print("Finished first round...")
21    215.1 MiB      0.0 MiB       session.close()
22    215.1 MiB      0.0 MiB       print 'Done.'

[2cbb7c1bbbb8] {jit-抂芁
トレヌス41 0.290082
バック゚ンド30 0.029096
合蚈1612.933400
ops79116
蚘録された操䜜23091
呌び出し2567
譊備員7081
opt ops5530
オプトガヌド1400
匷制力198
䞭止トレヌスが長すぎたす2
䞭止コンパむル0
䞭止vable゚スケヌプ9
アボヌトバッドルヌプ0
䞭止準免疫を匷制する0
nvirtuals9318
nvholes1113
nvreused6666
ルヌプの総数23
橋の総数8
解攟されたルヌプの数0
解攟された橋の数0
[2cbb7c242e8b] jit-summary}

私は最新のPyPyを䜿甚しお信頌できる32ビットを䜿甚しおいたす
https://launchpad.net/~pypy/+archive/ubuntu/ppa
https://launchpad.net/%7Epypy/+archive/ubuntu/ppa

—
このメヌルに盎接返信するか、GitHubで衚瀺しおください
https://github.com/kennethreitz/requests/issues/1685#issuecomment -62269627
。

ログ

Line #    Mem usage    Increment   Line Contents
================================================
    16     22.0 MiB      0.0 MiB   <strong i="6">@profile</strong>
    17                             def run():
    18     22.5 MiB      0.5 MiB       session = requests.Session()
    19     22.5 MiB      0.0 MiB       print 'Starting...'
    20    217.2 MiB    194.7 MiB       multi_get(session, 3000)
    21    217.2 MiB      0.0 MiB       print("Finished first round...")
    22    217.2 MiB      0.0 MiB       session.close()
    23    217.2 MiB      0.0 MiB       print 'Done.'
    24    221.0 MiB      3.8 MiB       gc.dump_rpy_heap('bench.txt')


[3fd7569b13c5] {jit-summary
Tracing:        41  0.293192
Backend:        30  0.026873
TOTAL:              1615.665337
ops:                79116
recorded ops:       23091
  calls:            2567
guards:             7081
opt ops:            5530
opt guards:         1400
forcings:           198
abort: trace too long:  2
abort: compiling:   0
abort: vable escape:    9
abort: bad loop:    0
abort: force quasi-immut:   0
nvirtuals:          9318
nvholes:            1113
nvreused:           6637
Total # of loops:   23
Total # of bridges: 8
Freed # of loops:   0
Freed # of bridges: 0
[3fd756c29302] jit-summary}

ここにダンプ https 

お時間を割いおいただきありがずうございたす。

したがっお、これはおそらく100MBの䜿甚量を占めたす。 䌑憩する堎所は2぀ありたす
そのうちの1぀は、「スペアメモリ」でGCがさたざたなこずを維持しおいるこずです。
GC以倖の割り圓お-これらはOpenSSLの内郚のようなものを意味したす
割り圓お。 OpenSSL構造かどうかを確認する良い方法があるかどうか疑問に思いたす
リヌクされおいたすが、ここでTLSでテストされおいるものです。はいの堎合、できたすか
非TLSサむトで詊しお、それが再珟されるかどうかを確認したすか

午前5時38分04秒PMスタヌスSuşcovの土2014幎11月8日には[email protected]
曞きたした

ログ

行番号メモリ䜿甚量増分行の内容

16     22.0 MiB      0.0 MiB   <strong i="18">@profile</strong>
17                             def run():
18     22.5 MiB      0.5 MiB       session = requests.Session()
19     22.5 MiB      0.0 MiB       print 'Starting...'
20    217.2 MiB    194.7 MiB       multi_get(session, 3000)
21    217.2 MiB      0.0 MiB       print("Finished first round...")
22    217.2 MiB      0.0 MiB       session.close()
23    217.2 MiB      0.0 MiB       print 'Done.'
24    221.0 MiB      3.8 MiB       gc.dump_rpy_heap('bench.txt')

[3fd7569b13c5] {jit-抂芁
トレヌス41 0.293192
バック゚ンド30 0.026873
合蚈1615.665337
ops79116
蚘録された操䜜23091
呌び出し2567
譊備員7081
opt ops5530
オプトガヌド1400
匷制力198
䞭止トレヌスが長すぎたす2
䞭止コンパむル0
䞭止vable゚スケヌプ9
アボヌトバッドルヌプ0
䞭止準免疫を匷制する0
nvirtuals9318
nvholes1113
nvreused6637
ルヌプの総数23
橋の総数8
解攟されたルヌプの数0
解攟された橋の数0
[3fd756c29302] jit-summary}

ここにダンプ https 

お時間を割いおいただきありがずうございたす。

—
このメヌルに盎接返信するか、GitHubで衚瀺しおください
https://github.com/kennethreitz/requests/issues/1685#issuecomment -62277822
。

@alex 、

@stasは、このベンチマヌクに通垞のhttp非SSL / TLS接続を䜿甚しおいたず@ stasのベンチマヌクスクリプトも䜿甚しお、通垞のhttp接続を䜿甚しおMacOSX 10.9.5 2.5 GHz i5 8 GB 1600 MHz DDR3で実行したした。

それが圹立぀堎合は、これが比范する私の結果ですあなたの指瀺を䜿甚しお
https://gist.github.com/mhjohnson/a13f6403c8c3a3d49b8d

どう考えおいるか教えおください。

ありがずう、

-マット

GitHubの正芏衚珟が緩すぎたす。 完党に修正されたずは思わないので、これを再開したす。

こんにちは、倚分私は問題が存圚するこずを指摘するのを手䌝うこずができたす。 リク゚ストを䜿甚するクロヌラヌず、マルチプロセッシングを䜿甚するプロセスがありたす。 耇数のむンスタンスが同じ結果を受け取っおいるこずが起こっおいたす。 結果のバッファたたは゜ケット自䜓にリヌクがある可胜性がありたす。

コヌドのサンプルを送信できるかどうか、たたは情報のどの郚分が「共有」されおいるリヌクされおいるかを特定するための参照ツリヌを生成する方法を教えおください

ありがずう

@barrocaそれは別の問題です。 スレッド間でセッションを䜿甚し、 stream=Trueを䜿甚しおいる可胜性がありたす。 読み取りが完了する前に応答を閉じおいる堎合、゜ケットはそのデヌタがただ残っおいる状態で接続プヌルに戻されたす私が正しく芚えおいる堎合。 それが起こらない堎合は、最新の接続を取埗し、サヌバヌからキャッシュされた応答を受信しお​​いる可胜性もありたす。 いずれにせよ、これはメモリリヌクを瀺すものではありたせん。

@ sigmavirus24ありがずうIan、あなたが蚀及したように、それはスレッド間でのセッションのいく぀かのミス䜿甚でした。 説明をありがずう、間違った問題を曎新しお申し蚳ありたせん。

心配ありたせん@barroca :)

これ以䞊の苊情はなく、最善を尜くしたず思いたす。 再開しお、必芁に応じお再調査させおいただきたす。

それで、この問題の解決策は䜕ですか

@Makecodeeasy私も知りたい

これたでのずころ、 requestsに関する私の問題は、スレッドセヌフではないずいうこずです。
スレッドごずに別々のセッションを䜿甚するのが最適です。

キャッシュ応答を怜蚌するために䜕癟䞇ものURLをりォヌクスルヌするための進行䞭の䜜業は、私をここに導きたす

requestsがThreadPoolExecutorたたはthreadingず盞互䜜甚するず、メモリ䜿甚量が合理的を超えお増加するこずがわかりたした。
結局、私はmultiprocessing.Processを䜿甚しおワヌカヌを分離し、ワヌカヌごずに独立したセッションを䜜成したす

@AndCycleなら、あなたの問題はここにはありたせん。 この特定のメモリリヌクのケヌスを修正するためにマヌゞされたPRがありたした。 それの呚りにテストがあるので、それは埌退しおいたせん。 そしお、あなたの問題は完党に異なっおいるように聞こえたす。

このペヌゞは圹に立ちたしたか
0 / 5 - 0 評䟡