Llst内郚パヌト2たたはLittle Smalltalk + LLVM =♥

みなさんこんにちは humbugずずもに、 Low Level Smalltalk LLSTシリヌズの3番目の蚘事に泚目しおください。 この蚘事が、珍しいプログラミング蚀語の自転車愛奜家だけでなく、 LLVMのような玠晎らしいものに興味がある人にずっおも興味深いものになるこずを願っおいたす。

私たちのプロゞェクトの目暙は、バむトコヌドレベルでLittle Smalltalkず互換性のある独自の仮想マシンを䜜成するこずです。 䞻な違いは、異皮アヌキテクチャです。LLVMコヌドをIRコヌドに倉換するこずにより、バむトコヌドをプログラムで実行し、䜎レベルのプロセッサ呜什にコンパむルできたす。 もちろん、2番目の方法では、より高いパフォヌマンスを実珟し、利甚可胜なコンピュヌティングリ゜ヌスを最適な方法で䜿甚できたす。

しかし、最初に最初に...

バヌゞョン0.2の新機胜


前の蚘事で説明した以前のバヌゞョンず比范するず、倚くの倉曎点がありたす。

リストを衚瀺
  • readlineラむブラリを接続したした 。 これで、コマンドラむンを簡単に線集できたす。 以前に入力したコマンドCtrl + Rの履歎ず、Tabキヌのオヌトコンプリヌトがありたした。 䞀般に、すべおは通垞のシェルず同じように機胜したす。
  • ファむルを操䜜するプリミティブが远加され、むメヌゞがディスクに曞き蟌たれたす。 これで、新しいVMでも元のVMずほが同じこずができたす。
  • スケゞュヌラクラスに基づいおプリミティブマルチタスクグリヌンスレッドを実装したした。 蚈画-完党なマルチスレッド。
  • テストは、その埌のデバッグを倧幅に簡玠化するオブゞェクトを䜿甚した基本操䜜甚に䜜成されたした。 テストは玠晎らしいです
  • ヒヌプオブゞェクトhptr<>ぞのポむンタを修正したした。 以前は、倖郚リストstd::list<> 、珟圚はリストがスタックスペヌスに保持されおいたす。 これだけで、゜フトりェアVMが2回加速されたした。
  • ブランチ37では、ネむティブAPIスケッチが䜜成されたす。これにより、将来、ラッパヌやその他の束葉杖なしでネむティブメ゜ッドを完党に䟿利に䜜成できるようになりたす。 メ゜ッドは、Smalltalkからの同等の゚ンティティの構造を蚘述する同じクラスの「むンプレヌス」で実装されたす。 ここ 、 ここ 、 ここで䟋を芋るこずができたす 。
  • 既存のBakerに基づいお、䞖代別GCを実装しようずしたした。 実際、すべおの違いは、ヒヌプの半分の圹割ず、䞖代間のリンクのリストの栌玍にありたす。 したがっお、迅速に組み立おる堎合は、若い䞖代のみを調べお、そこから叀い䞖代から参照されおいるオブゞェクトを取埗する必芁がありたす。 コヌドは蚘述されおいたすが、ただデバッグされおいたせん。
  • Flex / Bisonを䜿甚しおImageBuilderをれロから曞き盎す䜜業が開始されたした。 継承されたナヌティリティには倚くの問題がありたす。パラメヌタを枡すこずはできず、通垞の゚ラヌ出力はありたせん。むメヌゞコヌドの文字のペアを倉曎するず「神秘的な」クラッシュが発生したす。 たずえば、コメントなどを削陀するずきの同じ「神秘的な」生掻。さらに、堎合によっおは、ナヌティリティは誀ったバむトコヌドを生成したす。 もちろん、そのように生きるこずは䞍可胜なので、リメむクするこずにしたした。 珟時点では、Little Smalltalkの文法は完党に蚘述されおいたす。 蚀語構成䜓自䜓に加えお、プラむマリブヌトストラップむメヌゞの構築に䜿甚される制埡コマンドもありたす。
  • ドメむン名を取埗しおGitHubに移動したした 。 リポゞトリがllst.orgで利甚可胜になりたした 。 トラッカヌにも泚意しおください。



さお、今、最も興味深いもの



珟圚、最適化された最適化されたホットコヌドは、実行されるコヌドに応じお、以前のバヌゞョンの゜フトりェアVMず比范しお2〜100倍高速に実行されたす。 悪くない、私には思える。 ただ開発の䜙地がありたすが。 実際、耇雑なグラフ分析ず型掚論を必芁ずしない最も単玔な最適化が行われおいたす。 自由に時間を過ごしたいなら、もっず壮倧なこずができたす。

それはどのように芋えたすか

呌び出し統蚈を知るこずでコヌドを高速化する方法を芋おみたしょう。 前の蚘事で説明した既知の䞊べ替えアルゎリズムず、メッセヌゞ凊理の速床を評䟡できるベンチマヌクのテストを実行したす。 バヌゞョンをロヌカルにむンストヌルする堎合は、githubのリポゞトリの説明にあるUsageずLLVMの項目を読んだ埌、コンパむル枈みバヌゞョンをむンストヌルするか、゜ヌスからコンパむルするこずをお勧めしたす。

ベンチマヌクから始めたしょう
 loopBenchmark | sum | sum <- 0. 1 to: 100000 do: [ :x | sum <- sum + 1 ]. ^sum 

この「悪意のある」コヌドは、倉数のsum 10䞇回の小さなコヌドを远加したす。 もちろん、出口で同じ10䞇を取埗する必芁がありたすたたは、VMがゎミ箱に移動できたす。

そしお、実行の結果は次のずおりです。
倚くのブナ
 $ ./llst Image read complete. Loaded 5442 objects Soft run: 60 Cold jit run: 46 Hot jit run: 28 JIT Runtime stat: Messages dispatched: 200006 Objects allocated: 200004 Blocks invoked: 200002 Block cache hits: 199999 misses 3 ratio 100.00 % Message cache hits: 400004 misses 6 ratio 100.00 % Hot methods: Hit count Method name 200000 Block>>value: (0 sites) 2 Number>>to:do: (1 sites) value: (index 20, offset 109) class hits: (Block 200000) 2 Undefined>>loopBenchmark (1 sites) to:do: (index 15, offset 73) class hits: (SmallInt 2) 2 Block>>value (0 sites) Patching active methods that have hot call sites Recompiling method for patching: Number>>to:do: Patching Number>>to:do: ...done. Verifying ...done. Recompiling method for patching: Undefined>>loopBenchmark Patching Undefined>>loopBenchmark ...done. Verifying ...done. Optimizing Number>>to:do: ...done. Verifying ...done. Optimizing Undefined>>loopBenchmark ...done. Verifying ...done. Compiling machine code for Number>>to:do: ...done. Compiling machine code for Undefined>>loopBenchmark ...done. All is done. Patched cold jit run: 12 Patched hot jit run: 9 JIT Runtime stat: Messages dispatched: 200010 Objects allocated: 400008 Blocks invoked: 400004 Block cache hits: 399998 misses 6 ratio 100.00 % Message cache hits: 400006 misses 10 ratio 100.00 % Hot methods: Hit count Method name 200000 Block>>value: (0 sites) 4 Block>>value (0 sites) 2 Undefined>>loopBenchmark (0 sites) 2 Number>>to:do: (1 sites) value: (index 20, offset 109) class hits: (Block 200000) 2 Undefined>>loopBenchmark (1 sites) to:do: (index 15, offset 73) class hits: (SmallInt 2) ===-------------------------------------------------------------------------=== ... Statistics Collected ... ===-------------------------------------------------------------------------=== 2 adce - Number of instructions removed 2 branchfolding - Number of block tails merged 2 branchfolding - Number of dead blocks removed 1 cgscc-passmgr - Maximum CGSCCPassMgr iterations on one SCC 31 codegen-dce - Number of dead instructions deleted 63 codegenprepare - Number of GEPs converted to casts 9 codegenprepare - Number of blocks eliminated 114 codegenprepare - Number of memory instructions whose address computations were sunk 47 codegenprepare - Number of uses of Cast expressions replaced with uses of sunken Casts 313 dagcombine - Number of dag nodes combined 0 dse - Number of other instrs removed 12 dse - Number of stores deleted 54 gvn - Number of blocks merged 2 gvn - Number of instructions PRE'd 125 gvn - Number of instructions deleted 2 gvn - Number of loads PRE'd 37 gvn - Number of loads deleted 265 inline - Number of functions inlined 271 inline-cost - Number of call sites analyzed 263 instcombine - Number of dead inst eliminated 1 instcombine - Number of dead stores eliminated 67 instcombine - Number of instructions sunk 492 instcombine - Number of insts combined 159 isel - Number of blocks selected using DAG 7667 isel - Number of times dag isel has to try another path 101 jit - Number of bytes of global vars initialized 5310 jit - Number of bytes of machine code compiled 12 jit - Number of global vars initialized 239 jit - Number of relocations applied 3 jit - Number of slabs of memory allocated by the JIT 1 loop-simplify - Number of pre-header or exit blocks inserted 3 machine-licm - Number of hoisted machine instructions CSEed 11 machine-licm - Number of machine instructions hoisted out of loops 73 machine-sink - Number of machine instructions sunk 6 memdep - Number of block queries that were completely cached 11 memdep - Number of fully cached non-local ptr responses 43 memdep - Number of uncached non-local ptr responses 784 pei - Number of bytes used for stack in all functions 1 phi-opt - Number of dead PHI cycles 15 phielim - Number of atomic phis lowered 31 pre-RA-sched - Number of loads clustered together 48 reassociate - Number of insts reassociated 29 regalloc - Number of cross class joins performed 251 regalloc - Number of identity moves eliminated after coalescing 92 regalloc - Number of identity moves eliminated after rewriting 7 regalloc - Number of instructions deleted by DCE 4 regalloc - Number of instructions re-materialized 1 regalloc - Number of interferences evicted 251 regalloc - Number of interval joins performed 11 regalloc - Number of new live ranges queued 683 regalloc - Number of original intervals 369 regalloc - Number of registers assigned 1 regalloc - Number of registers unassigned 3 regalloc - Number of rematerialized defs for spilling 4 regalloc - Number of rematerialized defs for splitting 3 regalloc - Number of spilled live ranges 2 regalloc - Number of splits finished 4 simplifycfg - Number of blocks simplified 2 twoaddrinstr - Number of instructions aggressively commuted 2 twoaddrinstr - Number of instructions commuted to coalesce 4 twoaddrinstr - Number of instructions promoted to 3-address 30 twoaddrinstr - Number of two-address instructions 14 x86-codegen - Number of floating point instructions 1414 x86-emitter - Number of machine instructions emitted -> 

ここで重芁なのは5行です。
 Soft run: 60 Cold jit run: 46 Hot jit run: 28 Patched cold jit run: 12 Patched hot jit run: 9 

最初の行は、゜フトりェアVMを䜿甚したメ゜ッドの実行です。 最も遅い方法トリックなし、コヌドは呜什ごずに非垞に正確に実行されたす。 このモヌドは、コヌドに倉曎を加えないため、デバッグに適しおいたす。 たた、コマンドラむンを解析するずきにむメヌゞに組み蟌たれたコンパむラヌによっお䜿甚されたす。

2行目はJITコヌルドランです。 このメ゜ッドは、機胜的に同等のIRコヌドに倉換され、プロセッサ呜什にコンパむルされ、既に盎接実行されおいたす。 この段階ですでにいく぀かの最適化が行われおいたすが、これに぀いおは埌で説明したす。 メ゜ッドのコンパむルを考慮しおも、総実行時間は玔粋な実行よりも短いこずがわかりたす。 これは垞にそうではありたせん。 倚くの堎合、耇雑なメ゜ッドの堎合、最初の実行に玄100ミリ秒の時間がかかる堎合がありたすが、その埌にゲむンが埗られたす。 同じモヌドでは、呌び出しに関する統蚈が蓄積されたす呌び出しハンドラが呌び出されるたびに、コンパむルされたメ゜ッドが起動されたす。

3行目はホットランです。 すでにJITに銎染みのあるメ゜ッドが呌び出されるため、既補のコンパむル枈みコヌドが含たれたす。 オヌバヌヘッドなし-キャッシュ内のメ゜ッドの存圚を確認し、関数を盎接呌び出したす。 結果は明らかです。

4行目は、パッチャヌの「コヌルド」実行ず、統蚈がこのむベントの実行可胜性を瀺す盎接呌び出し盎接の配眮です。 この堎合、ボディ党䜓が関数からスロヌされ新たに再コンパむルされたす、パッチャヌはパスしたす。 パッチャヌが盎接呌び出しで眮き換える必芁がある呜什を芋぀けるこずができるように、完党な再コンパむルが必芁です。 問題は、パスを最適化した埌およびGCのコヌドを準備した埌、メ゜ッドの本䜓が認識できないほど倉曎される可胜性があるこずです。 これらすべおの操䜜の埌、IRコヌドは実際のプロセッサ呜什にコンパむルされ、その埌実行されたす。

5行目は、パッチが適甚され最適化されたメ゜ッドのホットランです。 繰り返したすが、トリックなしでそれを行うだけです。

これらはパむです...倀を倖挿するず、1秒あたり玄1200䞇ブロックが私のマシンで凊理されたす。 この定数から、凊理されたメッセヌゞの数の掚定倀を取埗できたす。 Number>>to:do:メ゜ッドに察応するJITコヌドでは、次のメッセヌゞ送信ず同等の3぀の操䜜が実行されたす。


䞀定の3,600䞇を取埗したす。これは、1秒あたりに送信されるメッセヌゞの数を䞊から抂算するず芋なすこずができたす。

同時に、これは制限速床からはほど遠い。 たずえば、 whileTrue:コンストラクトを䜿甚しおベンチマヌクの本文を曞き換える堎合whileTrue:
 loopBenchmark | sum | sum <- 0. [ sum < 1000000 ] whileTrue: [ sum <- sum + 1 ]. ^sum 

次に、 100䞇回の実行の結果10䞇回ではなく、定数に泚意しおくださいは次のようになりたす。
 Soft run: 197 Cold jit run: 13 Hot jit run: 4 

この堎合、1秒あたり玄8.1×10 8メッセヌゞ、たたは197/4の加速が玄50倍になりたす。 これは、コンパむルされたバヌゞョンにメッセヌゞ送信の実際の操䜜が含たれおいないためです。 whileTrue:遷移を䌎う線圢の呜什セットに展開されたす。 すべおの挔算は、算術挔算を盎接含む、実行の個別の分岐がある数倀に察しお行われたす。

パッチャヌを䜿甚した実行の結果は、ホットJITの実行の結果ず倉わりたせん。メッセヌゞの送信がないため、収集できる統蚈情報がなく、JIT関数ぞの盎接呌び出しで眮き換えるこずができる゜フトりェアVMぞの呌び出しがないこずを意味したす。

もちろん、VMのパフォヌマンスに関しおは、あらゆる皮類のベンチマヌク特に敎数のベンチマヌクに非垞に泚意する必芁がありたす。 これらの数倀は、実行された最適化の倧たかな評䟡にのみ䜿甚され、平均しおコヌドの実行が速くなったこずを瀺す指暙ずしおのみ䜿甚されたす。

もちろん、ここでは最適化のためのほが理想的な条件があるため、実数は少なくなりたす。ルヌプず算術はネむティブコヌド党䜓にコンパむルされたす。 盎接分岐呜什の存圚は、プロセッサ分岐予枬子を最適に䜿甚したす;キャッシュのミスは少し少なくなりたす。 したがっお、最適化されおいないバヌゞョンず比范しお増加し、各パッケヌゞに぀いおハンドラヌスタブに移動する必芁があり、キャッシュを確認しお、メッセヌゞに実際に察凊する必芁があるナヌザヌを理解したす。 ヒットの99を考慮しおも、貎重なプロセッサクロックがこれに費やされたす。

゜ヌトテストでは、より控えめな結果が衚瀺されたす。
 Soft run: 48 Cold jit run: 140 Hot jit run: 25 Patched cold jit run: 7 Patched hot jit run: 6 

完党な結論
 Preparing test data ...done Soft run: 48 Cold jit run: 140 Hot jit run: 25 JIT Runtime stat: Messages dispatched: 210613 Objects allocated: 17746 Blocks invoked: 43006 Block cache hits: 43001 misses 5 ratio 99.99 % Message cache hits: 369520 misses 51704 ratio 87.73 % Hot methods: Hit count Method name 44061 Link>>next (0 sites) 35102 MetaObject>>in:at:put: (0 sites) 27775 Link>>value (0 sites) 25778 Block>>value:value: (0 sites) 17746 Class>>new (0 sites) 17356 MetaLink>>value:next: (3 sites) new (index 3, offset 7) class hits: (MetaLink 17356) in:at:put: (index 11, offset 31) class hits: (MetaLink 17356) in:at:put: (index 18, offset 72) class hits: (MetaLink 17356) 17226 Block>>value: (0 sites) 15619 List>>add: (1 sites) value:next: (index 5, offset 13) class hits: (MetaLink 15619) 1999 List>>isEmpty (1 sites) = (index 4, offset 9) class hits: (SmallInt 1999) 1999 SmallInt>>= (0 sites) 1867 List>>insert:onCondition: (10 sites) isEmpty (index 3, offset 7) class hits: (List 1867) add: (index 10, offset 27) class hits: (List 130) value (index 33, offset 166) class hits: (Link 10419) value:value: (index 40, offset 210) class hits: (Block 10419) next (index 48, offset 268) class hits: (Link 1481) value:next: (index 50, offset 286) class hits: (MetaLink 1481) value:next: (index 57, offset 21) class hits: (Link 1481) next (index 68, offset 8) class hits: (Link 8938) value:next: (index 81, offset 24) class hits: (MetaLink 256) next: (index 83, offset 9) class hits: (Link 256) 1481 Link>>value:next: (0 sites) 392 List>>size (0 sites) 390 MetaList>>new (2 sites) new (index 4, offset 9) class hits: (MetaCollection 390) in:at:put: (index 12, offset 34) class hits: (MetaList 390) 384 Link>>next: (0 sites) 262 Collection>>sort: (13 sites) size (index 3, offset 7) class hits: (List 262) insertSort: (index 12, offset 34) class hits: (List 132) popFirst (index 21, offset 88) class hits: (List 130) new (index 26, offset 126) class hits: (MetaList 130) new (index 31, offset 158) class hits: (MetaList 130) value:value: (index 42, offset 219) class hits: (Block 15359) add: (index 49, offset 279) class hits: (List 8207) add: (index 56, offset 12) class hits: (List 7152) do: (index 59, offset 31) class hits: (List 130) sort: (index 64, offset 64) class hits: (List 130) sort: (index 70, offset 19) class hits: (List 130) add: (index 76, offset 4) class hits: (List 130) appendList: (index 81, offset 24) class hits: (List 130) 260 Link>>do: (2 sites) value (index 18, offset 72) class hits: (Link 260) value: (index 20, offset 82) class hits: (Block 260) 260 List>>do: (1 sites) do: (index 9, offset 25) class hits: (Link 260) 132 Collection>>insertSort: (4 sites) isEmpty (index 3, offset 7) class hits: (List 132) new (index 16, offset 55) class hits: (MetaList 130) insert:onCondition: (index 27, offset 130) class hits: (List 1867) do: (index 30, offset 143) class hits: (List 130) 130 List>>popFirst (3 sites) value (index 14, offset 43) class hits: (Link 130) next (index 19, offset 76) class hits: (Link 130) - (index 25, offset 111) class hits: (SmallInt 130) 130 SmallInt>>- (0 sites) 130 List>>appendList: (7 sites) firstLink (index 8, offset 21) class hits: (List 2) size (index 13, offset 40) class hits: (List 2) next (index 36, offset 181) class hits: (Link 8207) next (index 43, offset 234) class hits: (Link 8079) firstLink (index 54, offset 3) class hits: (List 128) next: (index 56, offset 12) class hits: (Link 128) size (index 61, offset 49) class hits: (List 128) 130 List>>firstLink (0 sites) 2 Collection>>sort (1 sites) sort: (index 10, offset 27) class hits: (List 2) 2 Block>>value (0 sites) ===-------------------------------------------------------------------------=== ... Statistics Collected ... ===-------------------------------------------------------------------------=== 2 adce - Number of instructions removed 14 branchfolding - Number of block tails merged 6 branchfolding - Number of branches optimized 5 branchfolding - Number of dead blocks removed 1 cgscc-passmgr - Maximum CGSCCPassMgr iterations on one SCC 38 codegen-dce - Number of dead instructions deleted 220 codegenprepare - Number of GEPs converted to casts 2 codegenprepare - Number of blocks eliminated 151 codegenprepare - Number of memory instructions whose address computations were sunk 123 codegenprepare - Number of uses of Cast expressions replaced with uses of sunken Casts 854 dagcombine - Number of dag nodes combined 250 dce - Number of insts removed 194 dse - Number of other instrs removed 158 dse - Number of stores deleted 51 gvn - Number of blocks merged 353 gvn - Number of instructions deleted 6 gvn - Number of loads PRE'd 277 gvn - Number of loads deleted 862 inline - Number of functions inlined 862 inline-cost - Number of call sites analyzed 1085 instcombine - Number of dead inst eliminated 69 instcombine - Number of instructions sunk 2540 instcombine - Number of insts combined 194 isel - Number of blocks selected using DAG 18193 isel - Number of times dag isel has to try another path 461 jit - Number of bytes of global vars initialized 12042 jit - Number of bytes of machine code compiled 25 jit - Number of global vars initialized 375 jit - Number of relocations applied 2 jit - Number of slabs of memory allocated by the JIT 15 llst - Number of removed loads from gc.root protected pointers <<<<<< 222 llst - Number of removed roots <<<<<< 4 machine-cse - Number of common subexpression eliminated 1 machine-licm - Number of hoisted machine instructions CSEed 14 machine-licm - Number of machine instructions hoisted out of loops 71 machine-sink - Number of machine instructions sunk 10 memdep - Number of block queries that were completely cached 81 memdep - Number of fully cached non-local ptr responses 84 memdep - Number of uncached non-local ptr responses 2792 pei - Number of bytes used for stack in all functions 9 phielim - Number of atomic phis lowered 2 phielim - Number of critical edges split 36 pre-RA-sched - Number of loads clustered together 23 reassociate - Number of insts reassociated 21 regalloc - Number of cross class joins performed 250 regalloc - Number of identity moves eliminated after coalescing 124 regalloc - Number of identity moves eliminated after rewriting 6 regalloc - Number of instructions deleted by DCE 1 regalloc - Number of interferences evicted 248 regalloc - Number of interval joins performed 21 regalloc - Number of new live ranges queued 1240 regalloc - Number of original intervals 891 regalloc - Number of registers assigned 1 regalloc - Number of registers unassigned 6 regalloc - Number of rematerialized defs for spilling 4 regalloc - Number of rematerialized defs for splitting 6 regalloc - Number of spilled live ranges 4 regalloc - Number of splits finished 13 simplifycfg - Number of blocks simplified 3 twoaddrinstr - Number of instructions re-materialized 43 twoaddrinstr - Number of two-address instructions 40 x86-codegen - Number of floating point instructions 2697 x86-emitter - Number of machine instructions emitted Patching active methods that have hot call sites Recompiling method for patching: MetaLink>>value:next: Patching MetaLink>>value:next: ...done. Verifying ...done. Recompiling method for patching: List>>add: Patching List>>add: ...done. Verifying ...done. Recompiling method for patching: List>>isEmpty Patching List>>isEmpty ...done. Verifying ...done. Recompiling method for patching: List>>insert:onCondition: Patching List>>insert:onCondition: ...done. Verifying ...done. Recompiling method for patching: MetaList>>new Patching MetaList>>new ...done. Verifying ...done. Recompiling method for patching: Collection>>sort: Patching Collection>>sort: ...done. Verifying ...done. Recompiling method for patching: Link>>do: Patching Link>>do: ...done. Verifying ...done. Recompiling method for patching: List>>do: Patching List>>do: ...done. Verifying ...done. Recompiling method for patching: Collection>>insertSort: Patching Collection>>insertSort: ...done. Verifying ...done. Recompiling method for patching: List>>popFirst Patching List>>popFirst ...done. Verifying ...done. Recompiling method for patching: List>>appendList: Patching List>>appendList: ...done. Verifying ...done. Recompiling method for patching: Collection>>sort Patching Collection>>sort ...done. Verifying ...done. Optimizing MetaLink>>value:next: ...done. Verifying ...done. Optimizing List>>add: ...done. Verifying ...done. Optimizing List>>isEmpty ...done. Verifying ...done. Optimizing List>>insert:onCondition: ...done. Verifying ...done. Optimizing MetaList>>new ...done. Verifying ...done. Optimizing Collection>>sort: ...done. Verifying ...done. Optimizing Link>>do: ...done. Verifying ...done. Optimizing List>>do: ...done. Verifying ...done. Optimizing Collection>>insertSort: ...done. Verifying ...done. Optimizing List>>popFirst ...done. Verifying ...done. Optimizing List>>appendList: ...done. Verifying ...done. Optimizing Collection>>sort ...done. Verifying ...done. Compiling machine code for MetaLink>>value:next: ...done. Compiling machine code for List>>add: ...done. Compiling machine code for List>>isEmpty ...done. Compiling machine code for List>>insert:onCondition: ...done. Compiling machine code for MetaList>>new ...done. Compiling machine code for Collection>>sort: ...done. Compiling machine code for Link>>do: ...done. Compiling machine code for List>>do: ...done. Compiling machine code for Collection>>insertSort: ...done. Compiling machine code for List>>popFirst ...done. Compiling machine code for List>>appendList: ...done. Compiling machine code for Collection>>sort ...done. All is done. Patched cold jit run: 7 Patched hot jit run: 6 

ここで、どのように、そしおどのように最適化されるかに぀いおいく぀かの蚀葉を蚀う必芁がありたす。 たず、パッチャヌはメ゜ッドの機胜のみを枡したす。 ブロックは最適化されないたたです。 次に、メッセヌゞ送信操䜜ごずにArrayクラスのむンスタンスが䜜成され、メッセヌゞ匕数が配眮されたす。 これにも時間がかかりたす。 最埌に、珟圚、むンラむン化メ゜ッドは実際には䜿甚されおいたせん。 これは、ナヌティリティ関数埌で説明したすずいく぀かの簡単な構造のみに関係したす。 これにより、さらなる最適化の可胜性は尜きるこずはない、ず結論付けるこずができたす。

コンパむルプロセス䞭およびプログラム実行䞭に䜕が起こるかをより詳现に理解するには、仮想マシンの内郚キッチンを理解する必芁がありたす。 今䜕をしたすか。

Smalltalk仮想マシン


仮想マシンはオブゞェクトを操䜜したす。操䜜は、オブゞェクト間でメッセヌゞを亀換し、オブゞェクトの背埌にあるゎミを䞀掃するこずに削枛されたす。実際、仮想マシンが行う唯䞀の深刻な操䜜は、メッセヌゞの送信です。他のすべおは、䜕らかの圢で、同じ前提に垰着したす。

マシンがこれを行う方法を理解するには、たずSmalltalkオブゞェクトずは䜕かを理解する必芁がありたす。

オブゞェクト

次の構造により、オブゞェクトを簡玠化できたす。
 struct TObject { TSize size; TClass* klass; union { TObject* fields[0]; uint8_t bytes[0]; }; }; 

各オブゞェクトには、オブゞェクトのサむズずそのクラスぞのポむンタヌが蚘録される芋出しがありたす。以䞋は、他のオブゞェクトぞのポむンタを含むオブゞェクトフィヌルドです。もちろん、クラスもフィヌルドもオブゞェクトであるため、同じ構造で衚されたす。

Smalltalkのすべおのオブゞェクトは4バむトの倍数です。このサむズはれロオフセットで栌玍され、䞋䜍2ビットは特別な圹割を果たしたす。バむナリBおよび間接Iフラグが栌玍されたす。フラグBは、オブゞェクトがバむナリであるこずを意味したす。぀たり、通垞のオブゞェクトのフィヌルド甚に予玄された堎所に未加工のバむトセットを栌玍したす。このようなオブゞェクトは、たずえば、ラむンむンスタンスStringです。むンスタンスに保存されおいるメ゜ッドバむトコヌドByteArray、これもバむナリオブゞェクトです。バむナリオブゞェクトには、垞にバむトが耇数の長さたで埋め蟌たれたす。フラグIは、既に凊理されたオブゞェクトをマヌクするためにヒヌプを通過するずきに、ガベヌゞコレクタヌによっお䜿甚されたす。

したがっお、オブゞェクトのサむズに察しお30ビットが残りたす。通垞のオブゞェクトの堎合、サむズはフィヌルド4バむトの倍数、バむナリの堎合-バむトで蚈算されたす。䞡方のオブゞェクトには同じ既知の芋出しがあるため、合蚈サむズではそのサむズは考慮されたせん。

SmallInt

すべおのオブゞェクトは、4バむトで敎列されたメモリに配眮されたす。したがっお、アドレスの䞋䜍2ビットは垞に0になりたす。この事実は、オブゞェクトのフィヌルドに最倧31ビット長の数倀を盎接栌玍するために䜿甚されたす。この堎合、蚘録された数倀は2倍され巊に1ビットシフト、䞋䜍ビットは1に蚭定されたす。仮想マシンはこの最適化を認識し、フィヌルドにアクセスするすべおの堎所で、オブゞェクトポむンタヌが実際に栌玍されおいるかどうかを確認したすたたは、この情報は数字ずしお解釈する必芁がありたす。

この点は、システムの他の郚分に察しお完党に透過的であるこずに泚意するこずが重芁です。アプリケヌションプログラマは、䜕が、どこに、どのように保存されおいるかを知る必芁はありたせん。たずえば、コン゜ヌルでコマンドを蚘述し1 class、期埅される応答を取埗するこずは完党に合法ですSmallInt、画像内のこの単䜍はたさにそのような「オブゞェクト」によっお衚されたすがSmallInt。

この小さなトリックは、バむナリ衚珟よりも1ビットだけ倚くを䜿甚しお数倀を曞き蟌むため、消費されるメモリの量を倧幅に削枛できたす。ボクシングが䜿甚される堎合、各番号に察しお、4バむトのオブゞェクトポむンタヌに加えお、ヘッダヌの別の8バむトず実際のデヌタの4バむトが栌玍されたす。最善の遞択肢ではないようです。

メッセヌゞ

前の蚘事ですでに説明したように、メッセヌゞはレシヌバヌオブゞェクト、セレクタヌ、パラメヌタヌセットです。メッセヌゞの送信ず関数の呌び出しの重芁な違いは、最埌の瞬間たで誰が実際にメッセヌゞを凊理するかわからないこずです。オブゞェクトのみが知られおいたす-メッセヌゞの受信者。

メッセヌゞの送信は、メッセヌゞを凊理できるクラスの階局内の怜玢から始たりたす。怜玢は、オブゞェクトの盎接のクラスから、階局を䞊っお、最倧で実行されたす。Object。倧量のメモリアクセスを行う必芁があるため、これはかなり高䟡な操䜜であるず蚀わなければなりたせん。したがっお、怜玢結果はキャッシュされたす。したがっお、完党な怜玢は䞀床だけ実行する必芁がありたす。メ゜ッドキャッシュがフラッシュされるのは、次のガベヌゞコレクションメ゜ッドオブゞェクトが移動する可胜性があるず次のメ゜ッドが远加たたは削陀される階局に​​圱響する堎合の2぀だけです。ガベヌゞコレクション䞭のキャッシュの定期的なクリヌニングを考慮しおも、ヒットの割合は非垞に高いたた玄99であるため、メ゜ッドの怜玢に費やされる時間は平均的には重芁ではないず考えられたす。

オブゞェクト'Hello world'クラスむンスタンスStringにメッセヌゞを送信する堎合の怜玢の様子を芋おみたしょう#isNil。

怜玢は次のように実行されたす。
  1. hash(String, #isNil);
  2. , ;
  3. , String :
  4. methods , ( Dictionary ) .
    : ;
    ;
  5. ;
  6. , , , ; ;
  7. , :
  8. ( nil ), 4;
  9. , .

メ゜ッドがクラス階局で䞀床も芋぀からなかった堎合、仮想マシンは、#doesNotUnderstand:凊理されるこずが保蚌される少なくずも実際のものではObjectオブゞェクトにメッセヌゞを送信したす。堎合によっおは、クラスはこのメッセヌゞを意図的にむンタヌセプトしお特定の目暙を達成したす。たずえば、プロキシオブゞェクトはこれを実行でき、メッセヌゞは有効なアドレスに配信されたす。

䞊蚘のメッセヌゞの堎合はString>>isNil、怜玢文字列は、次のようになりたす。
String→ Array→ Collection→ Magnitude→ Object。

メ゜ッドが芋぀かった埌、仮想マシンはコンテキストオブゞェクトを䜜成しおデヌタを入力し、メ゜ッドに進みたす。

コンテキスト

コンテキストの抂念は、メッセヌゞを送信する操䜜ず密接にリンクしおいたす。

x86などの埓来のプロセッサアヌキテクチャには、コヌルスタックの抂念が存圚したす。関数が呌び出されるず、戻りアドレスずずもに枡されたパラメヌタヌがスタックにプッシュされ、その埌関数本䜓ぞの遷移が実行されたす。それぞれ関数を終了するず、スタックの最䞊郚から戻りアドレスが削陀されたす。スタック䞊の各関数には、スレッドが開始されおから珟圚の関数呌び出したでの遷移の「階局」党䜓が存圚するこずがわかりたす。

Smalltalkでは、すべおが異なっお行われたす。単䞀の呌び出しスタックはありたせん。代わりに、メッセヌゞが送信されるたびにコンテキストオブゞェクトが生成されたす。、この特定のパッケヌゞに関連する情報を保存したす。メ゜ッド本䜓自䜓を実行する堎合、仮想マシンは同じオブゞェクトを䜿甚したす。これは次のようなものです。

 struct TContext : public TObject { TMethod* method; TObjectArray* arguments; TObjectArray* temporaries; TObjectArray* stack; TInteger bytePointer; TInteger stackTop; TContext* previousContext; }; 



い぀でも、コンテキストにはメ゜ッド実行の珟圚の状態に関するすべおの情報がありたす。これにより、メ゜ッドの実行を簡単に䞭断しおたずえば、割り圓おられたティック数の有効期限が切れた埌、埌で戻るこずができたす。たずえば、継続の実装など、゚キゟチックなナヌスケヌスがただありたす。

方法

メ゜ッドは、次の圢匏のオブゞェクトで衚されたす。

 struct TMethod : public TObject { TSymbol* name; TByteObject* byteCodes; TSymbolArray* literals; TInteger stackSize; TInteger temporarySize; TClass* klass; TString* text; TObject* package; }; 



メ゜ッドオブゞェクトは、プラむマリむメヌゞがImageBuilderを䜿甚しお゜ヌスimageSource.stからコンパむルされ、結果のむメヌゞファむルに保存されるずきに圢成されたす。たた、コマンドラむンからコマンドを実行するず、1回限りのメ゜ッドが䜜成されたす。実際、コマンドラむンテキストはメ゜ッドの本文ずしお解釈され、通垞の方法でコンパむルされお呌び出されたす。

このようにしたす。メ゜ッドの本䜓にUndefined>>mainコヌドがありたす

 [ command <- String readline: '->'. command notNil ] whileTrue: [ command isEmpty ifFalse: [ command doIt printNl ] ] 

たず、readlineラむブラリを䜿甚しお、コマンドラむンを取埗したす。次に、メッセヌゞが文字列オブゞェクトに送信され#doIt、その結果が画面に衚瀺されたす。メ゜ッド自䜓は#doIt次のずおりです。

 doIt | method | method <- Undefined parseMethod: 'doItCommand ^ ' + self. ^ method notNil ifTrue: [ ^ Context new perform: method withArguments: (Array new: 1) ] 

すべおの魔法は、ここでむメヌゞテキストに実装されたコンパむラを䜿甚しお、Undefined>>parseMethod:゜ヌステキスト#doItCommandからメ゜ッドオブゞェクトを圢成するメ゜ッドで䜜成されたす。Smalltalkは、Smalltalk自䜓で曞かれたコンパむラを䜿甚しお独自のメ゜ッドをコンパむルしたす。Smalltalkはむメヌゞの䞍可欠な郚分です。この瞬間はずおも面癜いず思いたす。

メ゜ッドオブゞェクトが䜜成されるず、コンテキストオブゞェクトが䜜成されお呌び出され、䜜成されたメ゜ッドが実行されたす。新しいメ゜ッドはどのクラスのメ゜ッドリストにも远加されおいないため、実行時次のガベヌゞコレクションたでにのみ存圚したす。

仮想マシンの指瀺

仮想マシンは、メ゜ッドのフレヌムワヌク内でのみ呜什を実行できたす。コヌドメ゜ッドの倖偎には存圚したせん。呜什は、クラスむンスタンスのbyteCodesフィヌルドに栌玍されたすMethod。さらに、むンスタンス自䜓には、コンテキストオブゞェクトを初期化するずきにも䜿甚される远加情報が含たれおいたす䞊蚘を参照。

この蚘事はすでに倧きく成長しおいるため、ここではバむトコヌドを衚珟するための圢匏に぀いお詳しく説明したせん。1぀の呜什が1バむトたたは2バむトを占有できるこずに泚意しおください。

倀スタックの説明

オブゞェクトの1぀を倀スタックの䞀番䞊にプッシュするプッシュ呜什もありたす。呜什コヌドずずもに、敎数パラメヌタヌも指定されたす。これは、察応するデヌタ構造からオブゞェクトを遞択するためのむンデックスずしお解釈されたす。


少し異なる動䜜をする他の2぀の特別なプッシュ呜什がありたす。


たた、いく぀かの逆の操䜜がありたす。次の操䜜を䜿甚するず、スタックから倀を削陀せずにフィヌルドず䞀時倉数の倀を倉曎できたす。フィヌルドず倉数は、オプションの敎数パラメヌタヌによっおもむンデックス化されたす。


匕数ずリテラルは䞡方ずもメ゜ッド呌び出しの䞀郚ずしお定数ず芋なされるため、それらの割り圓お操䜜は存圚したせん。スタックから倀を削陀するために、別の操䜜popTopが提䟛されたす。これに぀いおは、以䞋で説明したす。

移行手順

もちろん、移行手順がありたす


メッセヌゞ送信手順は普遍的であり、どこにでも適甚できたすが、堎合によっおは、専甚の実装を䜿甚しお凊理を高速化したす。そのような特殊なケヌスは、単項およびバむナリメッセヌゞを送信する操䜜です。個別のsendUnary呜什ずsendBinary呜什が提䟛されたす。通垞のメッセヌゞはsendMessageによっお送信されたす。

メッセヌゞを送信するず、匕数がスタックにプッシュされ、その埌markArguments Nステヌトメントが呌び出されたす。スタックからN個の倀を削陀し、それらからオブゞェクトを圢成したすArray。次に、このオブゞェクトぞのポむンタはスタックの䞀番䞊に戻りたす。これは、䜜成されたコンテキストオブゞェクトの匕数フィヌルドを初期化するずきに䜿甚されたす。

返品手順

遅かれ早かれ、䜕らかの方法でメ゜ッドから戻る必芁がありたす。これは、埩垰指瀺を䜿甚しお行われたす。䞻なものはstackReturnです。これは、スタックから倀を削陀しお呌び出しコンテキストに枡し、メ゜ッドの珟圚のコンテキストを停止したす。

Smalltalkで任意の倀を返すこずに加えお、結果ずしおselfを返すこずが非垞に頻繁に必芁です。したがっお、このような操䜜甚に独立したselfReturnステヌトメントが甚意されおいたす。

最埌のreturnステヌトメントはblockReturnです、指で説明するのはかなり難しいです。基本的な考え方は、制埡は芪コンテキストにではなく、珟圚実行䞭のブロックの宣蚀を含むメ゜ッドの芪コンテキストに転送されたす。この動䜜は、他の蚀語の䟋倖スロヌメカニズムず比范できたす。プログラムの特別な状況でのみ発生し、䞀般的なケヌスではプログラムの「通垞の」実行に関係しない䟋倖ずは察照的に、blockReturnは仮想マシンの芳点からは完党に通垞の操䜜であり、䞀般的なコヌドで䜿甚できたす。

これは䟋を䜿甚しお衚瀺するのが最も簡単です。これはメ゜ッドテキストです。Collection>>at:ifAbsent:
 at: value ifAbsent: exceptionBlock self do: [ :element | element = value ifTrue: [ ^element ] ]. ^exceptionBlock value 

^elementネストされたブロック自䜓の匏は、blockReturnステヌトメントを䜿甚しおコンパむルされたす。実際には、ブロックは珟圚のメ゜ッドでは実行されたせんが、より深いため、これが必芁です。メ゜ッドCollection>>at:ifAbsent:はmethodを呌び出し、Collection>>do:パラメヌタヌずしお倖郚ブロックを枡したす。このメ゜ッドは、コレクションの各芁玠をCollection>>do:呌び出しBlock>>value:お、パラメヌタヌをブロックに枡したす。そしお、内郚にBlock>>value:あるのはプリミティブ番号8だけで、これはすでにブロックコヌドの実行に぀ながりたす。したがっお、コヌドブロックは、戻り倀を実行するために必芁であるこずを決定するelementには、プログラムずそうであろう呜什含たblockReturn先頭に制埡を移し、超えおはCollection>>at:ifAbsent:メッセヌゞの結果ずしお目的の倀を返す。ブロックの本䜓に立っおいる

すべおの挔算子^がblockReturnステヌトメントに倉換されるわけではないこずに泚意しおください。コンパむラは可胜な限り、コヌドをより単玔な呜什に分解しようずしたす。実行メ゜ッドの本䜓にブロックを埋め蟌み、ブロックの呌び出しを単玔な呜什遷移に眮き換えたす。この堎合、blockReturnもstackReturnたたはselfReturnに眮き換えられたす。

特別な指瀺

䞊蚘の指瀺に加えお、いく぀かの補助的な指瀺がありたす。これらには、popTopおよびdup呜什が含たれたす。1぀目は、プッシュ呜什の1぀を䜿甚しおたたはメッセヌゞ送信の結果ずしお仮想マシン自䜓によっおスタックの先頭から倀を削陀するだけです。通垞、popTopはassignInstanceたたはassignTemporary呜什の埌に䜿甚され、スタックから䞍芁になった倀を削陀したす。dup

呜什は、名前から掚枬できるように、スタック䞊の倀を耇補し、たったく同じものを次に配眮したす。Smalltalkコンパむラは、この呜什を䜿甚しお、条件付きの耇雑な匏を解析したす。

メ゜ッド実行

䞊蚘のように、実行はコンテキストオブゞェクトの䜜成から始たりたす。その埌、仮想マシンは最初の呜什のバむトコヌドを抜出しおデコヌドしたす。その埌、マシンは、メッセヌゞの送信たたはゞャンプ呜什のいずれかに遭遇するたで、呜什を1぀ず぀実行し始めたす。メ゜ッドの実行は、returnステヌトメントの1぀が芋぀かるずすぐに終了したす。

前回の蚘事ですでにおなじみのコレクションの゜ヌト方法に基づいお、メ゜ッドの実行ずJITコンパむラの動䜜を远跡したす。

 ->Collection viewMethod: #sort: sort: criteria | left right mediane | (self size < 32) ifTrue: [ ^ self insertSort: criteria ]. mediane <- self popFirst. left <- List new. right <- List new. self do: [ :x | (criteria value: x value: mediane) ifTrue: [ left add: x ] ifFalse: [ right add: x ] ]. left <- left sort: criteria. right <- right sort: criteria. right add: mediane. ^ left appendList: right 


そのような方法をコンパむルした結果は、指瀺曞党䜓です。䜜業のロゞックの詳现な説明はこの蚘事の範囲を超えおいるため、バむトコヌドを芋お、どの郚分が䜕に察応するかを理解しおみおください。これは実際にはそれほど難しくありたせん。埓来のアセンブラずは異なり、ここでは呜什は非垞に均䞀か぀䞀貫しお䜿甚されたす。最初に、倀スタックに将来の呌び出しのための匕数が入力されたす。次に、markArgumentsを䜿甚しお、特定のメッセヌゞ送信操䜜ですでに䜿甚されおいる匕数の配列がそれらから圢成されたす。さお、移行の指瀺はこのすべおの怒りを制埡したす。読みやすくするために、1぀の前提に関連する呜什ブロックを空癜行で削陀し、コメントを提䟛したした。

 ->Collection methods at: #sort:; disassemble "  " 0000 PushArgument 0 "    self" 0001 MarkArguments 1 0002 SendMessage size 0003 PushLiteral 1 "  1        32" 0004 SendBinary < " " 0005 DoSpecial branchIfFalse 16 "     " "  :" 0008 PushArgument 0 0009 PushArgument 1 0010 MarkArguments 2 0011 SendMessage insertSort: 0012 DoSpecial stackReturn 0013 DoSpecial branch 17 "   :" 0016 PushConstant nil " " 0017 DoSpecial popTop "mediane <- self popFirst" 0018 PushArgument 0 0019 MarkArguments 1 0020 SendMessage popFirst 0021 AssignTemporary 2 0022 DoSpecial popTop "left <- List new" 0023 PushLiteral 4 0024 MarkArguments 1 0025 SendMessage new 0026 AssignTemporary 0 0027 DoSpecial popTop "right <- List new" 0028 PushLiteral 6 0029 MarkArguments 1 0030 SendMessage new 0031 AssignTemporary 1 0032 DoSpecial popTop "self do: ..." 0033 PushArgument 0 0034 PushBlock " " 0037 PushArgument 1 "criteria" 0038 PushTemporary 3 "x" 0039 PushTemporary 2 "mediane" 0040 MarkArguments 3 0041 SendMessage value:value: "  " 0042 DoSpecial branchIfFalse 52 "left add: x" 0045 PushTemporary 0 0046 PushTemporary 3 0047 MarkArguments 2 0048 SendMessage add: 0049 DoSpecial branch 56 "right add: x" 0052 PushTemporary 1 0053 PushTemporary 3 0054 MarkArguments 2 0055 SendMessage add: 0056 DoSpecial stackReturn 0057 MarkArguments 2 0058 SendMessage do: 0059 DoSpecial popTop "  left" 0060 PushTemporary 0 0061 PushArgument 1 0062 MarkArguments 2 0063 SendMessage sort: 0064 AssignTemporary 0 0065 DoSpecial popTop "  right" 0066 PushTemporary 1 0067 PushArgument 1 0068 MarkArguments 2 0069 SendMessage sort: 0070 AssignTemporary 1 0071 DoSpecial popTop "right add: mediane" 0072 PushTemporary 1 0073 PushTemporary 2 0074 MarkArguments 2 0075 SendMessage add: 0076 DoSpecial popTop "  " 0077 PushTemporary 0 0078 PushTemporary 1 0079 MarkArguments 2 0080 SendMessage appendList: " " 0081 DoSpecial stackReturn "( )" 0082 DoSpecial popTop 0083 DoSpecial selfReturn 


おわりに


...䞀般に、この蚘事でSmalltalk仮想マシンの内郚デバむスに぀いお説明したかったのはこれだけです。物語の圢匏には簡朔さが求められたすが、理解を損なうためにこれを行うべきではありたせん。私はなんずか劥協点を芋぀けるこずができたず思いたす。

Smalltalkむメヌゞでオブゞェクトがどのように芋えるか、数字がどのように衚珟されるか、メッセヌゞを送信しお適切なクラスを怜玢するアルゎリズムを理解し、コンテキストオブゞェクトずは䜕かを孊びたした。最埌に、仮想マシンの基本的な呜什に粟通し、既知の゜ヌト方法のコヌドず、コンパむラによっお倉換される呜什を調べたした。

では次の蚘事、Smalltalkメ゜ッドをLLVMが理解できる䞭間IRコヌドにコンパむルする際のJITの問題に぀いお説明したす。次に、実際のプロセッサ呜什にコンパむルされたす。メ゜ッドのバむトコヌドを分析し、それらを最適な方法でIRに倉換するために必芁なこずを理解しようずしたす。

最埌に、小さな調査

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


All Articles