CPythonデバむス。Yandexレポヌト

ビデオコヌス「Pythonでのバック゚ンド開発」の玹介講矩の芁玄を公開したす。その䞭でYegorOvcharenko゚ゎロフチャレンコYandex.Taxiのチヌムリヌダヌである、CPythonむンタヌプリタヌの内郚構造に぀いお話したした。





-芁するに、どのような蚈画がありたすかたず、Pythonを孊ぶ理由に぀いお説明したす。次に、CPythonむンタヌプリタヌがどのように機胜するか、メモリをどのように管理するか、Pythonの型システムがどのように機胜するか、蟞曞、ゞェネレヌタヌ、および䟋倖を芋おみたしょう。時間くらいかかるず思いたす。





なぜPythonなのか





* Insights.stackoverflow.com/survey/2019**

非垞に䞻芳的な

***研究の

解釈****研究の
解釈



始めたしょう。なぜPythonなのかスラむドは、バック゚ンド開発で珟圚䜿甚されおいるいく぀かの蚀語の比范を瀺しおいたす。しかし、芁するに、Pythonの利点は䜕ですかあなたはすぐにそれにコヌドを曞くこずができたす。もちろん、これは非垞に䞻芳的なものです。クヌルなC ++たたはGoを䜜成する人は、これに぀いお議論するこずができたす。しかし、平均しお、Pythonでの曞き蟌みは高速です。



欠点は䜕ですか最初の、そしおおそらく䞻な欠点は、Pythonが遅いこずです。他の蚀語よりも30倍遅くなる可胜性がありたす。これは、このトピックに関する調査です。しかし、その速床はタスクによっお異なりたす。タスクには2぀のクラスがありたす。



-CPUバりンド、CPUバりンドタスク、CPUバりンド。



--I / Oバむンド、タスクは入出力によっお制限されたすネットワヌク経由たたはデヌタベヌス内。



CPUにバむンドされた問題を解決しおいる堎合は、はい、Pythonは遅くなりたす。 I / Oがバむンドされおいお、これが倧きなクラスのタスクである堎合、実行速床を理解するには、ベンチマヌクを実行する必芁がありたす。そしお、おそらくPythonを他の蚀語ず比范しおも、パフォヌマンスの違いに気付くこずさえありたせん。



さらに、Pythonは動的に型付けされたす。むンタヌプリタヌは、コンパむル時に型をチェックしたせん。バヌゞョン3.5では、タむプヒントが衚瀺され、タむプを静的に指定できたすが、それほど厳密ではありたせん。぀たり、コンパむル段階ではなく、すでに本番環境で゚ラヌが発生する可胜性がありたす。バック゚ンド甚の他の䞀般的な蚀語Java、C、C ++、Goには静的型付けがありたすコヌドで間違ったオブゞェクトを枡すず、コンパむラヌがそのこずを通知したす。



もっず珟実的に蚀えば、Pythonはタクシヌ補品開発でどのように䜿甚されおいたすか私たちはマむクロサヌビスアヌキテクチャに向かっおいたす。私たちはすでに160のマむクロサヌビス、぀たり食料品を持っおいたす-35、Pythonで15、プラスで20。぀たり、珟圚、Pythonのみ、たたはプラスで蚘述しおいたす。



どのように蚀語を遞択したすか1぀目は負荷芁件です。぀たり、Pythonがそれを凊理できるかどうかを確認したす。圌が匕っ匵った堎合、私たちはチヌム開発者の胜力を芋たす。



それでは通蚳に぀いおお話したいず思いたす。CPythonはどのように機胜したすか



通蚳装眮



疑問が生じるかもしれたせんなぜ通蚳がどのように機胜するかを知る必芁があるのですか。質問は有効です。内郚に䜕があるかを知らなくおも、簡単にサヌビスを䜜成できたす。答えは次のずおりです



。1。高負荷の最適化。Pythonサヌビスがあるず想像しおください。それは動䜜し、負荷は䜎いです。しかし、ある日、仕事があなたにやっお来たす-重い負荷に備えおペンを曞くこずです。これから逃れるこずはできたせん。C++でサヌビス党䜓を曞き盎すこずはできたせん。したがっお、高負荷甚にサヌビスを最適化する必芁がありたす。むンタプリタがどのように機胜するかを理解するず、これに圹立ちたす。



2.耇雑なケヌスのデバッグ。サヌビスは実行されおいるが、メモリが「リヌク」し始めたずしたす。 Yandex.Taxiでは、぀い最近そのようなケヌスが発生したした。このサヌビスは1時間ごずに8GBのメモリを消費し、クラッシュしたした。私たちはそれを理解する必芁がありたす。それは蚀語、Pythonに぀いおです。 Pythonでメモリ管理がどのように機胜するかに぀いおの知識が必芁です。



3.これは、耇雑なラむブラリや耇雑なコヌドを䜜成する堎合に圹立ちたす。



4.そしお䞀般的に-ナヌザヌずしおだけでなく、より深いレベルで䜜業しおいるツヌルを知るこずは良い圢であるず考えられおいたす。これはYandexで高く評䟡されおいたす。



5.圌らはむンタビュヌでそれに぀いお質問したすが、それは重芁ではありたせんが、あなたの䞀般的なITの芋通しです。







翻蚳者の皮類を簡単に思い出しおみたしょう。コンパむラヌずむンタヌプリタヌがありたす。ご存知かもしれたせんが、コンパむラは、゜ヌスコヌドを盎接マシンコヌドに倉換するものです。むしろ、むンタプリタは最初にバむトコヌドに倉換し、次にそれを実行したす。 Pythonは解釈された蚀語です。



Bytecodeは、オリゞナルから取埗される䞀皮の䞭間コヌドです。プラットフォヌムに関連付けられおおらず、仮想マシン䞊で実行されたす。なぜ仮想なのかこれは本物の車ではありたせんが、ある皮の抜象化です。







どのような皮類の仮想マシンがありたすか登録しおスタックしたす。しかし、ここでは、これではなく、Pythonがスタックマシンであるずいう事実を芚えおおく必芁がありたす。次に、スタックがどのように機胜するかを確認したす。



そしおもう1぀の泚意点ここではCPythonに぀いおのみ説明したす。 CPythonは、ご想像のずおり、Cで蚘述されたリファレンスPython実装です。同矩語ずしお䜿甚されたす。Pythonに぀いお話すずきは、通垞、CPythonに぀いお話したす。



しかし、他の通蚳もいたす。 JITコンパむルを䜿甚しお玄5倍高速化するPyPyがありたす。めったに䜿甚されたせん。私は正盎に䌚ったこずがありたせん。 JPythonがあり、Java仮想マシンずDotnetマシンのバむトコヌドを倉換するIronPythonがありたす。これは今日の講矩の範囲倖です-正盎なずころ、私はそれに遭遇しおいたせん。それでは、CPythonを芋おみたしょう。







しばらく様子を芋おみたしょう。゜ヌス、行があり、それを実行したい。通蚳は䜕をしたすか文字列は単なる文字の集たりです。それで䜕か意味のあるこずをするために、あなたは最初にコヌドをトヌクンに翻蚳したす。トヌクンは、文字のグルヌプ化されたセット、識別子、数字、たたはある皮の反埩です。実際、むンタプリタはコヌドをトヌクンに倉換したす。







さらに、抜象構文ツリヌASTは、これらのトヌクンから構築されたす。たた、ただ気にしないでください。これらは、操䜜を行うノヌド内の䞀郚のツリヌです。この堎合、バむナリ操䜜であるBinOpがあるずしたしょう。操䜜-指数、オペランド䞊げる数、䞊げる力。



さらに、これらのツリヌを䜿甚しお、すでにいく぀かのコヌドを䜜成できたす。私は倚くのステップを逃したす、最適化ステップ、他のステップがありたす。次に、これらの構文ツリヌがバむトコヌドに倉換されたす。



ここでもっず詳しく芋おみたしょう。 Bytecodeは、その名前が瀺すように、バむトで構成されるコヌドです。たた、Pythonでは、3.6以降、バむトコヌドは2バむトです。







最初のバむトは、opcodeず呌ばれる挔算子自䜓です。 2番目のバむトはoparg匕数です。䞊から芋たようです。぀たり、䞀連のバむトです。しかし、PythonにはDisassemblerのdisずいうモゞュヌルがあり、これを䜿甚するず、より人間が読みやすい衚珟を芋るこずができたす。



それはどのように芋えたすか゜ヌスの行番号がありたす-巊端の番号です。 2番目の列はアドレスです。私が蚀ったように、Python 3.6のバむトコヌドは2バむトを取るので、すべおのアドレスは偶数であり



、0、2、4が衚瀺されたす... Load.name、Load.constはすでにコヌドオプション自䜓です。 Pythonが実行されたす。 0、0、1、1はoparg、぀たりこれらの操䜜の匕数です。次に、それらがどのように実行されるかを芋おみたしょう。



...バむトコヌドがPythonでどのように実行されるか、このためにどのような構造があるかを芋おみたしょう。







Cがわからなくおも倧䞈倫です。脚泚は䞀般的な理解のためのものです。



Pythonには、バむトコヌドの実行に圹立぀2぀の構造がありたす。 1぀目はCodeObjectで、その抂芁を確認できたす。実際、構造はより倧きくなっおいたす。これはコンテキストのないコヌドです。これは、この構造に実際に今芋たバむトコヌドが含たれおいるこずを意味したす。関数に定数ぞの参照、定数の名前などが含たれおいる堎合は、この関数で䜿甚される倉数の名前が含たれたす。







次の構造はFrameObjectです。これはすでに実行コンテキストであり、倉数の倀がすでに含たれおいる構造です。グロヌバル倉数ぞの参照。実行スタックこれに぀いおは埌で説明したす、およびその他の倚くの情報。呜什の実行回数を考えおみたしょう。



䟋ずしお、関数を耇数回呌び出したい堎合は、同じCodeObjectがあり、呌び出しごずに新しいFrameObjectが䜜成されたす。独自の匕数、独自のスタックがありたす。したがっお、それらは盞互接続されおいたす。







メむンのむンタヌプリタヌルヌプずは䜕ですかバむトコヌドはどのように実行されたすかあなたは私たちがopargでこれらのopcodeのリストを持っおいるのを芋たした。これはどのように行われたすか Pythonには、他のむンタヌプリタヌず同様に、このバむトコヌドを実行するルヌプがありたす。぀たり、フレヌムがフレヌムに入り、Pythonはバむトコヌドを順番に調べ、それがどのような皮類のopargであるかを調べ、巚倧なスむッチを䜿甚しおハンドラヌに移動したす。たずえば、ここでは1぀のopcodeのみが瀺されおいたす。たずえば、ここにバむナリ枛算、バむナリ枛算がありたす。たずえば、この堎所で「AB」が実行されるずしたす。



バむナリ枛算がどのように機胜するかを説明したしょう。非垞に単玔で、これは最も単玔なコヌドの1぀です。 TOP関数は、スタックから最䞊䜍の倀を取埗し、最䞊䜍の倀から取埗し、スタックからポップするだけでなく、PyNumber_Subtract関数が呌び出されたす。結果スラッシュSET_TOP関数がスタックにプッシュバックされたす。スタックに぀いお明確でない堎合は、䟋を次に瀺したす。







GILに぀いお簡単に説明したす。 GILは、Pythonのプロセスレベルのミュヌテックスであり、このミュヌテックスをメむンのむンタヌプリタヌルヌプに取り蟌みたす。その埌、バむトコヌドの実行が開始されたす。これは、むンタヌプリタヌの内郚構造を保護するために、䞀床に1぀のスレッドのみがバむトコヌドを実行するように行われたす。



もう少し進んで、Pythonのすべおのオブゞェクトにそれらぞの参照がいく぀かあるずしたしょう。たた、2぀のスレッドがこのリンク数を倉曎するず、むンタヌプリタヌが機胜しなくなりたす。したがっお、GILがありたす。



これに぀いおは、非同期プログラミングに関する講矩で説明したす。これはあなたにずっおどのように重芁ですかマルチスレッドは䜿甚されたせん。耇数のスレッドを䜜成した堎合でも、通垞はそのうちの1぀だけが実行され、バむトコヌドはスレッドの1぀で実行されるためです。したがっお、マルチプロセッシング、sish拡匵、たたは他の䜕かを䜿甚しおください。







簡単な䟋。このフレヌムはPythonから安党に探玢できたす。アンダヌスコア関数get_frameを持぀sysモゞュヌルがありたす。フレヌムを取埗しお、そこにある倉数を確認できたす。指瀺がありたす。これは教えるためのもので、実際には䜿甚したせんでした。



理解するために、Python仮想マシンスタックがどのように機胜するかを芋おみたしょう。䜕をするのか理解できない非垞に単玔なコヌドがいく぀かありたす。







巊偎はコヌドです。珟圚調査䞭の郚分は黄色で匷調衚瀺されおいたす。 2番目の列には、この郚分のバむトコヌドがありたす。 3番目の列には、スタックのあるフレヌムが含たれおいたす。぀たり、各FrameObjectには独自の実行スタックがありたす。



Pythonは䜕をしたすかそれは順番に進み、䞭倮の列のバむトコヌドが実行され、スタックで機胜したす。







LOAD_CONSTず呌ばれる最初のopcodeを実行したした。定数をロヌドしたす。この郚分をスキップし、そこにCodeObjectが䜜成され、定数のどこかにCodeObjectがありたした。 Pythonは、LOAD_CONSTを䜿甚しおスタックにロヌドしたした。これで、このフレヌムのスタックにCodeObjectができたした。先に進むこずができたす。







次に、PythonはopcodeMAKE_FUNCTIONを実行したす。 MAKE_FUNCTIONは明らかに関数を䜜成したす。スタックにCodeObjectがあるこずを前提ずしおいたす。䜕らかのアクションを実行し、関数を䜜成しお、関数をスタックにプッシュしたす。これで、フレヌムスタックにあったCodeObjectの代わりにFUNCTIONができたした。そしお、この関数を参照できるように、to_power倉数に配眮する必芁がありたす。







Opcode STORE_NAMEが実行され、to_power倉数に配眮されたす。スタックに関数がありたしたが、これはto_power倉数であり、参照できたす。



次に、10 +この関数の倀を出力したす。







Pythonは䜕をしたすかこれはバむトコヌドに倉換されたした。私たちが持っおいる最初のopcodeはLOAD_CONSTです。トップ10をスタックにロヌドしたす。スタックに12個登堎したした。次に、to_powerを実行する必芁がありたす。







この機胜は次のように実行されたす。䜍眮匕数がある堎合残りは今のずころ調べたせん、最初のPythonは関数自䜓をスタックに配眮したす。次に、すべおの匕数を入力し、関数匕数の匕数番号を䜿甚しおCALL_FUNCTIONを呌び出したす。







最初の匕数をスタックにロヌドしたした。これは関数です。







さらに2぀の匕数30ず2をスタックにロヌドしたした。これで、スタックに関数ず2぀の匕数ができたした。スタックの䞀番䞊が䞀番䞊にありたす。 CALL_FUNCTIONが私たちを埅っおいたす。 CALL_FUNCTION2ず蚀いたす。぀たり、2぀の匕数を持぀関数がありたす。 CALL_FUNCTIONは、スタック䞊に2぀の匕数があり、その埌に関数が続くこずを想定しおいたす。 2、30、およびFUNCTIONがありたす。



進行䞭のOpcode。







したがっお、私たちにずっおは、そのスタックが離れるず、新しい関数が䜜成され、そこで実行が行われたす。



フレヌムには独自のスタックがありたす。その機胜のために新しいフレヌムが䜜成されたした。それはただ空です。







さらに実行が行われたす。ここではすでに簡単です。 Aをパワヌアップする必芁がありたす。倉数A-30の倀をスタックにロヌドし、倉数power-2の倀をロヌドしたす。







そしおopcodeBINARY_POWERが実行されたす。







ある数倀を別の数倀の环乗にしお、スタックに戻したす。関数スタックで900になりたした。



次のopcodeRETURN_VALUEは、スタックから前のフレヌムに倀を返したす。







これが実行の方法です。関数が完了したした。参照がなく、前の関数のフレヌムに2぀の数字がある堎合、フレヌムはおそらくクリアされたす。







その埌、すべおがほが同じです。远加が発生したす。







...型ずPyObjectに぀いお話したしょう。



タむピング







オブゞェクトは、2぀の䞻芁なフィヌルドがあるsish構造です。最初のフィヌルドはこのオブゞェクトぞの参照の数であり、2番目はオブゞェクトのタむプです。もちろん、オブゞェクトのタむプぞの参照です。



他のオブゞェクトは、PyObjectを囲むこずによっお継承したす。぀たり、float、浮動小数点数、PyFloatObjectがある構造を芋るず、PyObject構造であるHEADがあり、さらに、このfloat自䜓の倀が栌玍されおいるデヌタdouble ob_fvalがありたす。







そしお、これはオブゞェクトのタむプです。 PyObjectで型を確認したした。これは、型を瀺す構造です。実際、これは、このオブゞェクトの動䜜を実装する関数ぞのポむンタヌを含むC構造でもありたす。぀たり、そこには非垞に倧きな構造がありたす。たずえば、このタむプの2぀のオブゞェクトを远加する堎合に呌び出される関数が指定されおいたす。たたは、枛算するか、このオブゞェクトを呌び出すか、䜜成したす。タむプでできるこずはすべお、この構造で指定する必芁がありたす。







たずえば、Pythonのint、integersを芋おみたしょう。たた、非垞に簡略化されたバヌゞョン。私たちは䜕に興味があるでしょうか Intにはtp_nameがありたす。 tp_hashがあるこずがわかりたす。ハッシュintを取埗できたす。 intでhashを呌び出すず、この関数が呌び出されたす。 tp_callれロがあり、定矩されおいたせん。これは、intを呌び出せないこずを意味したす。 tp_str-文字列キャストが定矩されおいたせん。 Pythonには、文字列にキャストできるstr関数がありたす。



スラむドには茉っおいたせんが、intを印刷できるこずはご存知でしょう。なぜここでれロなのですか tp_reprもあるため、Pythonにはstrずreprの2぀の文字列受け枡し関数がありたす。文字列ぞのより詳现なキャスト。それは実際に定矩されおおり、スラむドに茉っおいなかっただけで、実際に文字列に぀ながるず呌び出されたす。



最埌に、tp_newこのオブゞェクトの䜜成時に呌び出される関数が衚瀺されたす。 tp_initはれロです。 intは可倉型ではなく、䞍倉であるこずは誰もが知っおいたす。䜜成埌は、倉曎しお初期化しおも意味がないので、れロになりたす。







たずえば、Boolも芋おみたしょう。ご存知かもしれたせんが、PythonのBoolは実際にはintから継承しおいたす。぀たり、Boolを远加しお、互いに共有するこずができたす。もちろん、これはできたせんが、可胜です。



tp_baseベヌスオブゞェクトぞのポむンタがあるこずがわかりたす。オヌバヌラむドされたのは、tp_base以倖のすべおです。぀たり、独自の名前、独自の衚瀺機胜があり、曞き蟌たれるのは数字ではなく、真たたは停です。数倀ずしおの衚珟では、いく぀かの論理関数がそこでオヌバヌラむドされたす。 Docstringはそれ自身ずその創造物です。他のすべおはintから来おいたす。







リストに぀いお簡単に説明したす。 Pythonでは、リストは動的配列です。動的配列は、次のように機胜する配列です。事前にメモリ領域をある次元で初期化したす。そこに芁玠を远加したす。芁玠の数がこのサむズを超えるずすぐに、䞀定のマヌゞンで、぀たり1぀ではなく、耇数の倀で拡匵するので、適切な点がありたす。



Pythonでは、サむズは0、4、8、16、25のように倧きくなりたす。぀たり、定数に察しお挞近的に挿入を実行できるある皮の匏に埓いたす。そしお、リストに挿入機胜からの抜粋があるこずがわかりたす。぀たり、サむズ倉曎を行っおいたす。サむズ倉曎がない堎合は、゚ラヌをスロヌしお芁玠を割り圓おたす。Pythonでは、これはCで実装された通垞の動的配列



です。...蟞曞に぀いお簡単に説明したしょう。それらはPythonのいたるずころにありたす。



蟞曞



オブゞェクトでは、クラスの構成党䜓が蟞曞に含たれおいるこずは誰もが知っおいたす。倚くのものがそれらに基づいおいたす。ハッシュテヌブル内のPythonの蟞曞。







芁するに、ハッシュテヌブルはどのように機胜したすかいく぀かの鍵がありたすティミヌ、バリヌ、ギド。それらを蟞曞に入れたいので、各キヌをハッシュ関数で実行したす。ハッシュになりたす。このハッシュを䜿甚しおバケットを怜玢したす。バケットは、芁玠の配列内の単なる数倀です。最終的なモゞュロ分割が発生したす。バケットが空の堎合は、目的のアむテムをバケットに入れるだけです。空ではなく、すでにいく぀かの芁玠が存圚する堎合、これは衝突であり、次のバケットを遞択しお、それが空いおいるかどうかを確認したす。そしお、無料のバケットが芋぀かるたで続けたす。



したがっお、远加操䜜を適切な時間内に実行するには、䞀定数のバケットを垞に空けおおく必芁がありたす。そうしないず、この配列のサむズに近づくず、非垞に長い間空きバケットを怜玢し、すべおが遅くなりたす。



したがっお、Pythonでは、配列芁玠の3分の1が垞にフリヌであるこずが経隓的に認められおいたす。それらの数が3分の2を超える堎合、配列は拡匵されたす。芁玠の3分の1が無駄になり、有甚なものが䜕も保存されないため、これは良くありたせん。





スラむドからのリンク



したがっお、バヌゞョン3.6以降、Pythonはそのようなこずを行っおきたした。巊偎には、以前の状態が衚瀺されたす。これらの3぀の芁玠が栌玍されおいるスパヌス配列がありたす。 3.6以降、圌らはそのようなたばらな配列を通垞の配列にするこずを決定したしたが、同時にバケット芁玠のむンデックスを別のむンデックス配列に栌玍したす。



むンデックスの配列を芋るず、最初のバケットにはNoneがあり、2番目のバケットにはこの配列のむンデックス1の芁玠がありたす。



これにより、最初にメモリ䜿甚量を削枛でき、次に無料で箱から出しおすぐに䜿甚できたす。順序付けられた配列。぀たり、通垞のsish appendを䜿甚しお、条件付きでこの配列に芁玠を远加するず、配列は自動的に順序付けられたす。



Pythonが䜿甚するいく぀かの興味深い最適化がありたす。これらのハッシュテヌブルを機胜させるには、芁玠比范操䜜が必芁です。ハッシュテヌブルに芁玠を配眮しおから、芁玠を取埗するずしたす。ハッシュを取埗し、バケットに移動したす。わかりたすバケットがいっぱいです、そこに䜕かがありたす。しかし、これは私たちが必芁ずする芁玠ですか配眮されたずきに衝突が発生し、アむテムが実際に別のバケットに収たった可胜性がありたす。したがっお、キヌを比范する必芁がありたす。キヌが間違っおいる堎合は、衝突解決に䜿甚されるのず同じ次のバケット怜玢メカニズムを䜿甚したす。そしお次に進みたしょう。





スラむドからのリンク



したがっお、キヌ比范機胜が必芁です。䞀般に、オブゞェクトを比范する機胜は非垞に高䟡になる可胜性がありたす。したがっお、このような最適化が䜿甚されたす。たず、アむテムIDを比范したす。ご存知のように、CPythonのIDはメモリ内の䜍眮です。



IDが同じである堎合、それらは同じオブゞェクトであり、もちろん、それらは同じです。次に、Trueを返したす。そうでない堎合は、ハッシュを芋おください。なんらかの方法で再定矩しおいない堎合、ハッシュはかなり高速な操䜜になるはずです。これら2぀のオブゞェクトからハッシュを取埗しお比范したす。それらのハッシュが等しくない堎合、オブゞェクトは間違いなく等しくないため、Falseを返したす。



そしお、ごくたれなケヌスでのみ-ハッシュが等しいが、それが同じオブゞェクトであるかどうかわからない堎合-オブゞェクト自䜓を比范する堎合のみです。



興味深いこずに、反埩䞭にキヌに䜕も挿入するこずはできたせん。これは間違いです。







内郚的には、蟞曞にはversionずいう倉数があり、蟞曞のバヌゞョンを栌玍したす。蟞曞を倉曎するず、バヌゞョンが倉曎され、Pythonはこれを理解しお、゚ラヌをスロヌしたす。







より実甚的な䟋では、蟞曞は䜕に䜿甚できたすかタクシヌには泚文があり、泚文のステヌタスは倉曎される可胜性がありたす。ステヌタスを倉曎するずきは、SMSの送信、泚文の蚘録などの特定のアクションを実行する必芁がありたす。



このロゞックはPythonで蚘述されおいたす。 「泚文状況がそういうものなら、こうする」ずいう圢の巚倧なifを曞かないために、キヌが泚文状況であるずいう口述がありたす。たた、VALUEぞのタプルがあり、このステヌタスに移行するずきに実行する必芁のあるすべおのハンドラヌが含たれおいたす。これは䞀般的な方法であり、実際、スむッチの代わりになりたす。







タむプ別のいく぀かの事柄。䞍倉に぀いおお話したす。これらは䞍倉のデヌタタむプであり、可倉はそれぞれ可倉タむプですディクテヌション、クラス、クラスむンスタンス、シヌト、そしお倚分䜕か他のもの。他のほずんどすべおは文字列であり、数字だけです-それらは䞍倉です。可倉タむプずは䜕ですかたず、コヌドを理解しやすくしたす。぀たり、コヌドで䜕かがタプルであるこずがわかった堎合、それ以䞊倉曎されないこずを理解しおいたすか。これにより、コヌドが読みやすくなりたす。次に䜕が起こるかを理解したす。 tuple dsでは、アむテムを入力できたせん。あなたはこれを理解するでしょう、そしおそれはあなたずあなたのためにコヌドを読むすべおの人々を読むのを助けるでしょう。



したがっお、ルヌルがありたす。䜕かを倉曎しない堎合は、䞍倉の型を䜿甚するこずをお勧めしたす。それはたたより速い仕事に぀ながりたす。 tupleが䜿甚する定数は、pit_tuple、tap_tuple、max、およびCCの2぀です。ポむントは䜕ですかサむズが20たでのすべおのタプルに察しお、特定の割り圓お方法が䜿甚されたす。これにより、この割り圓おが高速になりたす。そしお、それぞれのタむプのそのようなオブゞェクトは、最倧で2,000個たで存圚する可胜性がありたす。これはシヌトよりもはるかに高速なので、タプルを䜿甚するず高速になりたす。



ランタむムチェックもありたす。明らかに、䜕かをオブゞェクトに接続しようずしおいお、それがこの機胜をサポヌトしおいない堎合は、゚ラヌが発生したす。これは、䜕か間違ったこずをしたずいう䜕らかの理解です。 dictのキヌは、存続期間䞭に倉曎されないハッシュを持぀オブゞェクトのみにするこずができたす。䞍倉のオブゞェクトのみがこの定矩を満たしたす。それらだけがdictキヌになるこずができたす。







Cではどのように芋えたすか䟋。巊偎はタプル、右偎は通垞のリストです。もちろん、ここではすべおの違いが芋えるわけではなく、私が芋せたかったものだけが芋えたす。 tp_hashフィヌルドのリストにはNotImplementedがありたす。぀たり、リストにはハッシュがありたせん。タプルには、実際にハッシュを返す関数がいく぀かありたす。これが、ずりわけタプルがdictキヌになるこずができ、listができない理由です。



次に匷調衚瀺されるのは、アむテム割り圓お関数sq_ass_itemです。リストでは、タプルではれロです。぀たり、タプルには圓然䜕も割り圓おるこずができたせん。







もう䞀぀。 Pythonは、芁求するたで䜕もコピヌしたせん。これも芚えおおく必芁がありたす。䜕かをコピヌしたい堎合は、たずえば、copy.deepcopy関数を持぀copyモゞュヌルを䜿甚したす。違いはなんですか copyは、兄匟リストなどのコンテナオブゞェクトの堎合、オブゞェクトをコピヌしたす。このオブゞェクトにあったすべおの参照は、新しいオブゞェクトに挿入されたす。たた、deepcopyは、このコンテナヌ内およびそれ以降のすべおのオブゞェクトを再垰的にコピヌしたす。



たたは、リストをすばやくコピヌする堎合は、単䞀のコロンスラむスを䜿甚できたす。あなたはコピヌを手に入れるでしょう、そのようなショヌトカットは簡単です。



...次に、メモリ管理に぀いお説明したす。



メモリ管理







sysモゞュヌルを芋おみたしょう。メモリを䜿甚しおいるかどうかを確認できる機胜がありたす。むンタヌプリタヌを起動しおメモリ倉曎の統蚈を芋るず、小さなオブゞェクトを含む倚くのオブゞェクトが䜜成されおいるこずがわかりたす。そしお、これらは珟圚䜜成されおいるオブゞェクトのみです。



実際、Pythonは実行時に倚くの小さなオブゞェクトを䜜成したす。たた、暙準のmalloc関数を䜿甚しおそれらを割り圓おるず、メモリが断片化されおいるため、メモリの割り圓おが遅いこずにすぐに気付くでしょう。







これは、独自のメモリマネヌゞャを䜿甚する必芁があるこずを意味したす。芁するに、それはどのように機胜したすか Pythonは、アリヌナず呌ばれるメモリのブロックをそれぞれ256キロバむトず぀割り圓おたす。内郚では、圌は自分自身を4キロバむトのプヌルにスラむスしたす。これは、メモリペヌゞのサむズです。プヌル内には、16〜512バむトのさたざたなサむズのブロックがありたす。



512バむト未満をオブゞェクトに割り圓おようずするず、Pythonは独自の方法でこのオブゞェクトに適したブロックを遞択し、そのオブゞェクトをこのブロックに配眮したす。



オブゞェクトの割り圓おが解陀され、削陀された堎合、このブロックは空きずしおマヌクされたす。ただし、オペレヌティングシステムには枡されないため、次の堎所でこのオブゞェクトを同じブロックに曞き蟌むこずができたす。これにより、メモリ割り圓おが倧幅に高速化されたす。







メモリを解攟したす。以前、PyObject構造を芋たした。圌女はこのrefcnt-参照カりントを持っおいたす。それは非垞に簡単に機胜したす。このオブゞェクトを参照するず、Pythonは参照カりントをむンクリメントしたす。オブゞェクトを取埗するずすぐに、そのオブゞェクトぞの参照が消え、参照カりントの割り圓おが解陀されたす。



黄色で匷調衚瀺されおいるもの。 refcntがれロでない堎合は、そこで䜕かを行っおいたす。 refcntがれロの堎合、すぐにオブゞェクトの割り圓おを解陀したす。ガベヌゞコレクタヌを埅っおいるわけではありたせんが、珟時点でメモリをクリアしおいたす。



delメ゜ッドに出くわした堎合、それは単に倉数のオブゞェクトぞのバむンドを削陀したす。たた、クラスで定矩できる__del__メ゜ッドは、オブゞェクトが実際にメモリから削陀されたずきに呌び出されたす。オブゞェクトに察しおdelを呌び出したすが、それでも参照がある堎合、オブゞェクトはどこにも削陀されたせん。そしお、そのファむナラむザヌ__del__は呌び出されたせん。それらは非垞に䌌おいるず呌ばれおいたすが。



リンクの数を確認する方法に぀いおの短いデモ。 getrefcount関数を持぀お気に入りのsysモゞュヌルがありたす。オブゞェクトぞのリンクの数を確認できたす。







もっずお話ししたす。オブゞェクトが䜜成されたす。リンクの数はそこから取埗されたす。興味深い詳现倉数AはTaxiOrderを指したす。リンクの数を取るず、「2」が印刷されたす。なぜだろうかオブゞェクト参照が1぀ありたす。ただし、getrefcountを呌び出すず、このオブゞェクトは関数内の匕数に察しおバンデヌゞされたす。したがっお、このオブゞェクトぞの参照はすでに2぀ありたす。1぀目は倉数、2぀目は関数匕数です。したがっお、「2」が出力されたす。



残りは些现なこずです。オブゞェクトに別の倉数を割り圓おるず、3が埗られたす。次に、このバむンディングを削陀し、2を取埗したす。次に、このオブゞェクトぞのすべおの参照を削陀し、同時にファむナラむザヌが呌び出され、行が出力されたす。







...CPythonには、構築できない興味深い機胜がもう1぀あり、ドキュメントのどこにも蚘茉されおいないようです。敎数がよく䜿甚されたす。毎回再䜜成するのはもったいないでしょう。したがっお、最も䞀般的に䜿甚される数倀であるPython開発者は、-5から255の範囲を遞択したした。これらはシングルトンです。぀たり、それらは䞀床䜜成され、むンタヌプリタヌのどこかにあり、それらを取埗しようずするず、同じオブゞェクトぞの参照を取埗したす。私たちはAずBを取り、それらを印刷し、それらのアドレスを比范したした。真実になった。そしお、たずえば、このオブゞェクトぞの参照が105ありたす。これは、珟圚、非垞に倚くの参照があるためです。



たずえば1408のように、さらに倧きな数をずるず、これらのオブゞェクトは等しくなく、それぞれ2぀の参照がありたす。実際、1぀。







メモリの割り圓おず解攟に぀いお少し話したした。それでは、ガベヌゞコレクタヌに぀いお話したしょう。それは䜕のためたくさんのリンクがあるようです。誰もオブゞェクトを参照しなくなったら、それを削陀できたす。しかし、埪環リンクを持぀こずができたす。たずえば、オブゞェクトはそれ自䜓を参照できたす。たたは、䟋のように、それぞれがネむバヌを参照する2぀のオブゞェクトが存圚する堎合がありたす。これはサむクルず呌ばれたす。そしお、これらのオブゞェクトが別のオブゞェクトぞの参照を䞎えるこずはできたせん。しかし同時に、たずえば、プログラムの別の郚分からは達成できたせん。アクセスできず、圹に立たないため、削陀する必芁がありたすが、リンクがありたす。これはたさにガベヌゞコレクタヌモゞュヌルの目的です。サむクルを怜出し、これらのオブゞェクトを削陀したす。



圌はどのように働いおいたすかたず、䞖代に぀いお簡単に説明し、次にアルゎリズムに぀いお説明したす。







Pythonでガベヌゞコレクタヌの速床を最適化するために、䞖代別です。぀たり、䞖代を䜿甚しお機胜したす。 3䞖代ありたす。圌らは䜕のために必芁ですかごく最近䜜成されたオブゞェクトは、寿呜の長いオブゞェクトよりも䞍芁である可胜性が高いこずは明らかです。関数の過皋で䜕かを䜜成するずしたす。ほずんどの堎合、関数を終了するずきに必芁ありたせん。䞀時倉数を䜿甚したルヌプでも同じです。これらのオブゞェクトはすべお、長い間存圚しおいたオブゞェクトよりも頻繁にクリヌニングする必芁がありたす。



したがっお、すべおの新しいオブゞェクトはれロ䞖代に配眮されたす。この䞖代は定期的に枅掃されたす。 Pythonには3぀のパラメヌタがありたす。各䞖代には独自のパラメヌタがありたす。それらを取埗し、ガベヌゞコレクタヌをむンポヌトし、get_threshold関数を呌び出しお、これらのしきい倀を取埗できたす。



デフォルトでは700、10、10がありたす。700ずは䜕ですかこれは、オブゞェクトの䜜成数から削陀数を匕いた数です。 700を超えるずすぐに、新䞖代のガベヌゞコレクションが始たりたす。たた、10、10は前䞖代のガベヌゞコレクションの数であり、その埌、珟圚の䞖代でガベヌゞコレクションを開始する必芁がありたす。



぀たり、れロ䞖代を10回クリアするず、第1䞖代からビルドを開始したす。第1䞖代を10回クリヌニングした埌、第2䞖代でビルドを開始したす。したがっお、オブゞェクトは䞖代から䞖代ぞず移動したす。圌らが生き残るならば、圌らは第䞀䞖代に移りたす。圌らが第䞀䞖代のゎミ収集を生き残った堎合、圌らは第二䞖代に移されたす。第二䞖代から、圌らはもはやどこにも移動せず、氞遠にそこにずどたりたす。







ガベヌゞコレクションはPythonでどのように機胜したすか第0䞖代でガベヌゞコレクションを開始するずしたす。いく぀かのオブゞェクトがあり、それらにはサむクルがありたす。巊偎には盞互に参照するオブゞェクトのグルヌプがあり、右偎のグルヌプも盞互に参照しおいたす。重芁な詳现-それらは第1䞖代からも参照されたす。Pythonはどのようにルヌプを怜出したすかたず、オブゞェクトごずに䞀時倉数が䜜成され、このオブゞェクトぞの参照数が曞き蟌たれたす。これはスラむドに反映されおいたす。䞊のオブゞェクトぞのリンクが2぀ありたす。ただし、第1䞖代のオブゞェクトは、倖郚から参照されおいたす。 Pythonはこれを芚えおいたす。次に重芁です䞖代内の各オブゞェクトを調べ、この䞖代内の参照の数だけカりンタヌを削陀、デクリメントしたす。







これが起こったこずです。䞖代内で盞互に参照するだけのオブゞェクトの堎合、この倉数は構造䞊自動的にれロになりたす。倖郚から参照されるオブゞェクトのみが1぀です。



Pythonは次に䜕をしたすか圌は、ここに1぀あるので、これらのオブゞェクトが倖郚から参照されおいるこずを理解しおいたす。たた、このオブゞェクトたたはこのオブゞェクトのいずれかを削陀するこずはできたせん。そうしないず、無効な状況が発生するためです。したがっお、Pythonはこれらのオブゞェクトを第1䞖代に転送し、第0䞖代に残っおいるものはすべお削陀し、クリヌンアップしたす。ガベヌゞコレクタヌに぀いおのすべお。







... 進め。ゞェネレヌタに぀いお簡単に説明したす。



ゞェネレヌタヌ







ここでは、残念ながらゞェネレヌタヌの玹介はありたせんが、ゞェネレヌタヌずは䜕かを説明しおみたしょう。これは、比范的蚀えば、yieldずいう単語を䜿甚しお実行のコンテキストを蚘憶する䞀皮の関数です。この時点で、倀を返し、コンテキストを蚘憶したす。その埌、もう䞀床参照しお、その倀を取埗できたす。



ゞェネレヌタヌで䜕ができたすかあなたはゞェネレヌタヌを生み出すこずができたす、それはあなたに倀を返したす、文脈を芚えおおいおください。ゞェネレヌタヌを返华できたす。この堎合、StopIteration実行がスロヌされ、その䞭に倀この堎合はYが含たれたす。



あたり知られおいない事実いく぀かの倀をゞェネレヌタヌに送信できたす。぀たり、ゞェネレヌタヌでsendメ゜ッドを呌び出すず、Z䟋を参照がゞェネレヌタヌが呌び出すyield匏の倀になりたす。ゞェネレヌタヌを制埡したい堎合は、そこに倀を枡すこずができたす。



そこで䟋倖をスロヌするこずもできたす。同じこずゞェネレヌタオブゞェクトを取埗しおスロヌしたす。あなたはそこに間違いを投げたす。最埌の歩留たりの代わりに゚ラヌが発生したす。そしお閉じる-ゞェネレヌタを閉じるこずができたす。次に、GeneratorExitの実行が発生し、ゞェネレヌタは他に䜕も生成しないこずが期埅されたす。







ここでは、CPythonでどのように機胜するかに぀いおお話ししたいず思いたす。実際には、ゞェネレヌタヌに実行フレヌムがありたす。そしお芚えおいるように、FrameObjectにはすべおのコンテキストが含たれおいたす。このこずから、コンテキストがどのように保持されるかは明らかです。぀たり、ゞェネレヌタヌにフレヌムがあるだけです。







ゞェネレヌタヌ関数を実行するずき、Pythonはどのようにしおそれを実行する必芁がないこずを認識したすが、ゞェネレヌタヌを䜜成したすか調べたCodeObjectにはフラグがありたす。たた、関数を呌び出すず、Pythonはそのフラグをチェックしたす。 CO_GENERATORフラグが存圚する堎合、関数を実行する必芁はなく、ゞェネレヌタヌを䜜成するだけでよいこずを理解したす。そしお圌はそれを䜜成したす。 PyGen_NewWithQualName関数。







実行はどうですかGENERATOR_FUNCTIONから、ゞェネレヌタヌは最初にGENERATOR_Objectを呌び出したす。次に、nextを䜿甚しおGENERATOR_Objectを呌び出し、次の倀を取埗できたす。次の呌び出しはどのように行われたすかそのフレヌムはゞェネレヌタヌから取埗され、倉数Fに栌玍され、EvalFrameExむンタヌプリタヌのメむンルヌプに送信されたす。通垞の機胜ず同様に実行されたす。YIELD_VALUEマップコヌドは、ゞェネレヌタヌの実行を返し、䞀時停止するために䜿甚されたす。フレヌム内のすべおのコンテキストを蚘憶し、実行を停止したす。これは最埌から2番目のトピックでした。



...䟋倖ずは䜕か、およびそれらがPythonでどのように䜿甚されるかを簡単に芁玄したす。



䟋倖







䟋倖は、゚ラヌ状況を凊理する方法です。 tryブロックがありたす。䟋倖をスロヌする可胜性のあるものを詊しおみるこずができたす。単語raiseを䜿甚しお゚ラヌを発生させるこずができるずしたしょう。ただし、特定の皮類の䟋倖この堎合はSomeErrorをキャッチできたす。ただし、匏なしですべおの䟋倖をキャッチしたす。 elseブロックはあたり䜿甚されたせんが、存圚し、䟋倖がスロヌされなかった堎合にのみ実行されたす。ずにかくfinallyブロックが実行されたす。



CPythonで䟋倖はどのように機胜したすか実行スタックに加えお、各フレヌムにはブロックのスタックもありたす。䟋を䜿甚するこずをお勧めしたす。











ブロックスタックは、ブロックが曞き蟌たれるスタックです。各ブロックには、タむプ、ハンドラヌ、ハンドラヌがありたす。ハンドラヌは、このブロックを凊理するためにゞャンプするバむトコヌドアドレスです。それはどのように機胜したすかコヌドがあるずしたしょう。 tryブロックを䜜成し、RuntimeError䟋倖をキャッチするexceptブロックず、finallyブロックを䜜成したした。



これはすべおこのバむトコヌドに瞮退したす。 tryブロックのバむトコヌドの最初に、40ず12の匕数を持぀2぀の2぀のopcodeSETUP_FINALLYがありたす。これらはハンドラヌのアドレスです。 SETUP_FINALLYが実行されるず、ブロックがブロックスタックに配眮されたす。぀たり、私を凊理するには、䞀方の堎合は40番目のアドレスに、もう䞀方の堎合は12番目のアドレスに移動したす。



スタックの䞋の12は、elseRuntimeErrorを含む行を陀いおです。これは、䟋倖がある堎合、SETUP_FINALLYタむプのブロックを怜玢するためにブロックスタックを調べるこずを意味したす。アドレス12ぞの遷移があるブロックを芋぀けお、そこに移動したす。そしお、䟋倖ずタむプの比范がありたす。䟋倖のタむプがRuntimeErrorであるかどうかを確認したす。等しい堎合は実行し、等しくない堎合は別の堎所にゞャンプしたす。



FINALLYは、ブロックスタックの次のブロックです。他に䟋倖がある堎合は、実行されたす。次に、このブロックスタックで怜玢が続行され、次のSETUP_FINALLYブロックに移動したす。たずえば、アドレス40を通知するハンドラヌがありたす。アドレス40にゞャンプしたす。コヌドから、これがfinallyブロックであるこずがわかりたす。







CPythonでは非垞に簡単に機胜したす。䟋倖を発生させるこずができるすべおの関数が倀コヌドを返したす。すべお問題がなければ0が返され、゚ラヌの堎合は関数の皮類に応じお-1たたはNULLが返されたす。



Cでそのような挿入図を取りたす。分割がどのように発生するかを確認したす。たた、Bがれロに等しく、れロで陀算したくない堎合は、䟋倖を蚘憶しおNULLを返すずいうチェックがありたす。そのため、゚ラヌが発生したした。したがっお、呌び出しスタックの䞊䜍にある他のすべおの関数もNULLをスロヌする必芁がありたす。これはむンタヌプリタヌのメむンルヌプで確認し、ここにゞャンプしたす。







これはスタックの巻き戻しです。すべおが私が蚀ったずおりです。ブロックスタック党䜓を調べお、そのタむプがSETUP_FINALLYであるこずを確認したす。もしそうなら、非垞に簡単なハンドラヌを飛び越えたす。実際、これがすべおです。



リンク



䞀般的な通蚳

docs.python.org/3/reference/executionmodel.html

github.com/python/cpython

leanpub.com/insidethepythonvirtualmachine/read



メモリ管理

arctrix.com/nas/python/gc

rushter.com/blog/python -memory-managment

instagram-engineering.com/dismissing-python-garbage-collection-at-instagram-4dca40b29172

stackify.com/python-garbage-collection



䟋倖

bugs.python.org/issue17611



All Articles