ホーム   最近   SikiWiki   編集   新規 

[SikiLanguage] [式鬼言語航海日誌]

2008-08-25

2008/08/26_021420




もうちょっとだけ『駆動』の続き。

「まだまだ修正入れてるだろ。大丈夫なのかね?」

オーライ、オーライ。ちょこちょこ調整して何となく良い感じになってきたので、たぶん大丈夫でしょう。

「で、どの辺を調整してるんかね?」

大きなところとしては

  • 実装の単純化のために、実装言語側の(暗黙の)駆動をどの程度活用するか?
  • 実行効率を上げるために、どの程度の効率化(複雑化)を容認するか?

の二点ですね。

「確かに悩ましいところだね。本来なら構成を簡単にすることを優先したいところだけど、そのせいで実装言語に依存したり、あるいは効率を犠牲にしたりするのは納得いかないしね」

ただ、まだ悩んでいる部分もありますので、今日は簡単にアイディアの説明だけで。

まずはこちらをご覧下さい。CellがCommandとして実行されるときの挙動を指定するCore::executeのC++での宣言です。

void Core::execute(Archive& archive, Cell* machine, Cell* invoker, Cell* command);

命令としてCellを実行するときは、このexecuteを実行して処理を行います。ご覧の通り、この関数には4つの引数が存在します。

「関数を呼び出すとスタックフレームを拵える……スタックフレームには実引数が保存されるから……実行に必要なCellの参照を記憶するにC++側のスタックフレームを活用している、つうわけだな」

そうです。上で言っている『実装言語側の(暗黙の)駆動を活用する』ということですね。archiveは単なる便利変数なので、実際には machine、invoker、commandの3つを記憶するのにC++側スタックフレームを活用しています。

これらのCellはCellを実行するときに重要な役割を果たすCellです。それぞれ下記のような意味を持ちます。

  • command: 処理を実行するCell。通常はCore(executeメソッド)を所有するCell。
  • invoker: commandを呼び出して実行したCell。<Stack>という引数渡し/戻り値受取用のCellを持つ。通常はYarn、Thread、Fiberのいずれか。
  • machine: 式鬼言語側のスタックフレーム(List)を管理するCell。通常はYarnかThread。

特に重要なのがmachineですね。このCellがスタックフレームを管理することで、実行時の効率化とか柔軟性を高めています。

真面目に式鬼言語のスタックフレームを実装するのならば、全てのcommandを実行する前に(machineの)スタックフレームに新しく実行フレームをこしらえて必要な情報を記憶してから実行する必要があるのですが…………そんなことしなくてもC++側のスタックフレームに必要なパーツは揃っていますよね?

ということで、式鬼言語側の実行が破綻しない場合は、式鬼言語側のスタックフレームを更新せずにC++側ので処理を終了するようにしました。

「破綻させないための条件は面倒臭そうだね」

まあ、厳密に考えはじめるとキリがありませんが、まずは妥協点として『commandから別のcommandを呼び出さない。別のcommandを呼び出す時は、式鬼言語側のスタックフレームを更新してから呼び出す』としました。大雑把に言えば、Yarn、Thread、Fiber、ついでにFilamentは他のcommandを呼び出す前にmachineのListを更新しろ、ということですね。

「なんか破綻する条件がありそうな……」

ですね。Yarn、Thread、Fiber(、Filament)を大量に連続的に呼び出すような場合はC++のスタックを食い尽してエラーになります。……まあ、そんな状態は特殊ケースということでまずは忘れましょう。そのあたりまで厳密に対処しようとすると効率がずいぶんと悪化しそうな感じなんですよね。

「『早過ぎる最適化』じゃないのかね?」

かもしれませんが、そんなにややこしくなっていないから大丈夫でしょう。

「中途半端にスタックフレームを分担しているからややこしいのかね。いっそのことC++のスタックフレームに全面依存することにしたら……」

……そうすると、今度は効率がかなり悪化しそうなんですよね。特に再帰的にFiberを呼び出すような状況は最悪です。

これは実行中のCellの構造を見たほうがいいでしょう。

  • スタックフレームをC++に依存する
    • Yarn List:
      • [FiberN List:
        • …………
            • [Fiber1 List:
              • [Fiber0 List:
      • ]……N個……]
  • YarnのListをスタックフレームとして活用
    • Yarn List:
      • [FiberN …… Fiber1 Fiber0]

ということで、全てC++に頼った場合、YarnからFiber0を実行するにはFiberN……Fiber1を全て実行する必要があります。YarnのListをスタックフレームとして活用した場合は一発でFiber0を実行することができるのと比べると、ずいぶんとムダですよね。

「むむむ、……そうなると効率もずいぶん違いそうだね」

ええ。そういうことで落とし所として式鬼言語側にもスタックフレームを用意して、C++側のスタックフレームも活用する方法で実装しています。

「ちょっとややこしくなっているけど、……まあ、この程度なら許容範囲か」

まあ、ネイティブコードを考え始めたら結局全部やらなきゃいけなくなりますが、そうなるのはしばらく後になりそうですからね。





制作・著作: 野分(nowake) at fiercewinds.net (Creative Commons 表示-継承 2.1 日本)