MJHD

エモさ駆動開発

2022年前半期(1月~7月)の振り返り

前回

mjhd.hatenablog.com

さて、2021年の年末に振り返りと抱負を書いてから早いものでもう年の半分が過ぎてしまったようです。 前半を振り返ってみたいと思います。

何をやるべきだったか?

抱負に書いた「今後何をやるべきか?」についておさらいしておきましょう。

均し

一番大きな抱負が、「均し」でした。今までのハードワーク燃え尽きサイクルからの脱却を目指し、コンスタントにパフォーマンスを出していきたいという目標でした。

低レイヤー

CPU層以降の低レイヤーについて学びを深めたい、例えばカーネルやドライバ、OSレイヤーなどについて学びたいというのが目標でした。

今のところの結果

均し

7月にいろいろアウトプットを重ねすぎて、少しバーンアウトしてしまいました。

まずは記事をたくさん書きました。

Flutter前史: ChromeがFlutterになるまで

PlayStationエミュレータ作りに取り組んだ

FlutterのRaster Cacheを追ってみる

Flutterの課題、Early-onset jankとは何か

それぞれ、たくさんの「いいね」や「はてブ」を頂き、書いてよかったなと思っています。

登壇もしました。

Flutterリプレースの難易度と取り組んだこと | CA BASE NEXT - CyberAgent Developer Conference by Next Generations

こちらも好評で、久しぶりの登壇でしたが良い経験になりました。

算数で読み解く コンピュータのしくみ | 馬場 敬信 | コンピュータ・IT | Kindleストア | Amazon

こちらは私が書いたわけではありませんが、私の開発するプログラムを解説に使っていただきました。(ありがとうございます)

と、いろいろなアウトプットができて個人的にも上半期を締めくくる良い月だったのですが、少し本業の方がバーンアウト気味でした…。チームメンバーの皆さん、ごめんなさい。

ここから得られる反省として、「計画的にアウトプットをするべき」と思いました。

上記アウトプットは、ほとんど計画的というよりは勢いや流れに身を任せて書いたものです。

そうではなく、「10月にアウトプットをしたい、そのために着実に文章をためていく」などのゴール設定と進捗管理をすべきだと考えています。

将来的には技術書店などである程度まとまった文章を世に出したい、という目標があり、そのためにもゴール設定と進捗管理は今後の課題になってきそうだなと感じています。

また、ゴール設定をすることで、今まではオンデマンドにアウトプットできる媒体(zennや開催頻度の高いのイベントなど)に出すことが多かったですが、より大きなイベントを狙うこともできるのではないかと思います。

低レイヤー

こちらについてはほぼ手付かずといった様子です。

一応、PlayStationエミュレータの開発やZig言語を使ったCHIP-8エミュレータの開発など低レイヤー寄りのことはしていますが、今までの延長であり、カーネルやドライバ、OS周りのような上のレイヤーについては学べていませんでした。

下半期ではOS自作本をやります…!

その他取り組んだこと

お勉強

統計学

放送大学で授業を取りました。(大学時代も統計は取っていたので、学び直しです)

平均や分散の話から、2項分布・正規分布ポアソン分布、信頼区間や各種検定の話、単回帰・重回帰・ロジスティック回帰、主成分分析などデータを扱ううえで大切な概念をたくさん学べました。

テストの結果も好調だったのですが、分かったことは「実践するにはまだまだ経験が必要」ということです。 授業の最後で教授も触れていましたが、「経験豊富な人にアドバイスを求めてください」とありました。

理論として理解はできても、例えば現場で検定を行っても、前提条件が正しいのか、得られた結論から言えることは何か、そういった解釈の話は、授業の内容から発展し現場で身につけないといけない知識です。

統計学は、設計によって結論がいくらでも変わってしまうため、正しい設計を行うためには実践をこなしていかなくては思いました。

線型代数

こちらも、放送大学で学び直しです。

放送大学には「入門線型代数」と専門科目の「線型代数」があるのですが、今回は専門の方を取りました。 ベクトルの図形的な意味から始まり、複素ベクトル空間の話や基底変換、三角化や広義固有空間、ジョルダン標準形、2次曲面と続きました。

大学で線型代数を学んだときは正直、「単位のため」のゆるい授業だったこともありほぼほぼこの辺りの理解ができていなかったことが再認識できました…良くない。

学びなおしたことでようやく線型代数の大切な部分を理解できた気がします。また、統計学でも主成分分析の項で線型代数の知識が活き、楽しかったです。

お絵かき

こちらの記事に触発され、お絵かきの練習を始めました。

note.com

絵、自体は少なくとも死ぬまでに思いのまま描けるようになりたい、と思っていた夢の一つでした。

(他にも、思いのまま曲を作れるようになりたいという夢もあります。自分の作品で人の感情を動かしたい、という欲が根源にあるのだと思います)

0-35日目: 参考記事に倣い、「ソッカの美術解剖学ノート」を進めました。

ソッカの美術解剖学の練習をしたコラージュ

(たまに全然関係ない絵が挟まってますが)

もともと高校の生物の授業などはとても好きだったため、人体の仕組みを詳しく知れる美術解剖学は絵の練習観点以外でも普通に面白かったです。

また、その後別の人体デッサンの本を読むにあたっても、ここで学んだ骨のつながりや形、筋肉・腱の接続部分などの知識がかなり役に立ちました。

40日目: 一旦ソッカが終わったので、試しにイラストを1枚描きました。(ヰ世界情緒という好きなキャラクターです)

35-40日目で描いたイラスト

ここで色々な課題が見つかります。

  1. 線・塗りが汚い、適当
  2. 手や肩など、立体感のある部分が破綻しがち(結局、自分でポーズを取り、写真を基に書きました…)
  3. 背景が描けない

41~50日目: 「3. 背景が描けない」に対処するため、「ファンタジー風景の描き方」という本を買いました。

ファンタジー風景の描き方 | ゾウノセ, 角丸つぶら | 絵画 | Kindleストア | Amazon

この本を進めることで、空気遠近法という概念や各種レイヤー効果(「オーバーレイ」とか「加算」「乗算」など)の使い方が学べました。

何よりも、付属するブラシ素材がめちゃくちゃ便利で、以降あらゆる場面でこのブラシを使わせてもらってます。

背景コーラジュ

「2. 手や肩など、立体感のある部分が破綻しがち」に対処するため、「箱と円筒で描く モルフォ人体デッサン」を購入しました。

www.amazon.co.jp

この辺りからアナログで練習を始めます。もっと早く気づくべきでしたが、練習はアナログの方が良い(早くサイクルを回せるため)。

モルフォの練習コラージュ

現在: 50~60日目ぐらい。

ちょうどこの辺りで7月のバーンアウトと重なり、なかなか1枚のイラストを描けずにおります。

(実際は、何枚か途中まで書いているのですが、途中で力尽きています…)

「1. 線・塗りが汚い、適当」に対処するため、「光と色のチュートリアル」を購入しました。

www.amazon.co.jp

また、いくつかイラストを描きかけてみて、目の描き分けができないことに気が付いたため、「表現を極める!目の描き方」を購入し取り組んでいます。

表現を極める! 目の描き方 | 末冨正直, 秋赤音, ちょん*, 湖木マウ, 金平東, SPIdeR. |本 | 通販 | Amazon

現時点ではまだまだですが、何事も楽しみつつ改善していく、という方針を念頭に引き続き取り組んでいきたいと思います。

Github Actionsのcacheをデータの永続化(?)に使う

Github Actionsでワークフローを作るとき、「どこかにデータを保存して次の実行時に参照したいな」「別のワークフローとデータを共有したいな」と思ったことはないでしょうか。

実は、普段キャッシュ用途に使っている actions/cache を使うと、制限付きでこの辺りが実現できます。

actions/cache

actions/cache は任意の文字列(key)を使って、指定ディレクトリや指定ファイルを保存し、任意の文字列(restore-keys)を使って復元できる仕組みです。

主に依存インストールのキャッシュやビルドのキャッシュに使われていますが、この仕組みをもっと汎用的に考えればデータの永続化(?)にも使えます。

制限

actions/cache には以下の制限があります:

  • レポジトリ全体で10GBまで(2022/6/4現在)
  • 保持期間は7日間

この条件を満たさないものは、古いものから順に削除されていきます。

なのでこの記事が適用できるのは、例えば決まった曜日にscheduleで実行されるWorkflowで7日以上間隔が空かないものや、一定期間過ぎたらリセットされても問題ないものになります。

(私は、月曜と木曜にメンバーを輪番で指名するWorkflowのために使用しています)

Starカウンター

簡単な例としてレポジトリにスターが付くたびにインクリメントするWorkflowを考えてみましょう。

on句にwatchを指定することでスターが付くたびにWorkflowが実行されます。あとは、cacheを使って現在のスター数をインクリメントしていけば、なんとなくカウントできそうです。

Github API叩けばスター数取れるじゃんというのは一旦置いておきましょう)

name: Star Counter

on:
  watch:
    types: [started]

env:
  STATE_FILE: './state'

jobs:
  increment:
    runs-on: ubuntu-20.04
    steps:
      - name: IDの生成
        id: build-id
        run: echo "::set-output name=id::$(date +%s)"

      - uses: actions/cache@v3
        id: state
        with:
          path: ${{ env.STATE_FILE }}
          key: counter-${{ steps.build-id.outputs.id }} # 毎回、最新のキャッシュを保存するためにIDを指定する
          restore-keys: counter- # 復元時は最新のキャッシュを指定する

      - name: 初回ならカウンターを初期化
        run: |
          if [ ! -f ${{ env.STATE_FILE }} ]; then
            echo '0' > ${{ env.STATE_FILE }}
          fi

      - name: カウンターをインクリメントする
        run: |
          VALUE=$(cat ${{ env.STATE_FILE }})
          echo "CURRENT: $VALUE"
          echo "$((VALUE + 1))" >| ${{ env.STATE_FILE }}

これを実行すると、以下のように実行されるたびに前回の状態を復元し、インクリメントするWorkflowが実現できます。

カウンターがインクリメントされている様子

その他の可能性

まだ試していませんが、Workflowが同時に走らないという前提があれば、このキャッシュを通じて他のWorkflowとも値を共有できます。

このキャッシュは「ブランチ」と「key」のスコープで共有されるため、同じ「key」を指定すれば同じデータが見れるはずです。

Workflowが同時に走りうる場合は不整合が起きるので、トリガーがscheduleで1日おきに動かすWorkflowなどだったら安全に扱えるのではと思っています。

https://github.com/actions/cache#cache-scopes

まとめ

制限はあるが、actions/cache を使ってデータの永続化(?)ができる。 簡単なWorkflowなら、Workflow間の状態を持たせることができる。

読みやすいコードって何だろう

読みやすいコードってなんでしょうね。  新卒の頃、たくさんリテイクを受けて書き直しした経験からよくよく考えはじめましたが、永遠に結論はでなそうです。

ひとまず、ここ数年考えている頭の中身を書き出したいと思います。

「コード」ってなんだ?

「読みやすさ」とは何か、の話に移る前に、そもそも「コード」とは何か考えてみます。

ここでいうコードは、もちろんあるプログラミング言語の文法に従って入力されたコードを差すわけですが、その本質は何でしょうか?

プログラミング言語の発祥を考えると、まずマシン語がありアセンブリ言語があり、より人間が読みやすく高度な構造化ができるよう高水準言語たちが生まれてきました。(と思います)

この言語の読者は、機械と人間であり、両方にとって読みやすい共通語として役割があります。この事実は、人間が読みやすいという自然言語との共通点でもありますし、人間以外の機械も読みやすいという差異でもあります。

今回の記事は、読者が人間である場合の「読みやすいコードとは何か」について深掘りたいと考えているので、ここでいうコードは人間の読み書きする文章としてのコードに重きを置きます。なので、安全性やパフォーマンス、その他の機械にとっての意味論は一旦前提として、人間にとっての意味論を考えます。

人間にとっての意味論を考えるとき、おそらく「読みやすい"文章"とは何か?」と考えても、結論は似たようなものになるのではないでしょうか。

読みやすい文章の話

よくネットで見かけるミームに「完全に理解した」→「何も分からない」があります。
私自身よく体感するのですが、例えば入門書を読んだ直後は「完全に理解した」状態になります。
一種の無敵感と言いますか、頭の中に矛盾が一切なく、読んだ内容を誰かに伝えたい、応用にチャレンジしたいとすら思っています。

ここで実際に他人に読んだ内容を伝えたり、もしくは自分で応用にチャレンジしたとき、今度は「何も分からない」状態になります。

不思議ですね…読み終わった直後は矛盾が一切なかったのに、他人から質問を受けたり、応用する中で新しい事実に出会ったときに「実は何も理解してなかったんじゃないか…?」と不安になります。
そしてこの不安をTwitterフィルタを通して書き込むと「何も分からない」になるのです。

私は、この「完全に理解した」→「何も分からない」の繰り返しのことを、事実の再グループ化だと思っています。

事実

ここでいうところの事実とは、実際に目で見て出会った出来事です。近所に新しくできたごはん屋さんに入ったら美味しかった。遠くの国で人が死ぬニュースを見た。実験の結果A群が優秀だった。なんでも良いのですが、とにかく当人にとって新しい出来事であり、集まると思い出や知識、陰謀論、宗教、いろいろな概念を形作ります。

グループ化

例えば、全く同じ事実をそれぞれ別の人に与えたとして、同じ概念には至らないと思います。地震が発生したとして、「良くあることだ」と思う人もいれば「某大学が実験をしている」と思う人もいるでしょう。(いるんですか?)

また、様々な心理的なバイアスによってもこの結び付く力関係は変わってきます。

事実同士をどう結び付けるか、逆に言うとどう線を引くのか、このグループ化によって概念はできていそうです。

再グループ化

「完全に理解した」→「何も分からない」の繰り返しを、事実の再グループ化と言いました。

「完全に理解した」は、入門書を読み終わった後など、自分の頭の中に矛盾がない一種の無敵状態でした。

「何も分からない」は、他人から質問を受けたり、応用する中で新しい事実に出会ったときに「実は何も理解してなかったんじゃないか…?」と不安になることでした。

ここで起きていることは、質問や応用で出会った「新しい事実」を、どうグループ化するかという悩みであり、これは事実の再グループ化と言えそうです。

入門書というのはよくできているので、分かりやすい事実(例や問題など)とそのグループ化の基準を最低限教えてくれます。当然、本としてまとまっているのでその中に矛盾はなく、しっかりと読んでいれば矛盾のない事実とグループ化による概念が読者の頭の中に構築されます。

しかし、その後「新しい事実」に出会ったときどうグループ化するのかは読者にゆだねられています。人は「完全に理解した」→「何も分からない」を繰り返しながら、頭の中の事実マップを拡張していき、事実のグループを適切にメンテナンスしていくことで、学習をしているからです。

文章

事実のグループ化を踏まえて読みやすい文章について考えたいと思います。

読みづらい文章というと、どんな文章を思い浮かべるでしょうか。「知らない単語がある」「何を言っているのか分からない」「矛盾している」「説明が端折られている」などが最近読んだ本だと思い当たります。読者の中で「分からない」が発生していると考えられそうです。

ということで、少なくとも「何も分からない」状態が起こりうる文章は分かりづらいはずです。まあ当たり前ですね、何も分からないのですから。

読みやすいコードの話

では、読みやすいコードとは何でしょうか。

文章における読みやすさはコードにも通じるはず、という前提なので、文章における事実のグループ化の構造にコードを当てはめて考えてみたいと思います。

事実

コードにおける事実は、例えば変数や関数などのシンボルが最も分かりやすいです。

letやvarなどの変数宣言や、fnやdeffunc、functionなどの関数宣言はまさに「新しい事実だよ」と伝えています。これらのシンボルを組み合わせて、ロジックを構築していきます。

もちろん、人間の読むコードはもっと曖昧ですので、プログラミング言語の文法と事実が完全に一致していないケースもあります。素晴らしいワンライナーなどはまとめて一つの事実となりそうです。今回は例として、シンボルに限った話をしたいと思います。

グループ化

コードにおけるグループ化は、例えばパッケージやファイル、関数やクラス、空行で区切ったコードの塊などが当てはまると思います。

他の関数や変数などのシンボルをまとめて、一つの意味を持たせたグループです。空行で区切ったコードも、暗示的に意味を持っていたり、または明示的にコメントで役割が説明されています。

コンテキスト

ここでもう一つ、コンテキストという概念も登場させたいと思います。

ここでいうコンテキストは、事実をグループ化した概念の中でも、コードを読む上で読者(レビュワーなどコードリーディングをしている人、またはコンパイラや機械)が前提として持っていなくてはいけない概念たちのことです。

例えば、AファイルのB関数を読んでいる場合、Aファイルでimport/include/useされているパッケージの情報は前提として読者が持っていなければ、B関数の中で使われているシンボルを読んだときにグループ化されていない新しい事実が生まれてしまいます。

これは、人間にとっては「何も分からない」ですし、機械にとっては「undefined symbol」です。

よって、「何も分からない」を防ぐためには、コンテキストも大切になってきます。

ただし一点だけ注意が必要で、「人間は、機械よりもコンテキストが曖昧」です。機械はメモリのある限りコンテキストを厳密に記憶し参照できますが、人間の認知はより忘れやすく曖昧でノイズも多いです。例えば、「人が覚えられる事柄はせいぜい3~5個」のような心理学のお話を見かけますが、これはコンテキストにも当てはまるため、「機械的には解決できるシンボルも、人間には解決できない」ことが多々あります。

強いコンテキスト、弱いコンテキスト

どのコンテキストが失われやすいのか?を把握するために、強いコンテキスト、弱いコンテキストという言葉を錬成したいと思います。

強いコンテキスト: 直前で定義された概念(画面内に収まる)、現在行よりも上で定義された概念(人は上から下に読む)、現在ファイル内で定義された概念、現在ファイル内で明示的に参照された概念(名前付きimport, 明示的なself, 引数など)…

弱いコンテキスト: ファイル外で定義された概念、暗黙的に参照される概念(グローバル変数など), 演算子オーバーロード

これは一例ですので、状況によってグラデーションは変わってきますが、弱いコンテキストに含まれる概念はIDEなどの支援なしでは読めない可能性が高そうです。また、プロジェクト内で普遍的な関数群などは強いコンテキストに含まれるかもしれませんし、新参者にとっては弱いコンテキストかもしれません。

よって、読みやすいコードの要素として、弱いコンテキストへの参照が少ない、という項目も挙げられそうです。

読みやすい、とは

本題ですが、「読みやすい」とはなんでしょうか。ここまで出た話をまとめれば「何も分からない」を防ぐため、適切な「事実」と「グループ化」を与えることだと言えそうです。

さらに、人間の認知の制約上、コンテキストは失われやすいという性質も考慮に入れる必要があるため、「弱いコンテキストへの参照が少ない」ことも挙げられました。

まとめると、「適切にグループ化され」「弱いコンテキストへの参照が少ない」コードが読みやすいと言えそうです。

「適切なグループ化」は色々な文脈で色々な言葉で語られていますが、「弱いコンテキスト」の話はあまり見かけない気がしています。(無知なだけかもしれません、既にあれば教えていただきたいです)

Go言語の例

以前、視覚に障害のある開発者からみたGo言語の良さに関する記事で「読みやすい/読みづらい」についての言及がありました。

zenn.dev

Go言語のレシーバー(他言語でいうthisやself)が明示的に記述されているためメソッドであることが確定する点や、public/privateが命名に現れるため参照しやすい、型が明示的であるためオブジェクトの動作を把握しやすい…などが挙げられています。

視覚情報が限られた状況では、強いコンテキストと弱いコンテキストのグラデーションはより濃くなるため、強いコンテキストの多いGo言語は読みやすいと言えるのでは、と思っています。

この話は晴眼者であるかに関わらず起こる問題で、コードレビューのような限られたdiff、限られた時間で読まなくてはいけないコードではグラデーションが濃くなると思います。

三行まとめ

コードは文章の一種。

適切なグループ化はやはり大事。

弱いコンテキストは読みづらい、特にレビュー時など。

 

余談

この「事実」「グループ化」「コンテキスト」の話は、文章、コード以外にもUIデザインだとか物語、音楽など、シーケンスがあり人間が認識する色々なものに適用できるんじゃないかとなんとなく思っています…。

2021年 振り返りと抱負

去年の振り返り
mjhd.hatenablog.com

早いものでもう2021年も終わり、体感的にはまだ6月ぐらいなんですが、カレンダーはバグらないので僕がバグってるのでしょう。
振り返りたいと思います。

どんな年だったか

エモさ溢れる年でした。今年も仕事でチームを移動しました。
同じプロジェクト内でチーム移動をするのが今回で二回目なのですが、やはり立ち位置を大きく変えるのではなく少しだけズラすことで差分が分かりやすく、組織的にも技術的にも人生的にも気づきが多いです。
2Dの映像も、ちょっとだけ視点をずらした映像を用意すれば立体的に見えると思います。あのイメージです。

仕事方針的な変化

チームを率いるというよりは、個人としての技術力を磨く方針に転換しました。
理由としては今の自分の技術力に自信がなく、確固たる芯を築きたい(そこまでは行かなくてももうちょい自信つけたい)というのが根本的な理由です。
あくまで技術に軸足を置きたい、でもまだ片足も地面についてない、という焦燥感がありました。

方針転換後、当然悩むのが、今までと同じだけの価値をどうやって個人で達成するのかという点。団体戦vs個人戦なわけです。
ここについては引き続き悩んでいきつつ、なんとなく対策は見えてきた気がするので来年実践していけたらと思います。(ユニークスキルを伸ばしつつ、影響範囲を広げてけば良いと思うのです)

内面的な変化

20歳も7年が過ぎ、いよいよ自分をだんだんと客観的に見れる余裕が出てきました。
これは人生を送るサブルーチンが刈り込まれ圧縮され、脳内メモリに余裕ができたので、自分をマネジメントするためのインスタンスを新たに立ち上げることができるようになったのが要因だと思ってます。
これがもっと脳内メモリに空きが出たら他人を気にかけるほどの余裕が生まれるのではないかな。(自分はこの段階に到達するのが遅い部類の人だと思います) 逆に一時的にでも脳内メモリが減り余裕がなくなるとマネジメントインスタンスがOOMKillされ、あっぷあっぷし始めます。リソースが足りてない中人生を送るために大切な非常措置だと思いますが、この状態を抜け出すのは大変です。
そして、そもそもこれは非常措置なので、発火させない予防が大事です。

均し

来年のキーワードは「均し」です。

地均し
1 地面の高低やでこぼこをなくし、平らにすること。また、そのために使う道具類。
2 あることをうまく進めるために、あらかじめ準備をしておくこと。「意見調整の地均しをする」
地均しとは - コトバンク

自分の今までの生き方は、端的に言ってこんな感じでした。
ノコギリ波

モチベやパフォーマンスが一時的に上昇します。ですが、上昇しきった後、意図的な代休や有休・虚空を見つめるだけの時間を作ることでクールダウンし、マイナスからまた徐々に上昇し始めます。意図的じゃなく燃え尽きることも度々ありました。
この生き方の問題点は、
1. 平均すると0付近にいるにも関わらず、自己評価や他人からの評価はプラス面を見がち。なので過大評価につながる
2. マイナス領域にいるときの上昇要因が、過大評価への焦りになりがち。なので振幅が徐々に増えていき負のサイクルに

誰も現状を正しく把握できなくなり、さらに負のサイクルも引き起こしてしまいます。
また、この生き方で成功体験を積んでしまうのも今後の自分や他人にとって悪影響があると思いました。

今年は、この波を均すことを目標にしたいと思っています。均すといっても傾き0というわけではなく、ゆっくり着実に右肩上がりを目指します。
コンスタントに学び、成果を出し、現状を正しく把握し、正しく振り返れる年。 それが理想です。

強みと弱み

マネジメントインスタンスを立ち上げたことで、徐々に自分の強みと弱みも具体的にわかってきました。

強み:
- 遅効的な思考が得意(じっくり考える, 執着する..)
- 深掘り好き(自分で考え理解する, 疑問点を残さない, ....)
- のめり込める
- 飲み込みが早い

弱み:
- 瞬発力のある思考が苦手(口頭で迫られる決断, 会話の切り返し, 臨機応変, 無茶ぶり...)
- ノイズのある環境に弱い(騒音, 集中力の乱れる情報, ...)
- 生き急ぎがち(パフォーマンスに波がある)

思考の早さやノイズについては立ち回り方でどうとでもなりそうです。

今まで自分は「熱しやすくて冷めやすい」や「一念発起が得意」な人間だと自覚していたのですが、改めて振り返ってみると、集中すると成果が出せるけど、セルフマネジメントが下手なので純粋に燃え尽きているだけだと感じます。
ここにも、来年のキーワードである「均し」が活きてくると良いです。

学んだこと

今年は三つの軸で学びました:
1. 低レイヤー(GB, NES, GBAエミュレータづくり)
2. CS・数学(暗号理論, 符号理論)
3. 哲学・思想(放送大学の授業)

低レイヤー

去年の抱負にこう書きました。

今年は、新しい技術や言語の習得ができていない年だった。(マズイ)

Rust を少し齧ったが、まだまだ「Rust らしいコード」を書けていない。

抱負としては「低レイヤーに関する知識を深める」。CPU/GPU、ドライバ、カーネル、OS、コンテナ…などなど。

この対策として、今年はゲームボーイファミコンゲームボーイアドバンスなどのレトロゲームエミュレータを複数書きました。

mjhd.hatenablog.com

mjhd.hatenablog.com

github.com

結果として、Rust力もはじめと比べたら多少は上がった…かな?(正直言語に関してはPRDで書かなければ上達しなさそう)
低レイヤーに関する知識は、正直そんなに上がっていません。命令パイプラインの実物を初めてちゃんと知れた…ぐらいでしょうか。
来年もGBAの開発を続けつつ、少し裾野を広げて「30日で作るOS本」でもやろうかな。

CS・数学

今年いっぱい、数学の得意な友人と一緒にCS・数学の輪読会を月2~3回ぐらいのペースで行いました。

XORSHIFT: Google Chromeが採用した、擬似乱数生成アルゴリズム「xorshift」の数理 – びりあるの研究ノート

楕円曲線暗号: 楕円曲線暗号アルゴリズムを理解する|TechRacho by BPS株式会社

暗号理論: 暗号理論と代数, 松本 眞, 広島大学: http://www.math.sci.hiroshima-u.ac.jp/m-mat/TEACH/network-algebra.pdf

暗号理論は、シーザー暗号に始まり、数学的難問を使った鍵交換、暗号化など現代でも使われている暗号化まで一通り網羅できる分野です。
CS・数学の分野的には、離散数学で扱うような剰余類の話であったりとか、そこから発展して群論の話など、幅広く扱います。

符号理論: 例題で学ぶ符号理論入門 | 先名 健一 |本 | 通販 | Amazon

符号理論は、ガロア理論に基づいた多項式表現により二進数を表現することで、多項式を用いた様々な性質を情報科学の分野に持ち込みます。
必要な数学的分野としては、離散数学線形代数です。
これにより、現代のQRコードや通信で使われるような誤り訂正のロジックを説明できます。

以上を学びました。個人的に、輪読会は続かないものだと思っていたので、今回一年を通して数学を学べたことはかなり良い成功体験になりました。

哲学・思想

哲学、思想については放送大学で、概論的な授業と、言語哲学の授業をとりました…が、正直言語哲学の方は時間がなく、単位を取ることができませんでした。
教科書自体はあるので、来年隙を見つけて取り組みたいと思います。

まとめ

来年は「均し」をキーワードにエンジニア人生を歩んでいく。
個人的な学習としては引き続き、低レイヤーとCS・数学をメインに行っていく。

今年もありがとうございました、来年もどうぞよろしくお願いいたします。

Rustでファミコンエミュレータを自作した話

前回、ゲームボーイエミュレータを開発してから2ヶ月ほど、今度はファミコンエミュレータを開発したのでゲームボーイとの開発の比較をしたいと思います。 ちなみに、ファミコンエミュレータの作成はこれで二回目。

f:id:wait0000:20210612115808p:plain
スーパーマリオを動かした様子

前回の記事: mjhd.hatenablog.com

命令がシンプル

CPUの実装の大半は数多い命令をデバッグしながらチマチマと実装していく部分なのですが、ファミコンで使用されているMOS 6502の命令セットはゲームボーイZ80, 8080と比べても命令数が少ないです。 ファミコンは8bitを素直にデコードすれば命令と一対一に対応するため、たかだか250個程度の命令しか存在しません。 比べて、ゲームボーイにはプレフィックス命令が存在して、特定のバイトが現れた時は、次の1バイトも考慮してデコードします。なので倍の500個ほど命令が存在します。

また、各命令も命令種別、アドレッシングモード、命令のカテゴリ(ロード命令、算術命令…など)をビット列として組み合わせたものなので、デコードも工夫をすればとてもシンプルになります。(CPU unofficial opcodes - Nesdev wiki

割り込みもゲームボーイに比べてかなり少なく、全体的にCPUの実装はサクッと終えることができました。

PPUはちょっとややこしい

比べて、PPUはゲームボーイより少しややこしいです。 ゲームボーイのPPUは、BG、スプライト、ネームテーブル、パターンテーブルがあれば描画できたのですが、ファミコンではここに属性テーブルが追加されます。 白黒ではなくてカラーなのでしょうがないですが、実装量は増えました。

また、ゲームボーイの時はCPUから直接VRAMを触れましたが、ファミコンレジスタ経由か、DMAが主になります。メモリ空間も共有していないので、バスの実装も倍になります。

他にもゼロスプライトヒットなどの機能や、スクロールもマッパーによってミラーリングが切り替わるなど、細かな部分で実装が増えます。

あと罠として、パターンテーブルを読むときに、ゲームボーイファミコンで2BPPの順序が逆です。今回見事にハマったため注意が必要。

全体的に、PPUはゲームボーイと比較して複雑です。

マッパーがややこしい

ゲームボーイのマッパーはバンクの切り替えだけ実装しておけば良かったのですが、ファミコンの場合はここにミラーリングの切り替えや、割り込みの実装などが追加になります。 メモリ空間もCPUのものとPPUのもので二つ存在するため、マッパーの実装も倍です。 また、MMC1はバンクの切り替えがシフトレジスタ経由なので、内部状態も複雑。

コントローラーはちょっとだけ複雑になった

ゲームボーイの時は8bitのセット/リセットでボタンの状態をレジスタに反映すれば良かっただけなのでシンプルなコントローラーの実装ができましたが、ファミコンはシフトレジスタのように読み込むたびに状態が変わり、各ボタンの状態がクルクルと切り替わりながら返ってくる仕様になってます。

全体的に

レトロゲーのエミュレータ作りで一番頭を使うのはPPUだと思っているのですが、その点でファミコンはPPUが比較的複雑でハマりポイントも多い印象です。 入門としてはゲームボーイが一番で、発展としてファミコンを作る、という順番が良いなと感じました。

今回作ったモノ(第二作目): github.com

第一作目: github.com

Rustでゲームボーイエミュレータを自作した話

ここ2ヶ月ぐらいゲームボーイエミュレータを自作してました。結果として「物量も少なくエミュレータ自作入門にオススメ」と思ったので、時系列で完成までの流れをまとめつつ、エミュレータ開発は楽しいぞということ伝えたいと思います。 

f:id:wait0000:20210414200924g:plain

星のカービィをプレイする様子

全体像を把握する

ゲームボーイエミュレータをRustで自作する記事が目に止まり、ちょうどRustの入門題材を探していたので作ることに。

記事で紹介されている動画をみるだけで全体像が把握でき、なんとなく作れそうな雰囲気を感じられます。↓

 

www.youtube.com

 

まずはROMをデコード

 何はともあれ、ROMをメモリに読み込めないことには実行もできないので、 PanDocs: The Cardridge Header を参考に、struct 定義と enum 定義をひたすら写経します。

ROMヘッダの取り込みとチェックサムの検証を実装したらデコード処理は完了なので、あとはMBC(Memory Bank Controller)というカードリッジの内部に埋め込まれているチップを模したプログラムを書きます。

カードリッジタイプのゲームは内部にチップが入っており、特定のアドレスに書き込みを行うことでバンクと言われるデータ領域を切り替えて、大容量なデータを限られたメモリマップに展開します。

この処理を再現するわけですが、まずは一番簡単な RomOnly というMBCを書きました。単純に先程読み込んだROMの配列にアクセスするだけなので5秒ぐらいで書けます楽ちん。

 

github.com

 

CPUの実装

ここがエミュレータ自作の本番です。

CPUの命令サイクルと呼ばれる、

1. フェッチ(PC番地のデータをとってくる)

2. 命令デコード(とってきたデータで処理を分岐する)

3. 命令実行

4. フラグ反映(Z, N, H, Cの4つのフラグを更新する)

5. PC更新(PC += 1)

を行ってくループ文を書きます。

次に Gameboy CPU Manual (PDF) にCPUの命令一覧と処理内容、影響を受けるフラグの一覧が載ってるので、これをひたすらに実装していきます。

だいたい 500 命令あるらしいですが、対象となるレジスタが違うだけで共通の物が多いです。

1. ロード命令(読み込み、書き込み)

2. ジャンプ命令(PCを書き換える系)

3. 加算減算、ビット演算

4. ビットシフト・ローテート

5. スタック操作

6. 特殊系

ぐらいで大まかにまとめられます。

 

ここで踏んだ罠:

- CBプレフィックス命令に気をつける(0xCB を見つけたら次の 1byte もデコードする)

- `ADD HL, rr` だけハーフキャリーの計算が違う(11bit が対象)

- `ADD SP, n` は16bit命令でも8bitだと思ってキャリー、ハーフキャリーの計算をする

- 16bit 命令は 0x0X ~ 0x3X 系と 0xCX ~ 0xFX 系で、レジスタの規則が違う(0x0X ~ 0x3X は BC, DE, HL, SP、0xCX ~ 0xFX は BC, DE, HL, AF

 

github.com

 

PPUの実装(BGだけ)

ひたすら黒い画面と睨めっこしてCPUがある程度形になったら、モチベを保つためにも画像処理を書きます。Hello World ROMが動き、画面が表示されることが目標です。

ここで実装するのは PPU(Picture Processing Unit)で、今で言うところのGPUでしょうか。

スプライト、ウィンドウ、スクロール…などPPUには色々な機能がありますが、一旦今必要なのはBGだけなので実装していきます。

GBEDG: The PPU にPPUの挙動が細かく記載されているので読みつつ、特にパレット、カラー、タイル、マップの関係性を紙などに図示すると理解しやすいです。

正常に実装できれば、Github - dusterherz/gb-hello-world に置いてあるROMを今まで書いた処理に食わせれば「Hello World」という文字が表示されるはずです。

 

ここで踏んだ罠:

- タイルのアドレス計算には二種類のモードがあり、それぞれ「符合なしでベースアドレスは 0x8000」、「符合ありでベースアドレスは 0x9000」

 

github.com

 

デバッガの実装

ここがキモです。この後の戦いに必須です。以下の機能があると便利です:

- ブレークポイント(指定のPCに到達したら、実行中断してステップ実行)

- トレース(CPUの状態とニーモニックのログを出力できる機能。別のエミュレータと出力フォーマットを合わせておくと diff とってデバッグできる)

- メモリダンプ(特定範囲のメモリを出力する機能)

 

CPUテストROMの実行

ラスボス戦です。辛かったです、地獄かな。

https://github.com/retrio/gb-test-roms に「cpu_instrs」というCPUの各種命令の実行結果をテストしてくれるROMがあります。エラーが起こると、「#1 failed」とどこでテストが落ちたのか教えてくれる便利ROMです。

前回まででBGは実装したので、テスト結果も画面表示で確認できますね。

 

これがだいたい通ると、急に市販のROMも動くようになる大事な局面なのですが…まあとにかくデバッグがしんどい。

テストROMが複雑で分かりづらいことが原因と思われるので、デバッグで大事なポイントを書いておきます。

 

1. MBC1を実装する

テストROMは MBC1 です。https://gbdev.gg8.se/wiki/articles/MBC1 を参考にMBC1を実装しましょう。

書き込み仕様で 0x0000-0x1FFF など範囲指定されている部分は、「指定範囲のどこに書き込んでも動作は一緒」です。

 

2. 信頼できる既成のエミュレータを一つ見つける

ブレークポイント、ステップ実行、トレース、メモリダンプなど一通りの機能が揃っていて、かつ安定しているエミュレータを見つけます。

macOSだと mGBA と言うエミュレータが安定していて良かったです。

ここで自作したトレース機能の出力フォーマットが統一されていると、テキスト diff ツールを使ってside-by-side で動作を検証できるので、抜群にデバッグが楽です。 

 

3. テストROMを逆アセンブルする

テストROMにもソースコードがもちろんあるのですが、マクロを多用していたり一度命令をメモリ上にコピーしていたりして追うのが大変なので、一度逆アセンブルすると吉です。

今回は GitHub - mattcurrie/mgbdis を使用しました。

基本的に 0x4XXX に存在するニーモニックは、実行時に 0xCXXX へコピーされて実行されます。なので、確認したいニーモニックのROM上の位置が 0x41B9 であれば、0xC1B9 へブレークポイントを張れば確認できます。

 

残り部分の実装

もうここからはずっと楽しい時間です。市販のROMも動き、見た目周りの実装を充実させたり、タイマー、ジョイパッドなどゲームプレイに必要なものを実装していきます。

 

完成!

RomOnly な Dr. MARIOテトリス、MBC1 の星のカービィ、マリオランドなどが動くようになります。めでたしめでたし。

f:id:wait0000:20210414214551p:plain

マリオランド

 

ROM吸い出し(番外編)

エミュレータ開発は実際のROMデータがあるとモチベが保てます。カードリッジ自体はブックオフなどで300円ぐらいで買えますが、ROM吸い出し機はアマゾンなどで6000円ぐらい。ちょっと高い。

なので今回は CUBIC STYLE さんが同人ハードとして開発している、「Raspberry PiにつないでGBAのROM読み書きできる拡張ボード」を少しいじり、GBのROMを吸い出しました。

※ 公式にGBへ対応しているわけではないので、自己責任です

cubic-style.booth.pm

 

読み出しプログラムをいじるだけでGB対応ができちゃいました。

GBAとGBでカードリッジ端子の互換性はあるのですが、ピンアサインが違います。(あと電圧も 3.3V と 5V で違うけど、一応そのまま動く)

GBのピンアサインは Arduinoを使ったgameboyカードリッジのdump が分かりやすく、読んだ結果アドレスが16ピン、データが8ピンで、RDをLowにしたままアドレスを書き込むと、データが流れてくると判明しました。

なので、ROMヘッダを読んで RomOnlyであればただメモリ番地をインクリメントしながらデータを読んでいくだけ、MBC1であればバンク番号をインクリメントしながらROM Sizeまで読み出していくだけですね。

 

追記: ROM読み出しツールをRustで書きました。興味のある方はつかってみてください。PRも歓迎しています。

github.com

 

今後

github.com

 

今後の仕事としては、実際のカードリッジから直接ゲームを起動する実験をしてみようかと思っています。

ROM吸い出しでカードリッジの低レベルな知識もついたので、MBCの実装を拡張ボードのGPIOとSPIに接続するプログラムを書きました。

(ARM 32bitへのクロスコンパイルが辛く、動かせていないです…動作速度など気になる)

これができればMBCの実装も必要なく、いろんなカードリッジが動かせますね!

 

エミュレータとしては、今まで 4bit(習作), 8bit(NES), 8bit(GB) と書いてきたので…次は 16bit の何かでしょうか…?GBが楽しかったのでまた何か書きたい…

 

 

github.com

 

2020年 振り返りと抱負

どんな年だったか

今年は引き籠りが合法となったことにより、プライベートが充実した年だった。

相対的に仕事の記憶があまりない。めちゃ頑張った気がするけど。

プライベートは年始から急にハマった現代思想に始まり放送大学に入学したり、UberEatsが高いので自炊をしたり、引っ越したり猫を飼ったりした。

 

現代思想

もともと大学の時に教養科目として現代思想の概論のような授業を受講し、毎回板書を三回読み直し、図書館へ通い原著を読む(当然何も理解できない)生活をしていたが、大学卒業後はしばらく触れていなかった。 

社内の技術交流会でひょんなことから現代思想の話が始まり、同時期にOOUIの勉強会などにも参加し、グレアムハーマンの四方対象を読み始めたこともあり、急にマイブームが訪れた。

おかげで今年は自分史上稀にみる読書の年になった。

読んだ本

自分の知識レベルとしては、一般教養として哲学者・思想家の名前を知っている程度だったので、ひたすら入門書を読んだ。

はじめての構造主義 (講談社現代新書)

はじめての構造主義 (講談社現代新書)

 

構造主義のお話。初心者でもとても読みやすく、現代思想でも大事な部分なので取り掛かりとしてとても良かった。

 

資本主義に出口はあるか (講談社現代新書)

資本主義に出口はあるか (講談社現代新書)

 

そういえば今の経済の歴史を何も知らないな?資本主義という言葉に苦手意識があったため、この本を読んだ。これも非常に読みやすく、分かりやすく思想をカテゴリ分けしてくれている。

歴史的な出来事や当時主流だった思想を、右と左、ロックとルソー、保守とリベラル、ルーズベルトとウィルソン…など対立関係で説明している。

現代思想の歴史としても、当時の世界情勢が理解できると思想の背景が理解できるため、学びがあった。

 

資本主義リアリズム

資本主義リアリズム

 

資本主義つながりでもう一つ、気になっていた本。資本主義が唯一の思想となっている現代において、資本主義の終わりを想像するよりも世界の終わりを想像する方がたやすい。資本主義の思想を内面化してしまっている現代人に、本当にこれでいいのか?と呼びかける内容だった。

上の「資本主義に出口はあるか」と一緒に読んだため、歴史的に現代がどのような状態に置かれているのか、理解がはかどった。

 

四方対象: オブジェクト指向存在論入門

四方対象: オブジェクト指向存在論入門

 

OOUI つながりで興味のあった、オブジェクト指向存在論の原著。ド素人が読んでも分かるものではないので、逆に「何を学ばなければいけないのか」をリストアップする用途になった。四方対象はライプニッツフッサールハイデガーホワイトヘッドなどなどの思想を引用して説明されているため、こいつらを学ばなければいけない。

ひとまずハイデガーホワイトヘッドを次に学ぶことに決めた。

 

ホワイトヘッドの哲学 (講談社選書メチエ)

ホワイトヘッドの哲学 (講談社選書メチエ)

 

オブジェクト指向存在論で度々言及される、ホワイトヘッドという思想家の入門書。

世界は連続する有機体である、物と物の間に厳密な区別はなく、我々は世界を抱握によって認識している…。

という有機体の哲学を分かりやすく説明してくれている。

合わせて、

1267夜『ホワイトヘッドの哲学』中村昇|松岡正剛の千夜千冊

を読むと理解がはかどる。

 

 神は実在するのか。

アンセルムスという「神」の定義をした司祭・思想家の解説から始まり、ルイスのそれに対する論理学的な批判を通して、分析哲学という一つの流派を学べる本。最終的に可能世界、様相論理などを解説している。

大学の卒論で様相論理、可能世界意味論を学んだのだが、後半、論理式がとても多くなるため、かなり読むのに苦労した…。

こういう哲学があるんだな、という学びが得られた。

 

 ここまで色々本を読み、これは外せないなと感じた思想家三人、ベルクソンサルトルフーコーを解説した入門書。

読みやすかった。ベルクソンは特に理解しやすく説明されていた。

 

現代フランス哲学に学ぶ (放送大学教材)

現代フランス哲学に学ぶ (放送大学教材)

 

放送大学でテキストとして使っている本。正直、放送大学の授業内容は、この本を音読した音声なので、実はこの本を買うだけで事足りる。

ベルクソンメルロ=ポンティ構造主義フーコー、ポールリクールを軸に、ハイデガー、マルセル、アラン、サルトルフッサール…と一通り概要を説明してくれている。

内容もとても分かりやすいし、都度他の思想家と対比をしながら説明をしているため、脳内マップの作製にとても役立つ。

ここでベルクソンの理解がかなり進み、「あーなるほどー!」と声に出しながら受講できた。

これから期末試験。

抱負

来年も、まだまだ穴埋めできていない思想家が沢山いるため、入門書を漁っていきたい。

特にカント、マルクスハイデガーについてまだ概要しかわかっていないがかなり重要人物なので、きちんと学びたい所存。

そして、準備万端の状態で四方対象を読み切りたい。また、フーコーの系譜学ももう一度読み直したい(今ならもう少し内容が入ってくる気がする)。

エンジニア

今年は、新しい技術や言語の習得ができていない年だった。(マズイ)

Rust を少し齧ったが、まだまだ「Rust らしいコード」を書けていない。

抱負としては「低レイヤーに関する知識を深める」。CPU/GPU、ドライバ、カーネル、OS、コンテナ…などなど。

ひとまず、下記の記事に取り組みたい。

kaminashi-developer.hatenablog.jp

また、Rust についても作成中の作品を作り切るところまでは最低限やっていきたい。

人生

そろそろ彼女作らないとまずくない…?まずくない?

20代後半戦に入って、ライフステージが周囲に比べて低いのが気になるところ。

総じて

来年は自発的に動いていく年になると良いなと思います。