世界を動かす技術を、日本語で。

エミュレーターのセーブステートを利用した「スーパーマリオブラザーズ」内のトイRTOS

概要

  • Super Mario Bros. のNESエミュレータ上で マルチスレッド を実装した事例
  • スレッド の概念を セーブステート状態管理 で表現
  • 同期プリミティブ (ミューテックス、条件変数など)をゲーム内で可視化
  • 教育的価値抽象層の理解 の重要性を強調
  • FCEUXエミュレータLuaプラグイン による実装詳細

スーパーマリオブラザーズで学ぶスレッドの仕組み

  • NESエミュレータで Super Mario Bros. を利用した マルチスレッド化 の試み
  • 3つの セーブステート を作成し、それぞれが独立した スレッド として機能
  • スレッドスケジューラ が160フレームごとにアクティブなスレッドを切り替え
  • 各スレッドには 異なるカラーパレット を割り当て、視覚的に区別
  • 実際には 1つのゲームインスタンス がアクティブだが、複数が同時進行しているように見せる構成

ゲーム内で体験できる同期プリミティブ

  • 特定エリア でスレッド制御を体験可能な設計
    • 赤色エリア: 割り込み禁止 領域(他のスレッドは実行不可)
    • 黄色エリア: ミューテックス (パイプ内は1スレッドのみ進入可)
    • 緑色エリア: 条件変数 (全スレッドがフラッグポールに到達するまで進行停止)
  • 敵を倒す とスレッドが300フレーム スリープ 状態になる仕様
  • 同期の可視化 によって、抽象的な概念を直感的に理解可能

スレッド管理の技術的アプローチ

  • FCEUXエミュレータLuaプラグイン 機能を活用
    • savestate.create()/savestate.save(): スレッド状態の保存
    • savestate.load(): スレッド状態の復元
    • memory.readbyte(): ゲームRAMから情報取得
    • gui.drawtext(): 画面への情報表示
    • emu.frameadvance(): フレーム進行の制御
  • スレッドスケジューラ の実装で、プリエンプティブなマルチタスクを疑似体験
  • ゲーム開始を メモリアドレス の値で検知し、スレッド管理を開始

抽象層・基礎技術の理解の重要性

  • 抽象化の進んだ現代開発 では、基礎層の理解が薄れがち
  • 土台(基礎技術) の理解が、新たな技術評価や問題解決に不可欠
  • スレッドの基本原理自体は シンプル で、複雑なのは効率化や堅牢化の部分
  • 自作経験 を通じて、他者の実装や新技術の評価能力が向上
  • 学習のモチベーション として、「誰も知らないこと」の理解に価値があると主張

実装例と学び

  • 数百行のLuaコード で、スレッドスケジューラと同期プリミティブを実装
  • オープンソースエミュレータ のFCEUXと、その Luaプラグインシステム を利用
  • 実装例 を読むことで、スレッドや同期の動作を実感できる
  • シンプルな実装 でも、概念の理解や改善点の発見につながる
  • 他者の優れた実装 と比較する経験が、より深い理解を促進

まとめ・教育的意義

  • スレッド同期プリミティブ は体験的に学ぶことで理解が深まる
  • 抽象層の内部 を覗くことで、技術の「なぜ?」に答えられる力が身につく
  • フロントエンドフレームワーク なども、自作経験が概念理解を促進
  • 基礎技術の学び が、ソフトウェア開発のあらゆるレイヤーで役立つ知見となる

追記

  • 本記事はもともと比喩として始まったが、実際に動作する「おもちゃRTOS」として完成
  • 未改造のNESエミュレータ(FCEUX) をCPUとして利用
  • セーブステート をスレッドコンテキストとして活用
  • ミューテックス・割り込みマスク・条件変数 などの基本機能を実装
  • 完全なマルチスレッドとは異なるが、 教育的にユニークな実例 として価値あり

Hackerたちの意見

これはRTOS/スケジューリングのめっちゃクールなビジュアルデモだね!地域ベースのクリティカルセクションが大好き!大学でリアルタイムオペレーティングシステムの授業を選択科目で受けたんだけど、4年間で一番難しかった授業の一つだったけど、めっちゃ面白かった。素晴らしい教授がいて、すごく要求が厳しいけど、すごく勉強になるプロジェクトベースの課題を出してくれた。もう一度この分野で遊ぶためのトイプロジェクトを見つけなきゃ。

ありがとう!この具体的な例が君の授業中に提供されたら、どれくらい効果的だったと思う?すごく役立つ教育ツールを見つけた気がするけど、大学に行ってないから、実際にどう教えられているのかはわからないんだよね :v

昨晩、エミュレーターのラグをなくす方法についての記事を読んだんだ。似たようなコンセプトでやってるよ。基本的には、各フレームごとにエミュレーターが異なるボタンの組み合わせの状態を計算するんだ。それから、実際に押したボタンに基づいて、表示される状態が事前に計算されたものに移るんだ。

これが何か正確には100%理解してるわけじゃないけど、ラグをなくすためのGGPOと基本的に同じアイデアのランアヘッドには詳しいよ: - エミュレーターを通常通り1フレーム実行して、リアルなポーリング入力を使う。で、状態をスナップショットする。 - その後、同じ入力でエミュレーターをnフレーム(通常は1フレームだけ)実行する。最後のフレームの動画と音声を表示する。 - 同期する。(一部のエミュレーターはかなり高度なことができて、単にvsyncを待つだけじゃなく、処理時間を見積もってウィンドウの終わりまで遅延させて、最後の瞬間に入力をポーリングすることもある。) - 保存したスナップショットにロールバックする。(入力が本当に変わっていないとわかっているなら、ロールバックを避けるために最適化できると思うけど、フレーム時間が予測しづらくなるかも。)このアイデアが良い理由は、ほとんどのゲームが設計上、ある程度の処理遅延を持っているからで、フレームを1つか2つ先に進めても目立った副作用がないことが多いから。現代のコンピュータはLCDスクリーンで、古いシンプルなマシンに比べてどこでも遅延が多いから、これは結構クールなアイデアだよね。残念ながら、このアプローチはエミュレーターの状態が小さくて速く復元できるときだけ有効なんだ。実は、もっと現代のコンソール向けにこれを実現できるか実験してみたいと思ってる。例えば、userfaultfd/MEM_WRITE_WATCHを使ってダーティメモリページを追跡したり、全体のキャッシュを落とさずに巻き戻しできるようにJITキャッシュのような構造を設計したりできるかも。すべてのエミュレーターが状態を読み込むときにキャッシュをクリアするかはわからないけど、一般的には、もしそれを最初から設計するなら、セーブステートをどれくらい速く小さくできるか知りたいな。

これは、リモートプレイを追加するエミュレーターがネットワークラグに対処する逆のような気がする…彼らは、現在のフレームに対して相手がどんな操作をするかを予測して表示するけど、実際に相手の操作を何フレームか後に受け取ったとき、そのフレームに戻って、実際の操作入力を知った上で再計算して、現在のフレームに追いつくんだ。こうすることで、ゲームはリモート入力を待って画面を更新することなく、すべての情報がわかった後に調整できるんだ。

ハードウェアのための投機的実行に似てるね。

うーん、すごいね。これは全ての現代のエミュレーターで使われてる技術なの?それとも最近のもの?

面白いね;これはどのエミュレーターのためのものだったの?この戦略は、ある程度の複雑さのレベル以下では主に効果的だと思うけど、現代に近づくほど、フレームを描画するのも運が良くないとできないよね。

素晴らしいビジュアルデモだね。概念を理解するのにすごく役立つ。

それは面白いね。スレッドに関してよく使うメタファーは、組み立てる必要があるいくつかのウィジェットを持っていることなんだ。ウィジェットBに取り掛かるには、ウィジェットAの作業を一時停止しなきゃいけない。SMPを追加するのは、ウィジェットを組み立てる手伝いをするために2人目を加えるようなもので、でも同じ道具箱を使っているから、もし2人とも同じドライバーが必要なら、2人目を加えてもパフォーマンスのメリットはない。マルチコアは、それぞれ自分の道具箱を持った複数の作業台を持っていて、完全に並行して作業できることだね。ミューテックスは、同時に1つのウィジェットの組み立てだけに使える専門的な道具のようなもの。各作業台には、ドライバー、ハンマー、レンチ、ソケットなどのフルセットがあるかもしれないけど、溶接機は1つだけなんだ。セマフォは、最大4つのウィジェットが入るオーブンのように、限られた数のウィジェットが使える道具だよ。

特別なチャレンジとして:Haskellで実装されているような(ソフトウェア)トランザクショナルメモリを君のメタファーで説明してみて。STMは、いくつかのデータベースがトランザクションを実装する方法にかなり似てるよ。

SMPを追加するのは、ウィジェットを組み立てるのを手伝うためにもう一人を加えるようなもので、でも同じ工具箱を使ってるから、二人とも同じドライバーが必要なら、二人目を加えてもパフォーマンスの向上はないよね。これってハイパースレッディングに似てる気がするけど、もしかしたらメタファーの中でドライバーを見逃してるのかな :)

これって、これらの概念を紹介するのにかなりいいメタファーだと思う。特に、学ぶ側がその知識をすぐに活用する必要がある状況ではね。私がこの投稿を書くきっかけになったのは(大学に行かなかったから、これから言うことは大きな塩を振って聞いてほしいけど)、状況に追い込まれてファームウェアエンジニアになったことで、マルチスレッドをうまく使うだけじゃなくて、そもそもこれが何なのかを理解できたからなんだ。あと、Redditのコメントを見すぎたせいで(これが間違い#1)、こういうことを知っていることに何のメリットもないっていう考えを軽視する意見が多かったのが気になった。とはいえ、このブログ投稿やメタファーは、すでにスレッドを効果的に使っている人向けだと思うから、初めての紹介としてはあまり良くないかもね。

SMPはセカンドプロセッサで、セカンドコアよりもさらに冗長なんだ。独自のキャッシュ、メモリ、ノースブリッジアクセスなどがあるよ。

https://web.archive.org/web/20250528203416/https://prettygoo... https://archive.ph/msHkO

これは本当に素晴らしい仕事だね、脱帽だよ。エミュレーターのスレッド処理だけじゃなくて(それ自体が天才的なアイデアだし)、記事自体にも感謝!自分も何年もかけて、君のポイントをうまく伝える方法を探してたんだ。みんなにもっとシステムについて学んでもらいたくてね。これからはこのリンクを送ることにするよ。ありがとう!

私の人だ!神秘化のセクションで高飛車なクソ野郎に見えるんじゃないかと心配してたけど、それは私の意図や態度とは真逆なんだ。別の人に響いたと聞いて嬉しいよ。ただ、私たち二人とも高飛車なクソ野郎かもしれないけどね。

これってコルーチンやグリーンスレッドに近いんじゃない?協調スケジューリングとプリエンプティブスケジューリングの違いを除けば、基本的には同じ概念だと思うけど。

実際のところは、あなたが説明していることに近いと思うけど、直感的に理解できるのはスレッドそのものだと思う。そうであればいいな。

Tsodingが最近、Cのためにコルーチンをゼロから作ったんだって。コルーチンの概念はスレッドに関連してるから、もし記事が気に入ったなら動画も楽しめると思うよ。 https://youtu.be/sYSP_elDdZw

ちょっと細かいことを言ってごめんね。あなたの前の投稿から引用すると、> スレッドについて理解すべき最初のことは、プロセッサがそれをサポートしていないということです。確かにそうだけど、外の世界でエミュレーターを変更することで(他のソフトウェアでエミュレーターソフトを拡張するのはいいけど)、内側の世界で何かをするのではなく、ハードウェアの並行処理サポートやプロセスに近いものを実装していると言えると思う。> 真のマルチスレッドに1:1でマッピングされるわけではない(例えば、エミュレーターのセーブステートはRAMを含むマシン全体の状態を表すが、スレッドコンテキストはもっとミニマルな部分を表す)。そうだね、でもスレッドとプロセスの違いを考えるなら(一般的な並行処理に対して)、コミュニケーションチャネルや同期プリミティブ(ミューテックスやバリア)が少ししかない場合と、広く開かれたもの(共有メモリ、注意が必要!)がある場合が重要な違いだと思う。