時間があればちょっとずつ続けてます。

とりあえず画面がでるようにはなりました。速度は全然追いつかないですけど。

image

CPU

基本的には変化なし。細かいバグが多くて大変・・・・

わずかでも速くしたいところなので、あまり構造を壊さず速度を上げられないかな、と思ってPrefetch cueを実装してみました。Prefetch cueはCPUの非常に基本的な最適化で、基本的故に単純、実装しても負荷になることはないだろう、ってことですな。

エミュレーターでは(とくにPythonでは)ハードウェアでいうメモリが遅いうんぬんとは別の理由で、メモリアクセスはかなり重い処理になります。

1def memory(self, addr):
2  if addr < "RAMの範囲":
3    self.ram[addr]
4  elif addr < "メモリマップドIOの範囲":
5    self.io.read(addr)
6  #else:
7  # .
8  # .
9  # .

さらにページングされている場合、C言語ならポインタでいけるんですけど、Pythonではそうはいかないので、いちいち長ったらしく書くか、ページング用にメソッドを書いてそれをはさむことになります。

そう、関数の呼び出しが非常に多い部分なのです。実際、profileモジュールでデータを取ってみてもメモリ読み込み書き込みが結構なウェイトをしめてました。

そこでPrefetch用のクラスを作って、別スレッドで現在のPC+いくらかをとるようにしてみました。これは現在のオペコードを実行している間に実行されます。これで次のオペコードを実行する前にオペコードとオペランドを取得でき、メモリ読み込み関数を呼ぶ回数が減ります。ただし、本物のPrefetch cueと同じでジャンプ命令が多いとあんまり効果がありません。

Prefetch-cueを入れてみると、まぁコードの内容によりますが、psycoを入れて1frame 0.075くらいまではいきました。PPUをいれると全然なんですけど(笑

PPU

なんとか表示できるまでにきました。正直つらいです(笑 特にスクロールに関しては loopyの文書 の文書が重要、ということをしらなかったのではじめはサッパリでした。

描画部分は pygame です。pygameで描画する場合、更新したRectだけupdateするってのが常套手段なわけですが、エミュの場合はどうも・・・。というわけで今は毎回全体を描画しています。これも結構重い処理になるなあ。

ほとんどスクロールしないゲームの場合、もしかしたら32x32くらいに区切ってスプライトが動いたところだけ更新するようにしたら、結構軽いのかもなあ。


というわけで、PPUをつめてパッド入力あたりを書けばマッパー0のゲームなら動き出しそうな感じがします。ちなみにマッパーは全然です。とりあえずPythonで書いてみることが目的なので実際に使うことは想定して無いですし。

卒論の試問会も終わり、家探しの旅も終わり、つかの間の落ち着きが戻ってきました。なんか資格とかをとらないといけないらしいので、それをちょっとずつ勉強しつつ、こっちもちょっとずつ進めていきたいなーと思ってます。


そういえば、こういうサイトに定番っぽい開発環境を書いたのがないので、自己紹介?も兼ねて晒してみます。

開発はだいたいWindows上のvmware(Debian)でやってます。ただし、そこは定番どころばっかりなのでパス。まぁvim+screen+zshです。puttyでつないでますよ。sambaでマウントしてますよ。ええ。それぞれ結構設定しまくっていて、グローバルなSVNサーバーにおいてあります。

Windowsでの開発環境

あんまりWindowsは好きではないけど、Macよりは好きだったりする。というわけでWindows。普段使いのOSでもあるので、主に自分用の小物を書くことが多いです。怠け者なので、自動化できるものはすぐプログラムに置き換えちゃいます。でも言語は結構様々。もとから言語にこだわらず、一番適しているものを使う主義なので小物が多いにもかかわらず、言語は結構多いのかも。

今のところ、だいたい

  • それなりのGUIが必要なアプリ:Delphi6
  • 一枚ウインドウがあるくらいのGUIアプリ:Python(wxWidgets)
  • GUIがなく、立ち上がりの速さや軽さが欲しいもの:C(MinGW)
  • それ以外の小物:Python

という感じで適材適所。

Linuxではまったく定番な感じ(定番が自分にしっくりきた)だったけど、Windowsにはキーボード派の定番ってのは少ない気がするので、結構独特なのかも。方針は

  • キーボードで操作しやすいように。
  • なるべくlinuxと同じような感覚(←これ重要)で。

感覚なので一緒じゃなくてもいいのです。

gvim

開発はほとんどこいつです。Delphi以外は。Linuxのvimと同じ設定ファイルを共有してます。俺はvimがなくちゃ生きていけない人間なので、USBメモリにいれてもち運べるようにしてあります。

Firefox

ブラウザはこいつ。ただし PortableFirefox です。これもUSBメモリにいれて持ち運べるように。学校にいってもUSBメモリをさすだけで普段の環境。拡張も結構つっこんでます。

cltc

タスク切り替えが便利になるソフト。こんな感じで表示されます。インクリメンタル検索での選択や、カーソルキーでの選択ができます。

image

windowsでタスク切り替えというと`Alt+TAB`ですが、これ非常に使いづらい。そこでこのソフト。ここでキモになるのがキー設定で

  • Ctrl+Shift+z で起動
  • Ctrl+jCtrl+k でタスクを選択

という風に設定しています。俺と同じLinux開発環境の人は分かるでしょう(笑

Ctrl+z` がscreenのエスケープでjk`` はvim。これだけでかなりLinuxと同じ感覚でタスクが選べるようになります。

MigemizeExplorer

こりゃ定番。説明不要ですよねえ。超便利。

craftlaunch

これがないと始まらない。コマンド型ランチャー。craftlanuchラブ。こいつはデスクトップ用と持ち運び用(USBメモリに入れる用)の2個も用意してます。起動用ホットキーはeclipseなんてもんは使ってないので Ctrl+Space です。

俺の場合はほぼ全ての作業の起点がこのソフト。ランチャーとしてのソフト起動から簡易シェルとしてまで大活躍。中でもオススメなのはexplorer.exe(windows標準のシェル)+craftlaunchの連携。

craftlaunchというと「あふ」との連携が有名ですが、俺はあえてexplorer.exeと連携してます。というのも、なにぶん普段使いのOSですから、マルチメディアのファイルなんかも多いわけです。重くてもプレビューが見れたりするのは便利なもんです。日本語のファイル名だし、ファイラーつかってもファイル選択するのがめんどくさいもんです。

どうやって連携してるかというと、基本的に小物アプリ+ショートカットキー。この使い方をはじめるきっかけになったのは「せっかくcraftlaunchからキーボードだけでフォルダ開けるんだから、ホームポジションで楽にキーボードだけで閉じたいなあ」という思い。

ctrl+space,ctrl+[

の2ステップでウインドウが閉じられます。ctrlは押しっぱなしでいいので非常に軽快に閉じられます。鉄の小指を持つemacs使いの人ならなおのことでしょう(笑

その他の連携

フォルダの新規作成

windowのexplorerの不満はフォルダを作るのがめんどくさいことですよね。キーボードならAlt+F W F でしょうか。というわけでfiniのような自作コマンドで対応してます。超テキトーなのですが、あげておきます。

amkdir.zip

こいつを ctrl+nにショートカットとして割り当ててあります。すると・・・ craftlaunchでフォルダを開く→MigemizeExplorerで快適にフォルダをたどる→「フォルダつくりてえ」→ctrl+spaceフォルダ名入力ctrl+nという感じになるわけで、結構ハッピーです。

コンソール

おなじような感じで、表示されているフォルダをカレントディレクトリとしてcmd.exeを起動するものもつくってあります。ほとんどつかわないけど。


以上のような、変なWindowsで日々暮らしています。cltcでブラウザやvim、コンソールを行き来しながらIDEはあんまり使わないでガリガリ書いています。 まぁ、だいたいvmware上のLinuxにいるんですけど(笑 まとめると

  • cltcでctrl+zctrl+j,k 。快適ですよ。
  • MigemizeExplorerはWindows標準装備になればいいのに。
  • craftlaunchはあふと連携してもいいけど、explorerと連携してもハッピーですよ。

という感じです。


エミュレーターのほうはあのあとCPUを若干チューニングして、1フレーム0.1はキリました。今はPPUを書いてるんですが、そろそろ就職に備えて家を探さないといけません。京都と関東を行き来するのはいろいろこたえます。移動中はシグマリオンのPocketSchemeでSchemeでも書いて暇つぶしです。


実は既に結構挫折気味。

やっぱりPythonではちょっと厳しいかもしれない。

とりあえず一番ややこしいPPU周りの情報を調べて、ちょろちょろ書き始めたあたりでいったんCPU部分のパフォーマンスを調べてみました。

かなり厳しいものがあります。CPU部分のコードはPython的な書き方で書いてたんですが、これじゃ話にならない。まず、アクセサなんてものはつかっちゃいけないのだ。

以下環境はOS:WinXP,CPU:Athlon64 3000+,Memory:1G,Python2.4です。

CPUのレジスタ関連の実装

NESのCPUである6502のレジスタはPCは16bit、それ以外は8bit。8bitの値なんてものはCならunsigned charで一発なんだけど、Pythonにはそんなものない。足し算したらどんどん大きくなるし、引き算したらどんどん小さくなる。ので

1class Py6502(object):
2  def __init__(self):
3    self.A = 0
4    self.X = 0
5    self.Y = 0
6    self.PC = 0
7    #  .
8    #  .
9    #  .

見たいなクラスにするとして、足し算するときなどは必ず self.A = (self.A + x) & 0xff みたいにして8bitに収めないといけない。ここで

 1def _create_n_bit_property(name, mask):
 2  rname = "_"+name
 3  result = {
 4    name:property(lambda self: getattr(self, rname),
 5                  lambda self,v : setattr(self, rname, v & mask),
 6                  )
 7  }
 8  return result
 9
10class ForceNbitType(type):
11  def __new__(cls, class_name, class_bases, classdict):
12    names = classdict.get("__16bit__")
13    for name in names:
14      classdict.update(_create_n_bit_property(name, 0xffff))
15    names = classdict.get("__8bit__")
16    for name in names:
17      classdict.update(_create_n_bit_property(name, 0xff))
18    cls = type.__new__(cls, class_name, class_bases, classdict)
19    return cls

のようなmetaclassをつくって

 1class Py6502(object):
 2  __metaclass__ = ForceNbitType
 3  __8bit__ : "A", "X", "Y"
 4  __16bit__ : "PC",
 5  def __init__(self):
 6    self._A = 0
 7    self._X = 0
 8    self._Y = 0
 9    self._PC = 0
10    #  .
11    #  .
12    #  .

とすれば self.A += 10 とかしてもちゃんと8bitに収まる。

非常に上手くかけるんですが、こんなのやってらんない。遅すぎる。1Frame分(28000サイクル程度)の実行に0.5秒(笑

getattr

getattr は遅い。 getattrの真価は第3引数でdefault値が指定できること(だと思う)。今回のように確実に属性が存在することが分かっているならself.__getattribute__(name)を使うと結構違ってくる。ちなみにPythonのソースではこんな感じ。

 1static PyObject *
 2slot_tp_getattro(PyObject *self, PyObject *name)
 3{
 4    static PyObject *getattribute_str = NULL;
 5    return call_method(self, "__getattribute__", &getattribute_str,
 6                       "(O)", name);
 7}
 8
 9static PyObject *
10slot_tp_getattr_hook(PyObject *self, PyObject *name)
11{
12    PyTypeObject *tp = self->ob_type;
13    PyObject *getattr, *getattribute, *res;
14    static PyObject *getattribute_str = NULL;
15    static PyObject *getattr_str = NULL;
16
17    if (getattr_str == NULL) {
18            getattr_str = PyString_InternFromString("__getattr__");
19            if (getattr_str == NULL)
20                    return NULL;
21    }
22    if (getattribute_str == NULL) {
23            getattribute_str =
24                    PyString_InternFromString("__getattribute__");
25            if (getattribute_str == NULL)
26                    return NULL;
27    }
28    getattr = _PyType_Lookup(tp, getattr_str);
29    if (getattr == NULL) {
30            /* No __getattr__ hook: use a simpler dispatcher */
31            tp->tp_getattro = slot_tp_getattro;
32            return slot_tp_getattro(self, name);
33    }
34    getattribute = _PyType_Lookup(tp, getattribute_str);
35    if (getattribute == NULL ||
36        (getattribute->ob_type == &PyWrapperDescr_Type &&
37         ((PyWrapperDescrObject *)getattribute)->d_wrapped ==
38         (void *)PyObject_GenericGetAttr))
39            res = PyObject_GenericGetAttr(self, name);
40    else
41            res = PyObject_CallFunction(getattribute, "OO", self, name);
42    if (res == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
43            PyErr_Clear();
44            res = PyObject_CallFunction(getattr, "OO", self, name);
45    }
46    return res;
47}

上が __getattribute__ 、下が getattr 。ま、こんなことやったって関数呼び出しのオーバーヘッドが一番ツライのでgetterは使わず、setterは必要があるときだけ使うように。これで結構はやくなる。

実行ループ

CPUのエミュレータなんてのはだいたい同じようなパターンで割り込みを除いて簡単に書くと

1def step_execute(self, clocks):
2  while (self.passed_clocks < clocks):
3    opecode = read()# opecodeを取得
4    # 実行
5    count = CLOCK[opecode] # 実行に必要なクロック数を取得
6    self.passed_clocks += count
7  self.passed_clocks -= clocks

なんてのになるわけで、ここが激しくループするわけで。ここはガリガリにちょっとでも節約できるものは節約。

 1def step_execute(self, clocks):
 2  read = self.memory.read
 3  get_method_by_opecode = self.get_method_by_opecode
 4  while (self.passed_clocks < clocks):
 5    old = self._PC; self._PC += 1; self._PC &= 0xffff
 6    opecode = read(old)
 7    method = get_method_by_opecode(opecode)
 8    count = method()
 9    count = count != None and count or CYCLES[opecode]
10    self.passed_clocks += count
11  self.passed_clocks -= clocks

javascriptで . (ドット)演算が遅いとかいうのは最近(?)よく言われていることで、それはPythonにも当然当てはまる。なのでループ前にだせるものはローカルに出しておく。(これが簡単にできるのがRubyよりもPythonがいい部分だよなあ)

あと、実行の部分。ここはCなら関数テーブルかswitch(コンパイラによるけど大差ないと思う)になるんだけど、あいにくPythonにはswitchがないので関数テーブル的なものか if opecode == 0x01: ... elif opecode == 0x02:... elif... かになる。

関数テーブル的なものは

1def ope_0x01(self):
2  #code
3def ope_0x02(self):
4  #code
5
6self.__getattribute__("ope_"+hex(opecode))()

となる。両方やってみたところ、大差はなかったので、関数テーブル的なほうに。

結局

そんな感じでいじってみてかつpsycoを入れて1Frame:0.15程度(PPUやAPUは中身がないので、最後までつくったらもっと遅くなる)。ほかにもいじれそうなところはあって、そこをいじれば0.1は切れそうな感じがしてます。

けど、そこまでやるとPythonである意味がないのも確か。ぶっちゃけ、エミュレータは確実にCが向いている。Javaで書かれているエミュレータもレジスタの値をセットするたびに A & 0xff みたいなことをやっていて、どーもめんどくさい。

じゃぁLLでエミュレーターを書く意味ってなんだ、というと・・・うーん(笑 自己満足以外なにもないでしょうねえ。というわけで自己満足のために、今後もヒマができれば、ちょっとづつ書き進めてみようかなあ。