Cコードを動的に生成し、実行中のCプログラムにすばやく再ロードできるようにしたいと考えています。
私はLinuxを使用していますが、これをどのように行うことができますか?
Linux上のライブラリ.soファイルを実行時に再コンパイルして再ロードできますか?
.soファイルを生成せずにコンパイルできますか?コンパイルされた出力はどういうわけかメモリに移動してから再ロードできますか?コンパイルしたコードをすばやくリロードしたい。
あなたがやりたいことは合理的であり、私はまさにそれをMELT(GCCを拡張するための高レベルのドメイン固有言語。MELTはMELTで書かれたトランスレーター自体を介してCにコンパイルされます)で行っています。
まず、Cコード(または他の多くのソース言語)を生成するときは、ある種の抽象構文木(AST)をメモリに保持することをお勧めします。したがって、最初に生成されたCコードのAST全体を構築し、次にそれをC構文として出力します。明示的なASTなしでコード生成フレームワークを考えないでください(言い換えると、printfの束を使用したCコードの生成はメンテナンスの悪夢であり、中間表現が必要です)。
次に、Cコードを生成する主な理由は、優れた最適化コンパイラーを利用することです(もう1つの理由は、Cの移植性と遍在性です)。生成されたコードのパフォーマンスを気にしない場合(そしてTCCがCを非常に素朴で遅いマシンコードに非常に高速にコンパイルする)、他のアプローチを使用できます。たとえば、Gnu lightning(低速マシンの非常に高速な生成)などのJITライブラリを使用できます。 code)、Gnu LibjitまたはASMJIT(生成されたマシンコードは少し優れています)、LLVMまたはGCCJIT(生成された優れたマシンコードですが、生成時間はコンパイラーに匹敵します)。
したがって、Cコードを生成し、それを迅速に実行したい場合、Cコードのコンパイル時間は無視できません(生成されたものから共有オブジェクトを作成するコマンドをフォークする可能性がある ためgcc -O -fPIC -shared
)。経験上、Cコードの生成は、(を使用して)コンパイルするよりもはるかに短時間で済みます。MELTでは、Cコードの生成はGCCによるコンパイルよりも10倍以上高速です(通常は30倍高速です)。しかし、Cコンパイラによって行われる最適化はそれだけの価値があります。foo.so
foo.c
gcc -O
Cコードを発行し、そのコンパイルを.so
共有オブジェクトにフォークすると、それが可能dlopen
になります。恥ずかしがらないでください。私のmanydl.cの例は、Linuxで大量の共有オブジェクト(数十万)をdlopenできることを示しています。本当のボトルネックは、生成されたCコードのコンパイルです。実際には、Linuxで実際に実行する必要はありませんdlclose
(数か月間実行する必要のあるサーバープログラムをコーディングしている場合を除く)。未使用の共有モジュールは実質的に使用されたままになる可能性があり、未使用のほとんどがスワップアウトされるdlopen
ため、ほとんどの場合、プロセスのアドレス空間(安価なリソース)がリークしています。は迅速に実行されますが、Cコンパイラで最適化を実行する必要があるため、時間がかかるのはCソースのコンパイルです。.so
dlopen
他の多くの異なるアプローチを使用することができます。たとえば、バイトコードインタープリターを使用してそのバイトコードを生成し、Common Lisp(たとえば、マシンコードに動的にコンパイルされるLinux上のSBCL)、LuaJit、Java、MetaOcamlなどを使用します。
他の人が示唆しているように、Cファイルを書き込む時間についてはあまり気にせず、実際にはファイルシステムのキャッシュに残ります(これも参照してください)。そして、それを書くことはそれをコンパイルするよりもはるかに速いので、メモリにとどまるのは問題の価値がありません。I / O時間が気になる場合は、いくつかのtmpfsを使用してください。
あなたは尋ねました
.so
Linux上のライブラリファイルを実行時に再コンパイルして再ロードできますか?
もちろんそうです:生成されたCコードからライブラリを構築するコマンドをフォークする必要があります(たとえばgcc -O -fPIC -shared generated.c -o generated.so
、。ファイル!)次に、dlopenを使用してライブラリを動的にロードし(のようなフルパスと、おそらくフラグを指定します)、内部の関連するシンボルを見つけるために使用する必要があります。同じように(2回目)再ロードすることは考えないでください。一意の(その後など)Cファイルを出力してから、それを一意の(2回目など)にコンパイルしてからコンパイルすることをお勧めします。にmake -j
generated.so
generated.c
/some/file/path/to/generated.so
RTLD_NOW
dlsym
generated.so
generated1.c
generated2.c
generated1.so
generated2.so
dlopen
それ(そしてこれは何十万回も行うことができます)。放出されたgenerated*.c
ファイルに、その時に実行されるいくつかのコンストラクター関数が必要な場合があります。dlopen
generated*.so
基本アプリケーションプログラムは、一連のdlsym -ed名(通常は関数)とそれらの呼び出し方法に関する規則を定義している必要があります。generated*.so
スルーされたdlsym
関数ポインタ内の関数のみを直接呼び出す必要があります。実際には、たとえば、それぞれgenerated*.c
が関数を定義し、void dynfoo(int)
およびint dynbar(int,int)
で使用dlsym
し"dynfoo"
、"dynbar"
これらのスルー関数ポインタ(によって返されるdlsym
)を呼び出すことを決定します。dynfoo
また、これらがいつどのようdynbar
に呼び出されるかについての規則を定義する必要があります。ファイルがアプリケーション関数を呼び出すことができる-rdynamic
ように、ベースアプリケーションをとより適切にリンクします。generated*.c
既存の名前を再定義する必要はありません。たとえば、を再定義して、すべてのヒープ割り当て関数が新しいバリアントを魔法のように使用することを期待したくない場合(おそらく機能しません。機能したとしても、危険です)。generated*.so
malloc
generated*.c
アプリケーションのクリーンアップ時と終了時を除いて、動的にロードされた共有オブジェクトに煩わされることはおそらくないでしょうdlclose
(ただし、私はまったく気にしませんdlclose
)。dlclose
動的にロードされるファイルを作成する場合はgenerated*.so
、そのファイルで何も使用されていないことを確認してください。ポインターはなく、呼び出しフレーム内のアドレスを返すこともありません。
PS MELTトランスレータは現在57KLOCのMELTコードであり、ほぼ1770KLOCのCコードに変換されています。
おそらくTCCコンパイラが最善の策です。これにより、ファイルに触れることなく、ソースコードをコンパイルし、プログラムに追加して実行することができます。
より堅牢であるがCベースではないソリューションについては、おそらくLLVMプロジェクトをチェックする必要があります。これはほとんど同じことを行いますが、JITの作成の観点からです。一種の抽象的なポータブルマシンコードを使用する代わりに、Cを経由することはできませんが、生成されたコードはより高速にロードされ、より活発に開発されています。
OTOHは、gccにシェルアウトし、コンパイルしてから自分でロードすることにより、すべて手動で実行したい場合は、必要な.so
処理を実行します。dlopen()
dlclose()
ここでCが正しい答えであると確信していますか?Lua、Bigloo Scheme、あるいはPythonでさえ、既存のCアプリケーションに非常にうまく組み込まれているさまざまなインタプリタ言語があります。拡張言語を使用して動的パーツを記述できます。拡張言語は、実行時のコードの再ロードをサポートします。
明らかな欠点はパフォーマンスです。コンパイルされたCの生の速度が絶対に必要な場合、これらは失敗する可能性があります。
ライブラリを動的にリロードする場合は、dlopen
関数を使用できます(mansを参照)。ライブラリの.soファイルを開き、そのファイルへのvoid *ポインタを返します。次に、。を使用してライブラリの任意の関数/変数へのポインタを取得できますdlsym
。
ライブラリをメモリ内でコンパイルするには、ここで説明するようにメモリファイルシステムを作成するのが最善だと思います。