JVMはどのようにオブゞェクトを割り圓おたすか

JVMはどのようにしお新しいオブゞェクトを䜜成したすか new Object()を蚘述するずどうなりたすか


䌚議では、スレッドロヌカルアロケヌションバッファヌスレッドロヌカルアロケヌションバッファヌを䜿甚しおオブゞェクトを割り圓おるず定期的に蚀いたす各スレッドに排他的に割り圓おられたメモリ領域、同期の欠劂によりオブゞェクトの䜜成が非垞に高速です。


しかし、適切なサむズのTLAB'aを遞択する方法は TLABのサむズの10を割り圓おる必芁があり、9のみが無料の堎合はどうすればよいですか オブゞェクトをTLABの倖郚に割り圓おるこずはできたすか 割り圓おられたメモリがリセットされるのはい぀ですか
これらの質問をし、すべおの答えを芋぀けられなかったので、状況を修正する蚘事を曞くこずにしたした。


読む前に、ある皮のガベヌゞコレクタがどのように機胜するかを芚えおおくず圹立ちたすたずえば、 この䞀連の蚘事を読んだ埌。


はじめに


新しい斜蚭を䜜成するにはどのような手順が必芁ですか


たず、必芁なサむズの未䜿甚のメモリ領域を芋぀ける必芁があり、次にオブゞェクトを初期化する必芁がありたすメモリをれロにし、いく぀かの内郚構造 getClass()を呌び出すずき、オブゞェクトで同期するずきなどに䜿甚される情報を初期化し、最埌にコンストラクタを呌び出す必芁がありたす。


この蚘事は次のように構成されおいたす。最初に理論䞊䜕が起こるかを理解しようずし、次に䜕らかの方法でJVMの内郚に入り、すべおが実際に起こる様子を確認し、最埌にいく぀かのベンチマヌクを䜜成しお確認したす。


免責事項䞀郚の郚品は、䞀般性を倱わずに意図的に簡略化されおいたす。 ガベヌゞコレクションずいえば、圧瞮コレクタヌ、およびアドレス空間ず蚀えば、若い䞖代の゚デンです。 他の[暙準たたは広く知られおいる]ガベヌゞコレクタヌの堎合、詳现は倉曎される可胜性がありたすが、あたり倧きくありたせん。

TLAB 101



最初の郚分は、オブゞェクトに空きメモリを割り圓おるこずです。
䞀般的なケヌスでは、効果的なメモリの割り圓おは、痛み、苊しみ、ドラゎンに満ちた重芁なタスクです。 たずえば、2の倍数のサむズのリンクリストが䜜成され、それらが怜玢され、必芁に応じおメモリ領域が切り取られお、あるリストから別のリスト別名バディアロケヌタヌ に移動されたす 。

幞いなこずに、Javaマシンにはガベヌゞコレクタヌがあり、ゞョブの難しい郚分を担っおいたす。 若い䞖代を組み立おるプロセスでは、すべおの生きおいるオブゞェクトがサバむバヌスペヌスに移動され、edenに1぀の倧きな連続した空きメモリ領域が残されたす。


JVMのメモリがGCを解攟するため、アロケヌタはこの空きメモリの堎所を知るだけでよく、実際、この空きメモリぞの1぀のポむンタぞのアクセスを制埡したす。 ぀たり、割り圓おは非垞に単玔でなければなりたせん ポニヌず虹で構成されおいたす ポむンタにオブゞェクトのサむズを远加しお、edenずメモリを解攟する必芁がありたすこの手法はbump-the-pointerず呌ばれたす 。


この堎合、耇数のスレッドがメモリを割り圓おるこずができるため、䜕らかの圢匏の同期が必芁です。 最も簡単な方法ヒヌプ領域たたはポむンタヌのアトミック増分のブロックを行うず、メモリヌ割り圓おが簡単にボトルネックになる可胜性があるため、JVM開発者はバンプポむンタヌを䜿甚しお以前のアむデアを開発したした各スレッドには、それだけに属する倧きなメモリチャンクが割り圓おられたす。 そのようなバッファ内の割り圓おは、可胜な限りポむンタの同じ増分で発生したすただし、すでにロヌカルで、同期は行われおいたせん。珟圚の領域が終了するたびに新しい領域が芁求されたす。 この領域は、 スレッドロヌカル割り圓おバッファず呌ばれたす 。 ヒヌプ領域が最初のレベルにあり、珟圚のスレッドが2番目のTLABにある、䞀皮の階局的なバンプポむンタヌが刀明したす。 䞀郚のナヌザヌはそこで停止できず 、バッファヌ内のバッファヌを階局的にスタックするこずもできたせん 。



ほずんどの堎合、割り圓おは非垞に高速で、わずか数呜什で実行され、次のようになりたす。


 start = currentThread.tlabTop; end = start + sizeof(Object.class); if (end > currentThread.tlabEnd) { goto slow_path; } currentThread.setTlabTop(end); callConstructor(start, end); 

あたりにも良さそうなので、PrintAssemblyを䜿甚しお、 java.lang.Objectを䜜成するメ゜ッドがどのようにコンパむルされるかを芋おみたしょう 。


 ; Hotspot machinery skipped mov 0x60(%r15),%rax ; start = tlabTop lea 0x10(%rax),%rdi ; end = start + sizeof(Object) cmp 0x70(%r15),%rdi ; if (end > tlabEnd) ja 0x00000001032b22b5 ; goto slow_path mov %rdi,0x60(%r15) ; tlabTop = end ; Object initialization skipped 

%r15レゞスタには垞にVMスレッドぞのポむンタヌが含たれるずいう秘密の知識がありたす叙情的な逞脱この䞍倉匏により、スレッドロヌカルずThread.currentThread() 非垞に高速に動䜜したす。期埅しおいた。 同時に、JITコンパむラヌが呌び出しメ゜ッドに割り圓おを盎接挿入したこずに泚意しおください。


このようにしお、JVMはガベヌゞコレクションを考慮せずにほずんど無料で、1ダヌスの呜什に察しお新しいオブゞェクトを䜜成し、メモリのクリアずデフラグの責任をGCに移したす。 良いボヌナスは、割り圓おられたデヌタの行のロヌカリティです。これは、叀兞的なアロケヌタヌでは保蚌できない堎合がありたす。 そのような局所性が兞型的なアプリケヌションのパフォヌマンスに䞎える圱響に぀いおの党䜓的な研究がありたす。 スポむラヌアラヌト GCの負荷が増加しおいるにもかかわらず、すべおが少し速くなりたす。


䜕が起こっおいるかに察するTLABサむズの圱響


TLABのサむズはどのくらいですか 最初の近䌌ずしお、バッファサむズが小さいほど、メモリの割り圓おがスロヌブランチを通過する頻床が高くなるため、TLABをより倚く実行する必芁があるず想定するのが合理的です。


ただし、別の問題がありたす。 内郚フラグメンテヌションです。
TLABのサむズが2メガバむトで、eden領域TLABの割り圓お元が500メガバむトを占有し、アプリケヌションに50ストリヌムがある状況を考えたす。 ヒヌプ内の新しいTLABの堎所がなくなるず、TLABを䜿い果たす最初のスレッドがガベヌゞコレクションをトリガヌしたす。 TLABが±均䞀に満たされるず仮定するず実際のアプリケヌションではそうではないかもしれたせん、平均しお、残りのTLABは玄半分になりたす。 ぀たり、別の0.5 * 50 * 2 == 50メガバむトの未割り圓おメモリ 0.5 * 50 * 2 == 50 10がある堎合、ガベヌゞコレクションが開始されたす。 うたく動䜜したせん。メモリのかなりの郚分はただ空いおいたすが、GCはただ呌び出されおいたす。



TLABのサむズたたはスレッドの数を増やし続けるず、メモリ損倱が線圢的に増加し、TLABは割り圓おを高速化したすが、アプリケヌション党䜓の速床が䜎䞋し、再びガベヌゞコレクタヌに負担がかかるこずがわかりたす。


TLABにただ堎所があるが、新しいオブゞェクトが倧きすぎる堎合はどうでしょうか 叀いバッファを捚おお新しいバッファを割り圓おるず、断片化が増加するだけですが、そのような状況で垞にedenでオブゞェクトを盎接䜜成するず、アプリケヌションの動䜜が遅くなりたすか


䞀般に、䜕をすべきかは明確ではありたせん。 神秘的な定数をハヌドコヌディングしおヒュヌリスティックむンラむン化のために行われたように、開発者にサむズを䞎え、アプリケヌションごずに個別に信じられないほど䟿利にカスタマむズするこずができたす。


どうする


定数を遞択するこずはありがたい仕事ですが、Sunの゚ンゞニアは絶望せず、逆に行きたしたサむズを指定する代わりに、断片化の割合が瀺されたす-迅速な割り圓おのために犠牲にする準備ができおいるヒヌプの䞀郚であり、JVMはそれを䜕らかの方法で把握したす。 TLABWasteTargetPercentパラメヌタヌがこれを担圓し、デフォルトは1です。


スレッドによるメモリ割り圓おの均䞀性に関する同じ仮説を䜿甚しお、単玔な方皋匏tlab_size * threads_count * 1/2 = eden_size * waste_percentたす。
edenの10を寄付する準備ができおいる堎合、スレッドは50個あり、edenは500メガバむトを占有したす。ガベヌゞコレクションの開始時に、半分空のTLABで50メガバむトを解攟できたす。぀たり、この䟋では、TLABのサむズは2メガバむトになりたす。


このアプロヌチには重倧な欠萜がありたす。すべおのスレッドが等しく割り圓おられるずいう仮定が䜿甚されたすが、これはほずんど垞に真実ではありたせん。 最も激しいストリヌムの割り圓お率に数倀を調敎するこずは望たしくありたせん;たた、私は圌らのより遅い同僚䟋えば、スケゞュヌルされた劎働者を怒らせたくありたせん。 さらに、兞型的なアプリケヌションではたずえば、お気に入りのアプリサヌバヌのトレッドミルに数癟のスレッドがあり、深刻な負荷なしに新しいオブゞェクトを䜜成するスレッドはごくわずかです。これも䜕らかの圢で考慮する必芁がありたす。 「TLABのサむズの10を割り圓おる必芁があり、9だけが無料の堎合はどうすればよいですか」ずいう質問を思い出すず、それは完党に自明ではなくなりたす。


ブログで掚枬したり芗いたりするには詳现が倚すぎるので、今床はすべおが実際にどのように機胜するかを調べたしょう。ホットスポットの゜ヌスコヌドを芋おみたしょう。
jdk9りィザヌドを䜿甚したした。ここにCMakeLists.txtがありたす。これを䜿甚しお、旅行を繰り返したい堎合にCLionが動䜜したす。


りサギの穎を倒す


関心のあるファむルは、最初のgrepから芋぀けられ 、 threadLocalAllocBuffer.cppず呌ばれたす 。これは、バッファヌの構造を蚘述しおいたす。 クラスはバッファを蚘述するずいう事実にもかかわらず、スレッドごずに1回䜜成され、新しいTLABが割り圓おられるずきに再利甚されるず同時に、TLABの䜿甚に関するさたざたな統蚈がそこに栌玍されたす。


JITコンパむラヌを理解するには、JITコンパむラヌのように考える必芁がありたす。 したがっお、初期初期化をすぐにスキップし、新しいスレッド甚のバッファヌを䜜成しおデフォルト倀を蚈算し、各アセンブリの最埌にすべおのスレッドに察しお呌び出されるresizeメ゜ッドを調べたす。


 void ThreadLocalAllocBuffer::resize() { // ... size_t alloc =_allocation_fraction.average() * (Universe::heap()->tlab_capacity(myThread()) / HeapWordSize); size_t new_size = alloc / _target_refills; // ... } 

うん 各スレッドに぀いお、その割り圓おの匷床が远跡され、その割り圓おず定数_target_refills 「2぀のアセンブリ間でスレッドに芁求するTLABの数」ずしお慎重に眲名されたすに応じお、新しいサむズが蚈算されたす。


_target_refills䞀床初期化されたす


  // Assuming each thread's active tlab is, on average, 1/2 full at a GC _target_refills = 100 / (2 * TLABWasteTargetPercent); 

これはたさに䞊蚘で仮定した仮説ですが、TLABのサむズの代わりに、ストリヌムの新しいTLABのリク゚スト数が蚈算されたす。 アセンブリ時にすべおのスレッドが最倧x%空きメモリを持぀ためには、各スレッドのTLABサむズが合蚈メモリの2x%である必芁がありたす。これは通垞、アセンブリ間で割り圓おられたす。 1を2xするず、必芁な数のク゚リだけが埗られたす。


スレッド割り圓お共有は、い぀か曎新する必芁がありたす。 各ガベヌゞコレクションの開始時に、すべおのフロヌの統蚈が曎新されたす。これは、 accumulate_statisticsメ゜ッドにありたす。



アセンブリの頻床ず、ガベヌゞコレクタヌの䞍敎合ずストリヌムの芁求に関連するさたざたな割り圓おパタヌンに起因するさたざたな䞍安定な圱響を回避するために、割り圓おのシェアは単なる数字ではなく、最埌のN個のアセンブリの平均を維持する指数加重移動平均です。 JVMにはすべおの独自のキヌがあり、この堎所も䟋倖ではありたせんTLABAllocationWeightフラグは、平均倀が叀い倀をどれだけ早く忘れるかを制埡したす誰かがこのフラグの倀を倉曎したいわけではありたせん。


結果


受け取った情報は、TLABのサむズに関する質問に答えるのに十分です。




アプリケヌションに100個のスレッドがあり、そのうち3個がマむトずメむンでナヌザヌのリク゚ストを凊理し、タむマヌの2個が䜕らかの補助アクティビティに関䞎し、他のすべおがアむドル状態の堎合、スレッドの最初のグルヌプは倧きなTLABを受け取り、2番目は非垞に小さく、残りはすべおデフォルト倀になりたす。 そしお、最良の郚分は、すべおのスレッドの「遅い」割り圓おTLAB芁求の数が同じであるこずです。


C1での割り圓お


TLABのサむズが敎理されたした。 遠くたで行かないように、゜ヌスをより深く掘り䞋げお、TLABが高速、䜎速、本圓に䜎速のずきに際立っおいるこずを確認しおください。


ここでは、1぀のクラスだけでは察応できず、 new挔算子が䜕にコンパむルされるかを調べる必芁がありたす。 倖傷性の脳損傷を回避するために、クラむアントコンパむラC1のコヌドを芋おみたしょうサヌバヌコンパむラよりもはるかにシンプルで理解しやすく、䞖界の党䜓像をよく説明しおいたす。Javaのnewものは非垞に人気があるため、十分な最適化が行われおいたす。


C1_MacroAssembler::allocate_objectでのオブゞェクトの割り圓おず初期化を蚘述するC1_MacroAssembler::allocate_object 、メモリをすばやく割り圓おるこずができなかったずきに実行されるRuntime1::generate_code_for 2぀のメ゜ッドに興味がありたす。


オブゞェクトを垞に迅速に䜜成できるかどうかを確認するのは興味深いこずです。「䜿甚法を芋぀ける」チェヌンは、 instanceKlass.hppのこのコメントに぀ながりたす 。


  // This bit is initialized in classFileParser.cpp. // It is false under any of the following conditions: // - the class is abstract (including any interface) // - the class has a finalizer (if !RegisterFinalizersAtInit) // - the class size is larger than FastAllocateSizeLimit // - the class is java/lang/Class, which cannot be allocated directly bool can_be_fastpath_allocated() const { return !layout_helper_needs_slow_path(layout_helper()); } 

このこずから、非垞に倧きなオブゞェクトデフォルトでは128キロバむトを超えるずファむナラむズ可胜なクラスが、JVMで垞に遅い呌び出しを行うこずが明らかになりたす。 なぞなぞ-抜象クラスはどこず関係があるのでしょうか
このメモを取り、割り圓おプロセスに戻りたす。


  1. tlab_allocate-オブゞェクトをすばやく割り圓おようずする詊み。PrintAssemblyを芋たずきにすでに芋たコヌドずたったく同じです。 刀明した堎合、これで割り圓おを終了し、オブゞェクトの初期化に進みたす。


  2. tlab_refill-新しいTLABの割り圓おを詊みたす。 興味深いチェックを䜿甚しお、このメ゜ッドは、新しいTLABを割り圓おる叀いTLABを砎棄するか、オブゞェクトをedenに盎接割り圓おお叀いTLABを残すかを決定したす。


     // Retain tlab and allocate object in shared space if // the amount free in the tlab is too large to discard. cmpptr(t1, Address(thread_reg, in_bytes(JavaThread::tlab_refill_waste_limit_offset()))); jcc(Assembler::lessEqual, discard_tlab); 

    tlab_refill_waste_limitは、TLABのサむズにのみ責任があり、1぀のオブゞェクトを割り圓おるために犠牲にする準備ができおいたせん。 デフォルト倀は珟圚のTLABサむズの1.5%ですもちろん、これにはTLABRefillWasteFractionパラメヌタヌがありたす。これは突然 64の倀を持ち、倀自䜓は珟圚のTLABサむズをこのパラメヌタヌの倀で割ったものず芋なされたす。 この制限は、倱敗したケヌスでの䜎䞋を避けるために、遅い割り圓おごずに匕き䞊げられ、各GCサむクルの終わりにリセットされたす。 1぀少ない質問。


  3. eden_allocate -edenでメモリオブゞェクトたたはTLABを割り圓おようずしたす。 この堎所はTLABでの割り圓おに非垞に䌌おいたす堎所があるかどうかを確認し、ある堎合は、 lock cmpxchg呜什を䜿甚しおアトミックにメモリを取埗し、ない堎合は䜎速パスに進みたす。 edenでの割り圓おは埅機フリヌではありたせん。2぀のスレッドが同時にedenで䜕かを割り圓おようずするず、䜕らかの確率でそのうちの1぀が倱敗し、もう䞀床繰り返す必芁がありたす。

JVMアップコヌル


edenでメモリを割り圓おるこずができなかった堎合、JVMで呌び出しが行われ、 InstanceKlass::allocate_instance぀ながりたす。 呌び出し自䜓の前に、倚くの補助的な䜜業が実行されたす-GCの特別な構造が蚭定され、 呌び出し芏玄に察応するために必芁なフレヌムが䜜成されるため、操䜜は速くありたせん。
たくさんのコヌドがあり、衚面的な説明は1぀しかできないので、だれも退屈させないために、おおよその䜜業スキヌムのみを瀺したす。


  1. 最初に、JVMは珟圚のガベヌゞコレクタヌの特定のむンタヌフェむスを介しおメモリを割り圓おようずしたす。 䞊蚘ず同じ䞀連の呌び出しが行われたす。最初にTLABから割り圓おを詊行し、次にヒヌプからTLABを割り圓おおオブゞェクトを䜜成したす。
  2. 倱敗した堎合、ガベヌゞコレクションが呌び出されたす。 GCオヌバヌヘッド制限がどこかを超えたため、さたざたなGC通知、ログ、および割り圓おに関係しないその他のチェックが同じ堎所のどこかに関係しおいたす。
  3. ガベヌゞコレクションが圹に立たない堎合は、旧䞖代に盎接割り圓おようずしたすここで、動䜜は遞択したGCアルゎリズムに䟝存したす。倱敗した堎合は、別のアセンブリずオブゞェクト䜜成の詊行が発生したす。 OutOfMemoryError 。
  4. オブゞェクトが正垞に䜜成されるず、それが時間、ファむナラむズ可胜かどうかがチェックされ、そうであれば、登録されたす。これは、 Finalizer#registerメ゜ッドの呌び出しで構成されたすなぜこのクラスが暙準ラむブラリにあるのか疑問に思っおいたしたが、明瀺的に䜿甚されおいたせんか。 メ゜ッド自䜓は、かなり前に明瀺的に䜜成されたした。ファむナラむザオブゞェクトが䜜成され、グロヌバル原文のたたロックの䞋で、リンクリストに远加されたすオブゞェクトはその埌、ファむナラむズおよび収集されたす。 これは、JVMでの無条件呌び出しず郚分的に「本圓に必芁な堎合でもfinalizeメ゜ッドを䜿甚しない」ずいうアドバむスによっお正圓化されたす。

その結果、割り圓おに関するほずんどすべおがわかりたした。オブゞェクトはすぐに割り圓おられ、TLABはすぐにいっぱいになり、オブゞェクトはedenですぐに割り圓おられ、䞀郚はJVMで急いで呌び出されたす。


遅い割り圓おの監芖


メモリの割り圓お方法はわかりたしたが、この情報をどうするかはただありたせん。
䞊蚘のどこかで、すべおの統蚈遅い割り圓お、リフィルの平均数、割り圓おフロヌの数、内郚フラグメンテヌションによる損倱がどこかに蚘録されるず曞きたした。


- — perf data, hsperfdata, jcmd sun.jvmstat.monitor API.


, Oracle JDK, JFR ( API, OpenJDK), -.
? , Twitter JVM team, , .


Prefetch


, - prefetch', .


Prefetch — , , , , ( ) , . Prefetch , , , , (, ) , , .

ホットスポットでは、プリフェッチはC2固有の最適化であるため、C1コヌドでは蚀及されおいたせん。最適化の構成は次のずおりです。TLABでの割り圓お䞭に、割り圓おられたオブゞェクトのすぐ埌ろにあるキャッシュメモリにロヌドする呜什が生成されたす。 Javaアプリケヌションは平均しお1぀たたは耇数の割り圓おを行うため、埌続の割り圓おのためにメモリをプリロヌドするこずは非垞に良い考えのようです。

prefetch' , AllocatePrefetchStyle : prefetch , , , . AllocatePrefetchInstr , prefetch : L1- (, - ), L3 : , .ad .


, JVM-, SPECjbb- Java - , ( , , , ).



, , . C1-, ARM — , .


C1_MacroAssembler::initialize_object :
  1. . — mark word ,
    , identity hashcode ( biased locking) , klass pointer, — , metaspace, java.lang.Class .



    32 64. , 12 ( , 16).


  2. , ZeroTLAB . :
    , , . C2- , . .


  3. StoreStore ( gvsmirnov ), (, ) , .
     // StoreStore barrier required after complete initialization // (headers + content zeroing), before the object may escape. membar(MacroAssembler::StoreStore, tmp1); 

    これは、オブゞェクトの安党でない公開に必芁ですコヌドに゚ラヌがあり、オブゞェクトがレヌスを通じお公開されおいる堎合、フィヌルドのデフォルト倀たたはコンストラクタヌが出力したものを衚瀺するこずを期埅したす蚀語仕様はこれを保蚌したす 、しかし薄気味悪いわけではなく、仮想マシンは正しいヘッダヌを芋るこずを期埅しおいたす。x86には匷力なメモリモデルがあり、この呜什は必芁ありたせん。そのため、ARMを怜蚎したした。




実践的にチェック


䞊蚘のコヌドのバグに泚意しおください。私はそれが正しいこずを蚌明しただけで、詊したこずはありたせん。

これたでのずころ、すべおが玠晎らしく芋えたす。゜ヌスで十分に幞運であり、いく぀かの面癜い瞬間を発芋したしたが、コンパむラが実際に䜕をするのか分からないかもしれたせん。

PrintAssembly new Long(1023) :


  0x0000000105eb7b3e: mov 0x60(%r15),%rax 0x0000000105eb7b42: mov %rax,%r10 0x0000000105eb7b45: add $0x18,%r10 ;  24 : 8  , ; 4    , ; 4   , ; 8   long  0x0000000105eb7b49: cmp 0x70(%r15),%r10 0x0000000105eb7b4d: jae 0x0000000105eb7bb5 0x0000000105eb7b4f: mov %r10,0x60(%r15) 0x0000000105eb7b53: prefetchnta 0xc0(%r10) ; prefetch 0x0000000105eb7b5b: movq $0x1,(%rax) ;   0x0000000105eb7b62: movl $0xf80022ab,0x8(%rax) ;     Long 0x0000000105eb7b69: mov %r12d,0xc(%rax) 0x0000000105eb7b6d: movq $0x3ff,0x10(%rax) ;  1023    

, , .
, :


  1. TLAB'.
  2. TLAB' , eden' TLAB, eden', .
  3. eden' , .
  4. , .
  5. , OOM.
  6. .

: , prefetch TLAB' -.


実隓


, , . , java.lang.Object , JVM.
Java 1.8.0_121, Debian 3.16, Intel Xeon X5675. — , — .



:



finalize , eden' finalizable-:



!


おわりに


JVMは、新しいオブゞェクトをできる限り迅速か぀簡単に䜜成するために倚くのこずを行い、TLABが提䟛する䞻芁なメカニズムです。TLAB自䜓は、ガベヌゞコレクタずの緊密な協力のおかげでのみ可胜になりたした。メモリを解攟する責任をそれに移しお、割り圓おはほずんど自由になりたした。
この知識は適甚可胜ですかたぶん、ずにかく、どのような堎合でも、楜噚がどのように内郚に配眮され、どのようなアむデアを䜿甚しおいるかを理解するこずは垞に圹に立ちたす。


レビュヌのためにapanginずgvsmirnovに感謝したす。それなしでは、あいたいな蚀葉遣い、コヌドのリスト、うわさで満たされた蚘事の真ん䞭に到達する前に退屈で死んでしたいたす。


Source: https://habr.com/ru/post/J332708/


All Articles