アカウント名:
パスワード:
いつも思うんですがエミュって重いですよねLLVMみたいに動的コンパイルすれば超早くなるのでは?と妄想してしまいます
# 難しい(現実的に作るのは不可能に近い)のはわかってます
PCSX2のMIPSエミュレーションで利用されてるかどうかはしりませんが最近のエミュレーターはJIT使ってるのも結構あるみたいですね。
重いとっても最近のx86は速いので、昔のCPUのエミュレーションだけならそんなに重荷でもなくて、どちらかというとグラフィックスやサウンドといったサブシステムのエミュレーションのほうが荷が重いかな、というところじゃないでしょうか。
そうなんですか?コード部分が圧縮とか暗号化とかしてあってメモリ展開だとJITは不可能じゃないのか?と思ってました。(switchしか無理だろうなと)
訂正switchじゃなくて関数ポインタの配列ジャンプかな?BYTE nimoc[0xFF]={func_00, func_01, ...}みたいなswitchだと何倍も遅くなっちゃいますよね
コンパイラ次第ですが、switch文はcase値が連続値だと配列ジャンプにしてくれることが多いので、遅くなるとは限らないのです。また、関数ポインタだと関数呼び出し時にスタック操作が入りますから(リターンアドレスの格納、スタックフレーム絡みの処理など)、switchよりも不利になる部分もあります。
下でもそういったコメントがあってなるほど参考になります。__inline void func_0x00(DWORD param1...)みたいに__inlineつければswitchのほうが早くなるのかな??(まさかswitch内に処理べた書きではないでしょうから)# 個人的な趣味的には関数ポインタのほうが好きかな?^^;
そゆことです。
void (*func[])() = { func0, func1, func2, ...};(*func[index])(param);
みたいな関数ポインタコードの場合は、関数をインライン展開することは不可能ですが、
switch (index) {case 0: func0(param); break;case 1: func1(param); break;case 2: func2(param); break;…}
とした場合には、関数のインライン展開が可能になります。インライン展開されなかったとしても関数ポインタ配列と同等。
あとはコンパイラ次第ですが、今時のコンパイラなら、関数の宣言側でinline を付けなくても自動で最適化で勝手にインライン展開してくれたりしますな。
で、その結果として関数呼び出しがなくなった場合、・引数のスタックへのプッシュが不要・関数入り口でのレジスタ退避が不要になるほか、・変数のライフタイム管理がより正確に行えるようになるというメリットも。
例えば、エミュレータの場合なら、エミュ対象CPUのレジスタなどの状態情報を変数に保持することになるかと思いますが、そのデータ内容は呼び出した関数でも書き換えられる可能性があります。となると、呼び出し元では、関数呼び出し前に変数内容をメモリからレジスタに読み込んでいたとしても、関数呼び出し後にはレジスタ内容は捨てて再度メモリから変数内容を読み込み直す必要があります。それが、インライン展開されるなどして関数呼び出しがなくなってしまえば、「書き換えられていない」ことが確定しているデータについては、無駄なメモリアクセスをなくすことができます。
まあ、これは突き詰めて言えば「C言語のポインタは、最適化を妨げる諸悪の根源だ」なんて考え方にもなっちゃうんですけどね。
#私が昔Z80のエミュレータを書いた時は、そこまで性能を気にしなかったので、関数ポインタの配列を使ったかな。#オペコード1バイト目と、CB/EDのオペコード2バイト目の、計3テーブル。DD/FDはフラグを立てたんだっけか。#今にして思えば、オペコードを8進数3桁に分離してからswitch-caseを使った方が、シンプルで保守性のよいコードになっただろうな、と思う…
Z80というかDMG-CPUのエミュを書いたが、コードは手書きせずに生成プログラムを書いたほうがいろいろよかった
なるほど。目から鱗が落ちました。ここら辺の最適化はエミュの速度を大幅に決めてしまいますから重要ですよね。参考になります。
# コンパイラの最適化のわかりやすい一覧があるといいですよね。ブラックボックス化(わたしが無知なので)してしまっています。そういったページが見当たらないのが残念
より多くのコメントがこの議論にあるかもしれませんが、JavaScriptが有効ではない環境を使用している場合、クラシックなコメントシステム(D1)に設定を変更する必要があります。
ナニゲにアレゲなのは、ナニゲなアレゲ -- アレゲ研究家
エミュ (スコア:0)
いつも思うんですがエミュって重いですよね
LLVMみたいに動的コンパイルすれば超早くなるのでは?と妄想してしまいます
# 難しい(現実的に作るのは不可能に近い)のはわかってます
Re: (スコア:2)
PCSX2のMIPSエミュレーションで利用されてるかどうかはしりませんが最近の
エミュレーターはJIT使ってるのも結構あるみたいですね。
重いとっても最近のx86は速いので、昔のCPUのエミュレーションだけならそんなに重荷
でもなくて、どちらかというとグラフィックスやサウンドといったサブシステムの
エミュレーションのほうが荷が重いかな、というところじゃないでしょうか。
Re: (スコア:0)
そうなんですか?
コード部分が圧縮とか暗号化とかしてあってメモリ展開だとJITは不可能じゃないのか?と思ってました。(switchしか無理だろうなと)
Re: (スコア:0)
訂正
switchじゃなくて関数ポインタの配列ジャンプかな?
BYTE nimoc[0xFF]={func_00, func_01, ...}みたいな
switchだと何倍も遅くなっちゃいますよね
Re: (スコア:0)
コンパイラ次第ですが、switch文はcase値が連続値だと配列ジャンプにしてくれることが多いので、遅くなるとは限らないのです。
また、関数ポインタだと関数呼び出し時にスタック操作が入りますから(リターンアドレスの格納、スタックフレーム絡みの処理など)、switchよりも不利になる部分もあります。
Re: (スコア:0)
下でもそういったコメントがあってなるほど参考になります。
__inline void func_0x00(DWORD param1...)みたいに__inlineつければswitchのほうが早くなるのかな??(まさかswitch内に処理べた書きではないでしょうから)
# 個人的な趣味的には関数ポインタのほうが好きかな?^^;
Re:エミュ (スコア:1)
そゆことです。
みたいな関数ポインタコードの場合は、関数をインライン展開することは不可能ですが、
とした場合には、関数のインライン展開が可能になります。
インライン展開されなかったとしても関数ポインタ配列と同等。
あとはコンパイラ次第ですが、今時のコンパイラなら、関数の宣言側でinline を付けなくても
自動で最適化で勝手にインライン展開してくれたりしますな。
で、その結果として関数呼び出しがなくなった場合、
・引数のスタックへのプッシュが不要
・関数入り口でのレジスタ退避が不要
になるほか、
・変数のライフタイム管理がより正確に行えるようになる
というメリットも。
例えば、エミュレータの場合なら、エミュ対象CPUのレジスタなどの状態情報を変数に保持することになるかと思いますが、そのデータ内容は呼び出した関数でも書き換えられる可能性があります。
となると、呼び出し元では、関数呼び出し前に変数内容をメモリからレジスタに読み込んでいたとしても、関数呼び出し後にはレジスタ内容は捨てて再度メモリから変数内容を読み込み直す必要があります。
それが、インライン展開されるなどして関数呼び出しがなくなってしまえば、「書き換えられていない」ことが確定しているデータについては、無駄なメモリアクセスをなくすことができます。
まあ、これは突き詰めて言えば「C言語のポインタは、最適化を妨げる諸悪の根源だ」なんて考え方にもなっちゃうんですけどね。
#私が昔Z80のエミュレータを書いた時は、そこまで性能を気にしなかったので、関数ポインタの配列を使ったかな。
#オペコード1バイト目と、CB/EDのオペコード2バイト目の、計3テーブル。DD/FDはフラグを立てたんだっけか。
#今にして思えば、オペコードを8進数3桁に分離してからswitch-caseを使った方が、シンプルで保守性のよいコードになっただろうな、と思う…
Re: (スコア:0)
Z80というかDMG-CPUのエミュを書いたが、コードは手書きせずに生成プログラムを書いたほうがいろいろよかった
Re: (スコア:0)
なるほど。目から鱗が落ちました。
ここら辺の最適化はエミュの速度を大幅に決めてしまいますから重要ですよね。参考になります。
# コンパイラの最適化のわかりやすい一覧があるといいですよね。ブラックボックス化(わたしが無知なので)してしまっています。そういったページが見当たらないのが残念