YDiary

メモ的な

Rustでファミコンエミュレータを作った その3

ずいぶん間があいてしまいましたが、

ydkk.hateblo.jp

の続きです。

前回の記事ではPPUの実装を進め、何とかスーパーマリオブラザーズ(SMB)の冒頭部分を動作させることができるようになりました。

今回の記事では、マップを右に進んだ際にスプライト0ヒットが発生せずにハングしてしまう理由や、APUの実装についてです。

スプライト0ヒットが発生せずにハングする理由

スプライト0ヒットが発生しなくなる理由は、前回の記事のラストに書いた通りPPUのメインスクリーンが変更され、スプライト0を含むスコア部分が描画されなくなってしまうからです。 SMBのプログラムの挙動から推測すると、どうやら毎回のVBlank後にメインスクリーンが0に戻っていることを期待しているようです。

そこで、試しにエミュレータの実装を変更してVBlank時にメインスクリーンを0にセットするという処理を加えてみると、スコア表示部分のチラつきが発生する*1ものの問題なくハングが発生していた箇所よりも先に進むことができるようになりました。

このことから「SMBのプログラムは何らかの方法でPPUのメインスクリーンを0にセットしているものの、エミュレータがその挙動を再現できていない」ということが推測されます。

PPUの内部レジスタ

ファミコンでは、PPUのメインスクリーンは$2000(PPUCTRL)レジスタの下位2ビットを通してCPUから設定できるようになっています。 そこで、まずはこのPPUCTRLレジスタへの書き込み周りについて実装の確認やデバッグなどを行いました。

結果として、PPUCRTLレジスタの実装に特に問題は見つかりませんでした。 レジスタへの書き込みは正しく処理されていますし、そもそもSMBのプログラムがPPUCTRLレジスタを通してメインスクリーンを0にセットしている形跡がありません。

つまり、PPUCTRLレジスタ以外に書き込む以外の方法でメインスクリーンを変更することができる方法が存在するということです。

そこで、改めてPPUについて詳しく調査を行いました。 そこで見つけたのが、PPUの内部レジスタに関する情報です。

ファミコンのPPUはCPUからPPUの様々な状態(メインスクリーン、OAM(スプライトメモリ)アドレス、スクロール位置など)を外部レジスタ*2から設定できるようになっています。 一方で、PPUはその状態を v, t, x, w と呼ばれている4つの内部レジスタで保持しています。

この4つの内部レジスタは合計で15+15+3+1の34ビットしかなく、CPUから設定できる状態を全て保持できません。つまり、CPUから書き込まれる複数の状態を同じレジスタで重複して管理しており、状態の設定に副作用が存在するのです。

これは「なるべく少ない内部レジスタでPPUを実装する必要がある」もしくは「ハードウェア的にその方が都合が良い」といったハードウェア側の都合によるものだと考えられますが、とにかく、エミュレータ実装時にこの内部レジスタによる副作用を考慮せずに外部から見えるPPUの状態をそのまま保持するように実装してしまうと、今回のような問題が発生します。

副作用の具体例

PPUの内部レジスタによる副作用の具体例を挙げます。

まず、PPUCTRLレジスタの書き込み時の動作を次に示します。

t: ...GH.. ........ <- PPUCTRL: ......GH

先ほどメインスクリーンはPPUCTRLレジスタの下位2ビットを通して設定できると書きましたが、このように設定されたメインスクリーンの値( GH の2ビット)はPPU内部のtレジスタの4, 5ビット目に格納されます。

次に、CPUやDMAからPPUのメモリにアクセスする際のアドレスを指定する$2006(PPUADDR)レジスタの書き込み時の動作を次に示します。 PPUADDRレジスタへの書き込みは1回目と2回目で動作が異なるのですが、ここでは1回目の書き込み時の動作を示します。

t: .CDEFGH ........ <- PPUADDR: ..CDEFGH

なんと、tレジスタの4, 5ビット目に設定されていたメインスクリーン値がPPUADDRレジスタに書き込まれた値の5, 6ビット目( EF の2ビット)によって上書きされてしまいました。 PPUメモリのアドレスを書き込んだと思ったら、実はメインスクリーン値も書き換えていたわけです。

SMBの処理を改めて確認したところ、実際にVBlank時にこのPPUADDRレジスタを通してメインスクリーンの値が0にセットされていることが確認できました。

副作用の実装

PPU実装の再現度を高めるには、本物のPPUと同様にこの内部レジスタを使用するように実装を変更するのがベストでしょう。 一方で、この内部レジスタによる状態の表現は複雑で、既存の実装部分への影響も大きいものでした。 そこで、ひとまず内部レジスタを再現するのではなく、PPUレジスタへの書き込み時の内部レジスタによる副作用を再現する実装を加えることにしました。

具体的には次のような実装です。

//PPUレジスタへの書き込み
pub fn write(&mut self, addr: u8, value: u8) {
  match addr {
    0x00 => self.registers.control_register.write(value),
    0x01 => self.registers.control_register2.write(value),
    //..省略..
    0x06 => match self.state {
      State::Idle => {
        self.v_ram_addr_h = value;
        self.scroll_vertical &= 0b0011_1011;
        self.scroll_vertical |= (value & 0b11) << 6; //★内部レジスタによる副作用★
        self.registers.control_register.main_screen = (value & 0b00001100) >> 2; //★内部レジスタによる副作用★
        self.state = State::Writing;
      }
      State::Writing => {
        self.v_ram_addr_l = value;
        self.scroll_horizontal &= 0b00000111;
        self.scroll_horizontal |= (value & 0b11111) << 3; //★内部レジスタによる副作用★
        self.state = State::Idle;
      }
    },
    //..省略..
    _ => panic!(),
  }
}

★内部レジスタによる副作用★ とコメントした部分が追加で実装した副作用です。 addr == 0x06 のPPUADDRへの書き込み時にメインスクリーン値を書き換えていることが確認できます。

この実装を行ったことでSMBでのハングが発生しなくなり、無事ステージの先に進めるようになりました。

無理やりメインスクリーン値を0にセットした場合のスコア表示のチラつきもなく、とても良好なエミュレーション結果です。

APUの実装

ここまででCPUとPPUが満足に動作するようになったので、続いて音を出すモジュールであるAPUの実装に入ります。

ファミコンのAPUには2つの矩形波チャンネルとそれぞれ1つの三角波、ノイズ、DMCチャンネルがあります。 DMCチャンネルはDPCMデータなら何でも再生することが可能ですが、ファミコンカセットのROMの容量は限られている(SMBでは40KBしかない)ので容量をバカ食いするDPCMを贅沢に使用するのは難しかったようです。 DPCMは主にドラム、ベース、パーカッションなどの一部として効果的に使用されていたようです。

余談ですが、ファミコンはAPUの各チャンネルの出力がミキシングされた結果の最終的な出力(マスターアウトとでも呼ぶべきでしょうか)が一度カセットを通ってから再びファミコン内に戻されたのちにTVに出力されるようになっています。 これによって、カセット側で出力音声にエフェクトをかけたり、拡張音源の出力をさらにミキシングしたりといったことが可能になっています。 良く出来ています。

今回はひとまずSMBで使用されている矩形波三角波、ノイズチャンネルを実装しました。

APUの実装もPPUと同様にNesdev Wikiの資料などをもとにひたすら実装するのみです。 実装量はそれほど多くないので、CPUやPPUよりも比較的簡単に実装することができました。 チャンネルごとに完全に分離されているため、1つのチャンネルを実装すればそれだけで動作確認やデバッグが行えるようになるのも実装を進めやすいポイントです。

APU実装の進め方

まずは適当なチャンネル(矩形波チャンネルがおススメ)を一通り適当に実装し、ファイルにPCM出力をダンプして聞いてみるとこんな感じでそれっぽい音が聞こえてきます。この時点で大分楽しいです。

あとは別のエミュレータや実機のプレイ動画などをリファレンスにしながら少しずつ音が違う箇所を直していく作業になります。 エミュレータによってはAPU出力の有効無効をチャンネルごとに切り替えたりできるので、そうしたエミュレータを使用するとデバッグが捗ります。 また、 SNDTEST.NES というAPUのレジスタをセットしながら出音を確認することができるROMがある*3のでそれを使うのもおすすめです。

フロントエンド側の音声出力実装

APUの実装時を進める際には、なるべく早い時点でフロントエンド側(エミュレータを動作させるGUIプログラム)に音声出力機能を実装するのがおススメです。 ファイルへのPCMのダンプを通したデバッグはAPUの実装初期なら良いのですが、本格的に実装を進める際には実際のゲームなどを動かしながら音を出す方が開発効率がとても高いです。

フロントエンド側の音声出力の実装には、当初はGUIに使用していたwindows-rsからそのまま使えるXAudio2を使用して実装していました。 しかし、音途切れを防ぐためにバッファの供給タイミングを正確にコントロールしようとしたときに、RustからCreateSourceVoiceに渡すIXAudio2VoiceCallbackインスタンスを通してコールバックを受け取るのが難しそうだったので、後にSDL2に乗り換えました。

SDLはクロスフォーム対応なので、将来的にブラウザ(WASM)対応を行う際などに同じような実装で音を鳴らせるんじゃないかといった目論見もあったりします。まだ全然調べてないので実際はどうなのか分かりませんが。

ファミコンのAPUはCPUクロックと同期して動いているため、毎クロックごとに音声信号を生成して出力するとサンプリングレートがめちゃくちゃ高くなります。 具体的には1.789MHzとかになります。現代のゲーム機なんかよりもよっぽどハイレゾですね。 さすがにそのままでは扱いにくいので、雑に10分の1に間引いてからSDLのリサンプラを介して鳴らしています。

APUの実装を進め、最終的には次のような音を鳴らせるようになりました。

ノイズチャンネルの出音が若干怪しい気もしますが、まずまずいい感じにエミュレーション出来ているのではないでしょうか。

次回予告

Rustの特徴などについても書きたかったのですが、結構長くなってしまったので今回はここまでにします。 気が向いたら続きを書きます。

SMBをちゃんと動かすというのをファミコンエミュレータ実装開始時の目標にしていたのですが、達成できたんじゃないかと思います。 あとはもう少し実装を整理したうえでソースコードを公開したり、最適化やマルチスレッド対応、ブラウザ(WASM)対応なんかができたら良いなーと思ってます。

その他

エミュレータ実装の作業ログを適当なハッシュタグをつけてツイートしたりしておくと後から見返せて良いです。 今回の一連の記事を書く際の動画やスクショなどもそこから拾ってきています。

*1:このチラつきの原因は良く分かっていません。

*2:CPUからアクセス可能なレジスタ

*3:どこで入手したのか忘れてしまいましたが、探せば出てくると思います。

Rustでファミコンエミュレータを作った その2

ydkk.hateblo.jp

の続きです。

前回の記事ではCPUと最低限のPPUを実装してnestestの公式命令テストを完走することができました。

今回の記事ではいよいよスーパーマリオブラザーズ(SMB)を動作させてみます。

ROMの入手

さて、動作させるにはまず初めにROMを入手しなければいけません。

手元にSMBのカセットは無かったので中古で入手しました。

カセットの溝にマイナスドライバーを差し込んで力を加えるとカパッと殻が外れるので、あとは基板に実装されているマスクROMを読み込むだけです。

マスクROMを読み出す様子

SMBのカセットのマスクROMには特に技術的保護手段が施されていないので、ユニバーサルプログラマなどで普通に読み込むことが可能です。

PRG とマーキングされている方がプログラムROMで、 CHR とマーキングされている方がキャラクターROMです。

読み出すROMのサイズやハッシュ値などの情報はNesCartDBというサイトで確認することが可能なので、読み出したデータの整合性確認に使用すると良いでしょう。

SMBの場合は32KBのプログラムROMと8KBのキャラクターROMで、合わせてわずか40KBです。 この40KBの中にゲームのロジックからキャラクターや背景の絵、BGMや効果音などがすべて含まれています。

こうしてROMを入手することができたので、いよいよ実装したエミュレータに読み込ませてみます。

初回起動

実装したファミコンエミュレータで初めてSMBを起動させた時の様子が次の通りです。

まぁ案の定ボロボロです。 オリジナルの画面を知っていれば何となく何が写ってるんだろうなという想像ができる程度でしょうか。

CPUはnestestを完走するぐらいなので問題なく動いていそうですが、Hello, world!ロム程度でしか動作確認をしていないPPUがまともに動作する訳がありません。

ここから先は表面的な理解だけで実装を進めるのは難しいので、本格的にNesdev Wikiのお世話になることになります。

恐らくいきなりNesdev Wikiを見ても内容を理解するのは難しいと思いますが、一度Hello, world!ロムを動かす程度まで実装したうえで見れば割と理解しやすいのではないかと思います。

あとはNesdev Wikiに記載されている仕様と自分の実装とを見比べたうえで、不足している部分を実装したり誤っている部分を修正していくことになります。

まずはPPUの実装を修正してSMBを問題なく描画することができるようになることを目指しました。

PPU実装・修正の過程

細かい実装や修正の内容まで書くと長くなってしまうのでダイジェストで行きます。

それっぽい絵が表示されるようになる

少しPPUの実装を修正するとそれっぽい絵が表示されるようになりました。

空の色が黒いですが、これは自作ファミコンエミュレータあるある現象のようで、VRAMの特定のメモリアドレスのミラーリングを正しく実装できていないとこのように空が黒くなります。

Addresses $3F10/$3F14/$3F18/$3F1C are mirrors of $3F00/$3F04/$3F08/$3F0C. Note that this goes for writing as well as reading. A symptom of not having implemented this correctly in an emulator is the sky being black in Super Mario Bros., which writes the backdrop color through $3F10.

わざわざ↑のようにNesdev Wikiにも書かれるぐらいで、相当多くのファミコンエミュレータ実装者がこの黒い空を拝んでいるようです。

自分も以前からこの現象のことは何となく知っており、それなりに意識して実装していたにもかかわらず間違えました。

空が明るくなる

さて、当該の箇所を修正することで無事に空が明るくなりました。 ところが、全体的に色がおかしいうえに肝心のマリオの姿が見当たりません。

マリオ登場、しかし動きが…

DMAを実装してBGとスプライトのテーブルアドレス選択を修正することでついにマリオが登場しました。 しかし、どうも動きがぎこちないです。

というのも、この時点ではスプライトの描画位置が正確ではなく、ピクセル単位ではなく8*8ピクセルのタイル単位までしか実装できていません。そのため、カクカクとしたぎこちない動きになってしまっています。

また、スクロールの実装も正しくありません。

マリオの血色と動きが良くなるも、右に進むとハング

さらにPPUの実装を修正するとマリオや世界の色が正しく表示されるようになりました。 スプライトの描画もより正確になり、一気にそれらしい見た目になります。

ところが、動画の終盤に見て取れるようにある一定以上ステージを右に進むとハングして操作不能になってしまいます。 この現象は偶発的なものではなく、ステージのある地点まで画面がスクロールすると必ず発生する再現性のある現象です。

このハングが発生した際にCPUが何の処理をしているのかを調べたところ、どうやらひたすらスプライト0ヒットを待ち続けているようでした。

スプライト0ヒットとハングの原因

スプライト0ヒットはファミコンのPPUに存在する機能で、スプライトメモリ(OAM)上の0番目に存在するスプライトを描画する際に、スプライト内の不透明なピクセルがBGの不透明なピクセルと重なった場合にPPUの特定のレジスタのフラグを立てるという機能です。 スプライト0ヒットを使用することによって、プログラムが厳密なCPUクロックを数えなくてもPPU上で画面がどこまで描画されたのかを知ることができます。

ファミコンのPPUは、効率的なスクロール描画を行うために内部で2画面分の描画領域を持っており、PPUのスクロールレジスタの値に応じて描画領域のうちのどの部分を画面に出力するかを決定しています。

https://www.nesdev.org/w/images/default/b/bf/NTS_scrolling_seam.gif
PPUのスクロール処理(Nesdev Wikiより引用)

一方で、SMBには画面上部にスコア表示があります。 そのため、単純に画面全体をスクロールさせてしまうとスコア部分も一緒にスクロールしてしまいます。 スコアをスプライトとして表示することができれば画面のスクロールと両立させることができますが、ファミコンの仕様上スコアの文字を全てスプライトとして表示させることはできません。*1

そこで、SMBではスコアをBGとして描画したうえで、スコアの表示を終えたラインから画面をスクロールさせるという手法を取っています。*2

図にすると次のような感じです。

画像全体で2画面分の内部描画領域を表しており、赤や緑色の枠は画面の出力範囲です。

  1. まず、最初の時点ではスクロール値を変化させずにBGにあるスコアを描画させます(赤枠部分)
  2. スコアの描画が終了したら、スクロール値を適切な値に設定して画面をスクロールさせます(緑枠部分)
  3. 画面を最後まで描画し終えたら、再びスクロール値を0に戻して次のフレームでのスコア描画に備えます

この、2. のスコアの描画が終了したかどうかの判定に先ほど説明したスプライト0ヒットを使用しています。

SMBのスプライト0はスコア表示のコインアイコンの下部2ピクセルほどの部分です。

 

ジャンプ等でマリオのスプライトを重ねると、スプライト0だけ前面に描画されるのでスプライト0を簡単に確認することができます。

エミュレータがハングした際、内部では次のような状態になっていました。

スクロール範囲が内部描画領域の半分を超えたため、折り返しを行うためにメインスクリーン*3が変更されます。

これによって、スクロール値0がもはや内部描画領域の左側を指さなくなります。結果としてスプライト0ヒットに必要なスコアのコインアイコンが描画されなくなりスプライト0ヒットも発生しなくなりますが、SMBのプログラムはいつまでも発生することのないスプライト0ヒットを待ち続けるためハングしてしまいます。

さて、ハングしてしまう原因はすぐに分かりましたが、どうしてこのような状態になってしまうのかはすぐに分からず、しばらくの間悩み続けました。

ワークアラウンドとして、エミュレータの実装で毎フレームの描画が始まる前に強制的にメインスクリーンを0に切り替えるようにすることでハングを回避することはできますが、スコア表示がちらつく上にSMB以外のスクロールを使用するゲームで問題が発生すると思われるため、そうしたワークアラウンドによる対処は避けました。

次回予告

今回の記事はここまでです。

次回は、このスプライト0ヒットが発生せずにハングしてしまう問題の解決編やAPU(音声処理ユニット)の実装、Rustの特徴についてなどを書きたいなと思います。

その3を書きました。

ydkk.hateblo.jp

*1:8*8ピクセルのスプライトを画面全体で64枚まで&同一水平ライン上に8枚までしか表示させることができない

*2:ファミコンは、画面を上から下に1水平ラインずつ描画しています。

*3:内部描画領域のうちの、描画を始める基準となる位置

Rustでファミコンエミュレータを作った その1

はじめに

前回の記事の終わりに新年の抱負として書いていた通り、Rustを書けるようになりたいなーと思っていたので勉強も兼ねてファミコンエミュレータを作ってみました。

新しい技術を身に着けるには実際に使ってみるのが一番、ということで別に作るものは何でもよかったのですが、なんかRustというと皆ファミコンエミュレータとかGBエミュレータとかを作ってるイメージがあったので自分も真似してみました。

身の回りでいうと、少し前にkamiyaさんがRustでファミコンエミュレータを作っている様子がTLに流れてたのを見て、楽しそうだなーと感じたのも大きいです。

確かにCPUやらメモリやらといったハードウェアの低レベルな処理をエミュレートするというのは、基礎的な処理やらデータ構造やらを扱えるという意味でプログラミング言語を学ぶのに向いているように思います。 それに、実際に絵が出て音が出るのは楽しいのでモチベーションの維持にも繋がりますしね。

取っ掛かり→Hello, world!ロムを動かすまで

いきなりファミコンエミュレータを作るといっても何も分からないので、まずは調べて出てきた次の記事を参考にしました。

qiita.com

基本的な部分のみですが、Hello, world!ロムを動かすまでに必要なことが一通りまとまっていて、とても良い記事です。

記事にもある通り、Hello, world!ロムはNES研究室のサイトから入手可能です。

↑の記事を読めばファミコンアーキテクチャやスプライトのデータ構造などは何となく理解できます。 が、それだけでは肝心のプログラムが動かないのでまずはCPUの実装が必要になります。

CPU実装

CPUの命令セットについてはNES on FPGAのサイトに日本語で詳しくまとめられているので、その資料を基にひたすら実装していきました。

何も動かない状態でひたすら実装をし続けないといけないので、このCPU実装フェーズが最初の難関になるでしょう。 命令セットはトータルで百数十種類ありますが、命令自体はそれほど種類があるわけではなく、豊富なアドレッシングモード×命令の組み合わせでトータルの種類が増えている感じです。 非公式命令もかなりの数が存在しますが、普通に動作させる分には必要ないと思うので自分は実装していません。

PPU実装

CPU実装が完了したら、今度は出力する画像を生成するPPUの実装に着手します。 PPUはスクロール周りも実装するとそこそこ大変ですが、Hello, world!ロムを動かす分には必要ないのでいったん無視してしまって問題ありません。PPUの基本的な部分は、↑のQiitaの記事を見れば実装できると思います。 PPUの実装はCPUに比べれば大分楽で、実際に出力される絵を見ながら作業できるので楽しみながら進められると思います。

Hello, world!ロムの動作!

CPUとPPUが実装できればいよいよHello, world!ロムが動きます。 初めて動かした際の様子が次の通りです。

f:id:YDKK:20220307001509p:plain

この時点ではまだファミコン内部の色表現と実際に出力される色との変換を行っていませんが、まぁそのまま適当に出力するだけでもそれっぽく見えます。 HELLO, WORLD!が2行にわたって出力されているのはVRAMアドレス計算のミスによるバグです。

とまぁ本当に最低限の処理で表示もバグっていますが、この段階で自分の実装したCPUとPPUでプログラムが動いて絵が出ている訳で、めちゃくちゃテンションが上がります。

そのまま勢いで実装を修正しながら色変換を実装して、ロムを書き換えて好きなメッセージを出力してみます。

f:id:YDKK:20220307001538p:plain

ここまでくればチュートリアルは完了といって良いでしょう。

自分の場合はここまで来るのに、Rustの勉強と合わせて0から始めて大体2, 3日程度でした。

本当のCPU実装

さて、Hello, world!ロムが動いたら次に行うべきはnestestを完走させることです。

nestestはファミコンのCPU命令が正しく実装されているかどうかをテストできるロムのことで、これが本当に良く出来ています。

エントリーポイントを変更することでPPUやPAD入力が未実装でも動作し、またその動作を記録したリファレンスとなる有名なCPUログが存在するため自分のCPU実装と比べながらデバッグを進めることが可能です。

nestestやリファレンスとなるCPUログはNesdev wikiのページにあるリンクから入手可能です。

まずはCPUにリファレンスのログと同じ形式のログ出力機能を実装し、リファレンスのログとのdiffを取ることで徹底的にCPUのバグを洗い出していきます。

f:id:YDKK:20220307001606p:plain
実装したCPUログ出力

f:id:YDKK:20220307003146p:plain
リファレンスログとの比較

普通ならここでCPUのバグがそれはもうボロボロと出てくると思うので、地道にデバッグしていきます。

nestestは公式命令→非公式命令の順でテストが走るので、非公式命令を実装していない場合は非公式命令に当たる部分までのCPUログが一致していれば問題ないです。

f:id:YDKK:20220307003222p:plain
デバッグの結果、非公式命令に当たるまでログが一致するようになる

ちなみにnestestはPAD入力とPPUのVBlankでのフラグセットとNMIを実装すれば実際に操作できる形でも動かせます。が、↑でCPUの修正が済んでいれば当然ここでも問題なく動作するのでまぁおまけみたいなものです。*1

次回予告

長くなるので今回の記事はここまで。
次回はいよいよスーパーマリオブラザーズを動作させてみます、が……。

f:id:YDKK:20220307001220p:plain:w256

その2を書きました。

ydkk.hateblo.jp

*1:ちなみに、APUも実装すると効果音も鳴るようになっていて意外と凝っています。

2021年に新しく触れた技術など振り返り

年の瀬なので書きます。

なんか普段のお仕事の内容をあまり書けないのもあって、技術的なアウトプットをあまりできてないなーという気がするのでそれを取り返す意味でもまとめておきます。
意図的にぼかした書き方になっているような箇所は察してください。

Kubernetes

勉強

必要に迫られたので何も知らないところから一から勉強した。
まずは全体像を知りたいなと思ったので体系的にまとまっていそうな本を探した。

social.0ko.me

評判がよさそうだった↑の本をポチって1~2週間かけて読み込んだ。
何も知らない状態からいきなり理解するのは難しいので、最初はとにかく「こういう概念がある」というのを覚えるために広く浅く読んだ。
最初の時点では細かい点まで覚える必要はなくて、一通り読み終わった後に

  • ○○をしたい
  • 確かそういう概念があったな
  • 確か△△みたいな名前だった
  • 本の索引から調べて該当する箇所を読み直したりググったりする

というサイクルを繰り返すことでスッと理解が進んだ気がする。
↑の本はそうした使い方にとてもよくマッチしていて、一からKubernetesを勉強したい人に十分おススメできる。

自宅サーバKubernetesクラスタ

とは言っても流石に本を読んだりチュートリアルを試したりしているだけではイメージしにくいので、実際に手を動かして触ってみたくなる。
そこでちょうど趣味の自宅サーバで動かしていたMastodonやらSlackLineBridgeやらをKubernetesで動かしてみることにした。

自宅サーバKubernetesクラスタを作るとなるとUbuntuあたりにkubeadmをインストールしてって感じで構成するのが一般的な気がするものの、どうも面倒くさい。
せっかく趣味で弄るのでもっといい感じで楽しそうなものは無いかなと探したところK3sというのを見つけた。
Kubernetes準拠の軽量なディストリビューションとのことでとても面白そう。
さらに調べるとK3sを動かすためのLinuxディストリビューションであるK3osというのを見つけた。

github.com

昔からこういう機能特化した軽量Linuxディストリビューションみたいなのは大好きなので、一目見た瞬間にコレにしようと決めた。

もともと上に書いたMastodonやらSlackLineBridgeやらといったモノはBargeというDockerコンテナを動かすための軽量Linuxディストリビューションでdocker-composeを使って動かしていた。
ただ、最近Bargeのメンテが途絶えてしまっているので何とかしたいなーと思っていた。
ちょうどここからK3osに置き換える形でKubernetesを導入できたら良いなと思い作業を始めた。

おうちKubernetesクラスタならではの課題と構成

せっかくKubernetesクラスタを組むのにノードが1台だけというのは寂しいので、録画サーバとして稼働しているマシンも加えて2台構成にすることにした。
自宅サーバだと後述するようなストレージを提供するサーバが単一障害点になるので複数台でHA構成を組む意味はあまりないんだけど、勉強という意味ではtaintの挙動を確かめたりとか色々できることが増えるのでおススメ。

K3osの導入とセットアップはすごく簡単ですんなりできたものの、自宅サーバならではの悩みポイントとして

  • グローバルIPアドレスが1つしかない
  • ノードをまたいだストレージの永続化を何とかする必要がある
    • クラウドサービスみたいに「EBSをくっ付けておしまい!」にならない
    • 当然それでもPVCでDynamic Provisioningをしたい!

という点がある。

1つ目はファイアウォールで内向きHTTP/HTTPSトラフィックをNAPTして2台のノード宛てにロードバランシングするように構成した。
L4ではあるものの正常性チェックもかけているので、物理的に片ノードがダウンした場合でもトラフィック自体は問題なく正常なノードに届くような構成になっている(はず)である。
LAN内に対しては、今までは単純にコンテナが稼働しているマシンに繋がれば良かったので内向きDNSで向けていたものの、外向きと同様にロードバランシングさせたいのでヘアピンNATを構成した。結果的に今まで外向きと内向きで二重管理していたDNSレコードが一つで済むようになったので管理が楽になった。
Kubernetes側にはTraefikをDaemonSetとして導入*1することで、NodePort的な感じでファイアウォールからのトラフィックを受け取るようにした。
ここにTraefik Kubernetes Ingress providerやらcert-managerやらも導入することでIngressを定義するだけでいい感じにHTTPSなサービスを公開できるようになった。
現状DNSレコードの追加だけは手動なのでここも自動化できたらいいなーと思ってる。誰かPorkbunで使えるいい感じの方法を知ってたら教えてください。APIはあるので無ければ自分で作ればいいんだろうけども。

f:id:YDKK:20211227225806p:plain

結果的にこの構成はとても良くできた気がするので気に入っている。

2つ目のノードをまたいだストレージの永続化は、まずは普通にNFSサーバを立ててnfs-subdir-external-provisionerを試してみた。
普通に構成したら普通に動作して、「ストレージ問題はこれで解決!」と思ったのも束の間で、試しにPostgreSQLのDBをNFS経由で永続化して動かしてみたところ、まぁ驚くほどパフォーマンスが出なかった。
それはそうという感じなので、iSCSIによる永続化も導入することにした。 iSCSIに関しては、open-iscsi/targetdを含んだiSCSIターゲットを立ててKubernetes側にはiSCSI-targetd provisionerを導入することで、PVCでDynamic Provisioning可能なiSCSIストレージを構成することができた。
そのままPostgreSQLで試したところ、NFSと違って今度は良い感じにパフォーマンスが出るようになった。

iSCSI周りの構築手順は

blog.whywrite.it

をそのままなぞっただけなので、試してみたい人は↑の記事を見ながらトライしよう!

ちなみにiSCSI-targetd provisionerやそれを含むexternal-storageリポジトリはEOLになってアーカイブされてしまっており、Kubernetes v1.20以降とは互換性が無く動作しなくなってしまっている。

social.0ko.me

そこでKubernetes v1.20でも動くようにパッチを当てたので必要な方は使ってください。

social.0ko.me ydkk/iscsi-controller

↑のやつは問題が起きている kubernetes/client-go のコードをピンポイントで修正するといった感じで対応しているのに対して、 @sukukyon がもっと根本的な対応を行ったものもあるので動かない場合はそちらを試してみるのもおすすめです。

NFSiSCSIの使い分けは、NFSでもスループット自体は出るし内容の管理もしやすいので画像データや動画データみたいなサイズが大きめでファイル内への細かいアクセスが少ないものが置かれる場所はNFSにして、PostgreSQLやElasticsearchのファイルみたいな細かいアクセスが多くてパフォーマンスが求められるような箇所はiSCSIを使うようにした。

こうしていい感じに使えるおうちKuberenetesクラスタを構築することができた。
また、こうして実際に手を動かすことでKubernetes自体やネットワーク周り、ストレージ周りの理解がとても進んだ気がする。

Mastodonの死活監視のログを見るにダウンタイムが171時間なので、1週間ぐらいかけて↑の課題にあーだこーだしながら対応していたようである。

f:id:YDKK:20211227234451p:plain

GO言語

こちらも必要に迫られたので一から勉強した。

GO言語を使いこなしているかと言われると大分怪しいが、少なくとも普通に読み書きできるようにはなった気がする。
言語仕様がシンプルなのでCライクな言語を書いたことがあればすんなりと使えるようになる気がする。

  • goroutineがすごくいい感じ
    • でも無名関数と組み合わせたときのキャプチャの挙動が初見殺し
  • cgoがあるのでネイティブライブラリとの連携もバッチリ
  • ソースコードにアクセスできないパッケージをimportすると途端に補完周りが死ぬのでつらい
  • メモリ管理が楽でいいけどヘビーな使い方をした時のオーバヘッドやらGCの負荷やらがどうなるのか少し気になる

DynamoDB

同じく必要に迫られたので一から勉強した。

  • ただのKVSかと思ったら、キーやらインデックスやらで色々な概念があってとっつきにくかった気がする
  • まともなトランザクションも制約もなくてつらい
    • 単に自分がNoSQLに慣れてないだけな気もするけど、大規模なものはやっぱりRDBMSで作りたいなーって気持ちになった
  • AWS SDK for .NETのクライアントでスレッドプールが枯渇するのを早く直してほしい
    • DynamoDb .NET threadpool starvation · Issue #1476 · aws/aws-sdk-net · GitHub
    • 通常動作は問題なかったのに負荷テストでいっぱいリクエストを投げた途端に動かなくなって1日溶けた
      • 集計処理でいっぱいクエリを投げる場合とかも同じ問題に当たりやすい
    • ThreadPool.SetMinThreads を盛ればまぁ使えるようにはなるけどなんだかなぁという感じ

Agones

知ったきっかけはOOPartsがらみだったが、ちょうど別件で必要な要件とマッチしていたので使ってみた。
ちなみにOOPartsでの導入については id:yu_suke1994 が詳しく発表しています。

note.com

  • Kubernetesでステートフルなゲームサーバの管理どうすんねん」と思っていたところへのアンサーな感じがして楽しかった
  • Kubernetesの拡張性の高さを実感できた
    • 「これができるなら何でもできるな」という感じがした
  • Agones自体の機能では一部不足しているなと感じる点もあった
    • そのまま使うと1Pod内で複数ルームを動かすのに対応してない*2とか
    • 待機中のPodにも safe-to-evict=false が付くのでスケールインするときにノードが減りきらないで残ってしまったりするとか

その他

最近は自分でプロトコルを作ってUDPでめっちゃ送り合ったりみたいなことをしてます。楽しい。

買ってよかったもの

プロジェクターとスクリーン

在宅勤務で可処分時間が増えたので、思い切って中華プロジェクターやら100インチスクリーンやらChromecastやらを買った。
3万円程度の出費で最高のおうちシアター環境ができたのでとても満足。

f:id:YDKK:20211228124022p:plain

f:id:YDKK:20211228124917p:plain

f:id:YDKK:20211228125326p:plain

スマホゲーム

ブルーアーカイブ

サクサク遊べてとても楽しい。
スクスト以来の長く続いているスマホゲームな気がする。

総力戦トロフィーはこんな感じ
f:id:YDKK:20211228133428p:plain

2022年の抱負

とりあえずProtocol Buffersを使いこなせるようになりたいのと、Rustに興味があるので書けるようになりたいと思う。
Rustは何かのエミュレータでも作ってみるのがいいのかな。

他にも引き続きいろいろな新しい技術を学んで身につけていけたらいいなーと思う。

それでは、皆様良いお年を!

*1:K3s自体にデフォルトでTraefikが含まれているものの、バージョンが古そうな感じだったのでそちらは無効化して自前で導入しなおした

*2:少なくとも自分が触った時点では

「サーマルスロットリングまできっちり回せ!!」 第12世代Intel Core i9-12900KFレビュー

メインマシンのCPUをi7-6700Kからi9-12900KFにアップグレードしたのでそのレビューです。

アップグレードのきっかけ

もともとのi7-6700Kで性能的には特に困っていなかったのですが、もう6世代も前だしAlder LakeからDDR5とかbig.LITTLEとか面白そうな要素がてんこ盛りで楽しそうなので思い切ってアップグレードすることにしました。

あと、一番大きな理由はi7-6700KがWindows 11でサポートされなくなったことです。 別にWindows 10のままでも当面は問題ないといえばないのですが、メインマシンが最新OSでサポートされないような古いPCというのも悲しい話なので……。

アップグレード先としてはAMDでも良かったのですが、せっかくならZen4が良いなーと思うとまだまだ先になりそうなので今回はIntelにしました。
Alder LakeとWindows 11の登場タイミングといいIntelとMSのマーケティング戦略的な何かを感じますね。

新マシンの構成

パーツ 型番 メモ
CPU Intel Core i9-12900KF
CPUクーラ Noctua NH-D15
メモリ ARK ARD5-U32G88MB-48B-D DDR5-4800 32GB (16GBx2)
マザボ MSI MPG Z690 CARBON WIFI
GPU ASUS DUAL-RTX2070-O8G-MINI 流用
GeForce RTX 2070 8GB
OSストレージ Transcend TS1TMTE220S 流用
PCIe M.2 SSD (2280) 1TB NVMe PCIe Gen3 x4 3D TLC
電源 SILVERSTONE SST-ST1200-G 流用
1200W 80PLUS Gold
ケース Cooler Master CM 690 II Plus 流用
OS Windows 10 x64 → Windows 11 x64

アップグレードなのでCPU、メモリ、マザボ周り以外は既存PCパーツの流用です。

f:id:YDKK:20211205140945p:plain
新規に購入したパーツはこれだけ。

パーツ選定

CPU

パーツ選定時点では第12世代Intel CoreのCPUとしては

型番 価格 スペック
i9-12900K 79,799円 16 (Pコア8+Eコア8) コア 24スレッド
i7-12700K 53,980円 12 (Pコア8+Eコア4) コア 20スレッド
i5-12600K 39,000円 10 (Pコア6+Eコア4) コア 16スレッド

という感じで、内蔵グラフィックス非搭載のF付きモデルが↑の価格-3,000円(i5のみ-2,000円)といったところでした。

「うーん、まぁこの価格差なら迷わず最上位でしょ」という感じでi9-12900Kをチョイスしたかったのですが、発売開始直後はi9-12900Kが品薄で全然手に入りませんでした。そこで仕方なく内蔵グラフィックス非搭載のi9-12900KFを購入。 3,000円差ならQSVでもいろいろと遊べるF無しの方が良かったのでちょっと残念。

今調べるとF無しの在庫も復活してるので、これに関してはもうちょっと待てば良かったかも。

f:id:YDKK:20211205231224p:plain
Intel Core i9-12900KF

CPUクーラ

さて、どうもi9-12900K/KFは爆熱らしいという噂をちょくちょく耳にしていたため、なるべく冷やせるCPUクーラにしたいなと最初は水冷を検討していました。

ただ、流用する予定のケースだとラジエータを外付けにしない限り240mmラジエータまでしか搭載できません。 いい感じの外付けラジエータも見当たらず、中途半端な240mm水冷でメンテナンスに苦労するぐらいならハイエンド空冷で良いのでは?と思い、空冷最強とも謳われるNoctua NH-D15をチョイス。 記事後半にベンチマークスコア等を載せていますが、結果的にこれで正解だった感じがします。

f:id:YDKK:20211205202722p:plain アップグレード前の虎徹と比較するとこんな感じで、幅は増えてるものの高さは低くなっておりメモリなどの周辺部品とも干渉しにくそうな作りでなかなか良さ気なCPUクーラです。

なお、2021年12月現在流通しているNH-D15にはLGA1700に対応したマウントキットが含まれていないため、別途 NM-i17xx-MP83 というマウントキットを入手する必要があります。 こちらのマウントキットは各ショップで1,000円程度で販売されていますが、NH-D15と第12世代Intel Core CPUもしくはLGA1700のマザーボードの購入証明があればNoctua公式から 無料 で届けてもらえます。

国際郵便なので多少日数はかかってしまいますが、DDR5メモリを気長に待ちながら少しずつパーツを揃えていくようなつもりであればNoctua公式に注文するのもおススメです。 すでにパーツが手元にそろっている場合は手持ち無沙汰になってしまうので1,000円払って購入したほうが良いでしょう。 自分が購入しようとしたときは入荷未定だったり転売屋が3,000円で売ってたりしたのでNoctua公式に注文しました。 11月9日に注文したところ11月28日に手元に届きました。

また、数か月以内にはCPUクーラに最初からLGA1700に対応したマウントキットが同梱されるようになると思います。

メモリ、マザーボード

さて、第12世代Intel Coreが発売されてから現在に至るまでの間、最も入手困難なパーツがDDR5メモリでしょう。 自分もCPUを先に確保したは良いもののDDR5メモリが全く手に入らず、年末ぐらいまでに手に入れば良いなぁと半ば諦めムードでした。

social.0ko.me

そんな中、お昼休みにTwitterを見ていると「ARKでDDR5の在庫が復活してる!」とのツイートを見つけます。 その場で確認してみると確かに在庫がある! CPUかマザーボードとの抱き合わせ販売ではあったものの、ちょうどマザーボードをまだ購入していなかったのでこれ幸いと即決で購入。 その後確認したところすぐに売り切れてしまったようで、ラッキーでした。

social.0ko.me

PCは据え置きで有線LAN接続するのでマザーボードWi-Fi機能は完全に不要で、その分もう少し安めのマザーボードが欲しかったのですが、Z690のちょっと良さ気なマザーボードにはほぼ必ずと言っていいほどWi-Fiが搭載されているのでそういうものだと割り切りました。
世の中デスクトップPCでWi-Fi接続したい需要ってそんなにあるんですかね……?

MSIマザーボードは初めてでしたが、今回購入したMPG Z690 CARBON WIFIはCPUの電源周りもしっかりとした構成で、i9-12900Kを十分活かせそうです。 組んでから日が浅いのでまだ何とも言えませんが、この調子で安定して長持ちしてくれれば文句なしです。

一つ気になる点は、UEFIのUIが独特な感じでやや使いにくく感じました。なんというか、Advanced Modeと言いつつEZ Modeの1項目の中に設定が全て詰め込まれている感じ、とでも言うのでしょうか。 あとはUEFIのアップデートがUEFI中からオンラインでできないのもちょっと不便に感じます。これに関してはむしろASUSの方が進んでいるんですかね。

I/Oパネルがマザーボードに付いていて取り付け不要なのはちょっと楽で良かったです。抑えのツメが変な位置に入っちゃってるのに気づかずに組み立ててしまったりすると面倒なんですよね。

ストレージ周りではM.2ソケットが4本もある上にヒートシンクまでマザーボードと一体となって標準搭載されており、ストレージの主役はもう完全にSATAじゃなくてM.2なんだなぁと感じました。

f:id:YDKK:20211205231521p:plain
DDR5メモリ(16GB)

f:id:YDKK:20211205231648p:plain
MSI MPG Z690 CARBON WIFI

組み立て

特に大きなトラブルもなくサクッと組みあがりました。

f:id:YDKK:20211205201458p:plain
こちらがアップグレード前の旧PCで、

f:id:YDKK:20211205201617p:plain
こちらがアップグレード後の新PCです。

CPUクーラは横向きの方がスペース的に余裕があるのですが、CPUクーラはヒートパイプがCPUパッケージの長辺と平行になるように設置した方が良いという記述を見かけたためそのように設置しています。 が、これに関してはヒートパイプの本数が少ないCPUクーラでなければそこまで大きな影響はないような気もします。

仮組した際の動作確認には例の格安USB HDMIキャプチャボード*1が大活躍しました。

f:id:YDKK:20211205214557p:plain
こんな感じで広いスペースで作業しつつその辺のノートPCをディスプレイ代わりに使えるので取り回しが非常に良いです。

性能

さて、お待ちかねのi7-6700K→i9-12900KFでどの程度性能が向上したかについてです。 単純なベンチマークスコアは検索すればいくらでも出てくると思うので、この記事ではi7-6700Kからの性能差i9-12900KFは空冷でも冷やしきれるのかといった点に焦点を当ててみようと思います。

CINEBENCH R23

まずは何も設定を変えていないCINEBENCH R23の10分ベンチマークのMulti Coreスコアです。
特にバックグラウンドアプリを落としたりせず、HWiNFO64でモニタリングしながらの結果なので本体の性能よりは多少低めの結果が出ていると思います。

旧PCではスコアを取っていなかったので、i7-6700Kはインターネット上で調べた参考値です。また、i9-12900Kも360mm水冷できっちり冷やしたらどの程度のスコアが出るのかを見る意味でインターネット上で調べた参考値を付けています。 CINEBENCH R23はCPUベンチマークなので、内蔵グラフィックスの有無の差しかないi9-12900Kとi9-12900KFは実質同スコアと見て良いでしょう。

CPU Multi Coreスコア
i7-6700K (参考値) 5,600程度
i9-12900KF (Windows 10、実測) 25,623
i9-12900KF (Windows 11、実測) 26,146
i9-12900K (Windows 11、参考値) 27,200程度

スコア参照元*2 *3 *4

i7-6700K→i9-12900KFで比較するとなんと4.5倍強もの数値が出ています。 これは普段使いでのコンパイルやビルド時間の短縮などにもつながりそうです。

また、Windows 10→Windows 11で2%程度ですがスコアが向上しており、Windows 11でのAlder Lake向けの最適化*5は確かに利いていそうです。 2%と書くと大した差ではないように見えますがCPU全コアをフルに使うベンチマークでの値なので、様々なワークロードを並行して走らせるような日常使いのパフォーマンスにはもっと大きな影響があるような気がします。

また、360mm水冷でのスコアと比較しても5%程度しかスコアの低下がないため、空冷のNH-D15でも十分性能を引き出せると見て良いのではないでしょうか。

ベンチマーク中のCPUクロック・温度・消費電力

f:id:YDKK:20211205171445p:plain

↑のグラフはCINEBENCH R23でのベンチマーク中(Windows 11でスコア26,146を記録した際)のCPU(Pコア)クロック周波数と温度、消費電力をHWiNFO64でモニタリングした結果です。

グラフから読み取れる通り、i9-12900KFとNH-D15でCINEBENCH R23を回すとCPU温度(黄色)が瞬時に100℃に張り付きサーマルスロットリングが発生します。 一方で、サーマルスロットリングが発生しているから性能がガタガタかというとそんなことはなく、Pコアのクロック周波数(灰色、オレンジ色)はほぼ4,788MHzを維持しつつ、冷却状態に応じて動的にクロック周波数を調節しているような動作が見受けられます。

この結果を見るに、i9-12900K/KFは「サーマルスロットリングが発生することを前提にしつつ、その状態でも冷却が間に合う限り最大限の性能を発揮する」というような設計思想になっていると思われます。 そのためサーマルスロットリングの発生をそこまで恐れる必要はなく、むしろサーマルスロットリングが発生するまできっちりCPU回すことで冷却性能に対する最大限の処理性能を得られるように感じます。 「爆熱で手が付けられないCPU」というよりは「冷却が間に合う限度まで性能を発揮するCPU」と捉えるのが適切でしょう。

それでもCPU温度が100℃になるのはちょっと……と感じる場合は、電力リミットや温度リミットの値を調整することで発熱を抑えた運用も可能です。 電力リミットや温度リミットのチューニングに関しては次の記事で詳しく紹介されているので、気になる方は参考にしてみるのが良いでしょう。

akiba-pc.watch.impress.co.jp

Battlefield 2042

さて、ベンチマークよりももう少し実用的なワークロードに対する性能として、先日発売されたばかりのBattlefield 2042をプレイした際のフレームレートとCPU・GPUの状態を比較してみましょう。

GPUASUS DUAL-RTX2070-O8G-MINI(GeForce RTX 2070 8GB)を使用し、画質オプションは低プリセット、画面解像度はFHD(1920x1080)でのプレイです。GPUドライバは GeForce Game Ready Driver 496.76 で、OSにはi7-6700KではWindows 10、i9-12900KFではWindows 11を使用しています。

i7-6700K

f:id:YDKK:20211205193441p:plain

まずはアップグレード前のi7-6700Kでの結果からです。

なんとRTX 2070を使用しているにも関わらず、FHDの低プリセットでもフレームレート(青線)が約18FPSしか出ていません。 それだけBattlefield 2042が 最適化不足な 重いゲームであることが見て取れます。 こんなフレームレートではまともにプレイできません。

また、GPU Core Loadが15%程度なのに対してTotal CPU Utilityが70%~80%前後を推移しており、CPU負荷がボトルネックとなってGPU性能を活かしきれていなそうなことが読み取れます。

i9-12900KF

f:id:YDKK:20211205194009p:plain

アップグレード後のi9-12900KFでは、GPUを変更していないにもかかわらずフレームレート(青線)が大幅に改善しており、45FPS程度とi7-6700Kの約2.5倍ものパフォーマンスが出ています。 Total CPU Utilityが70%~80%→30%~40%程度に落ち着いた代わりに、GPU Core Loadが15%→35%と増加しており、i7-6700KよりもGPU性能を引き出せていることが確認できます。(それでもまだGPU性能を活かしきれていない感じがしますが。)

また、CPU温度に注目すると、ベンチマークといった極端な例とは異なりゲームプレイといった一般的な用途であればサーマルスロットリングは発生しておらず、NH-D15の空冷でもi9-12900KFを冷やすのに十分な冷却性能があることが見て取れます。 もっとも、Battlefield 2042がi9-12900KFのマルチコア性能を十分に活用しているかは大分怪しい気がするので、CPUのマルチコア性能をしっかりと使うゲームではまた別の結果となる可能性があります。

ところで、快適に遊ぶためにはもう少しフレームレートが欲しいところなのですが、これはGPUをアップグレードするべきなのかDICEによる最適化が進むのを待つべきなのか……。

その他

スリープ復帰後に不安定になる?

どの部分に原因があるのかをイマイチ切り分けられていないのですが、PCをスリープ状態にしてから復帰させた際にWindowsが不安定になる(新しいプロセスを何も立ち上げられなくなり、シャットダウンや再起動もできなくなる)現象がちょくちょく発生していました。

アップグレード前の古いドライバが悪さをしているのかと思い、デバイスマネージャから 非表示デバイスの表示 で出てくる色の薄いデバイスをすべて削除してみたところ再現しなくなった気がするので今はそれで様子を見ています。

追記

結局上記の方法でもダメで、色々調べたところマザーボード内蔵の ASMedia ASM1061 から出ているSATAポート(SATAAとSATAB)に接続しているデバイスがスリープ復帰後に応答しなくなっていることが原因だとわかりました。
そこで、試しにAsmedia 106x 向けのドライバ(3.3.5.0)を探して入れてみたところデバイスが応答しなくなる現象は無くなったものの、今度は代わりにデバイスが完全に認識されなくなる(取り外されたのと同じ状態になる)ようになりました。

仕方がないのでSATAAとSATABを使わずにZ690チップセットから出ているSATAポートを使うようにしたところ上記の問題は解決しました。

もしかしたらWindows 11との相性とかなのかもしれませんが、MPG Z690 CARBON WIFIを購入する場合はSATAAとSATABは無いものとして考えておいた方が良いでしょう。

Windowsのライセンス認証、外れる

流石にWindowsのライセンス認証は一旦外れてしまいました。

ただ、MSアカウントにライセンスを紐づけていたため ライセンス認証のトラブルシューティングこのデバイス上のハードウェアを最近変更しました とすることで無事に再認証されました。

大幅なハードウェア構成の変更前にはWindowsのライセンスをMSアカウントに紐づけているかどうかを事前に確認しておくのがおススメです。

MSIの製品登録の罠

今回初めてMSI製品を購入したのですが、マザーボードの製品登録を行う際にシリアル番号だけではなく マザーボード上のシールにしか記載されていない CHKというコードが必要でした。当然製品登録はPCを組み上げて問題なく動作することを確認してから行うものだと思っていたので*6、いざ製品登録を行おうとしたところでCHKコードの存在を知り膝から崩れ落ちました。

social.0ko.me

幸いCHKコードが記載されたシールはATX電源ソケットの側面に貼り付けられていたため、頑張って斜めから覗き込むことで辛うじてCHKコードを読み取ることができ、無事に製品登録を行えました。 これがマザーボードの裏に貼り付けられていたりしたら完全に詰んでいたのでそのまま製品登録を諦めていたと思います……。

MSIさん!今からでも遅くないのでマザーボードと一緒に「組み立て前に製品登録用のCHKコードを控えろ!」ってデカデカと書かれたメモを同封するなり何なりしたほうが良いですよ!

おわりに

今回Windows 11でCPUのサポートが切れることを主なきっかけとしてCPUをi7-6700K→i9-12900KFにアップグレードをしましたが、思っていた以上の性能向上が実現できてとても満足しています。

というわけで、以上i9-12900KFのレビューでした。

今回の記事に関連するパーツのAmazonアフィリンクを貼っておきます。 記事が参考になった場合は踏んでもらえるとありがたいです。

*1:Amazonとかアリエクとかで「USB HDMI キャプチャ」みたいなキーワードで探すと出てくるやつ。どうせ1080p30Hzしか出ないので買うなら700円ぐらいの一番安いやつで十分。

*2:https://pcrecommend.com/cpu/cinebench-r23/

*3:https://btopc-minikan.com/cpu-hikaku.html

*4:https://www.gdm.or.jp/review/2021/1104/414061/4

*5:https://pc.watch.impress.co.jp/docs/news/1344898.html

*6:そうじゃない場合は製品登録以前に初期不良でそのまま交換対応になりますよね?

VPNを使うとWSLからTLS接続ができなくなる

問題について

タイトルの通りです。
リモートワーク等でWindows標準のVPN機能を利用すると、何故かWSL内からTLS接続ができなくなるという現象が発生していました。

完全にインターネット通信が出来なくなる訳ではなく、例えばpingや平文HTTPなどは問題なく通信できます。

> ping google.com
PING google.com (172.217.174.110) 56(84) bytes of data.
64 bytes from nrt12s28-in-f14.1e100.net (172.217.174.110): icmp_seq=1 ttl=117 time=51.8 ms
64 bytes from nrt12s28-in-f14.1e100.net (172.217.174.110): icmp_seq=2 ttl=117 time=69.8 ms
64 bytes from nrt12s28-in-f14.1e100.net (172.217.174.110): icmp_seq=3 ttl=117 time=40.1 ms
64 bytes from nrt12s28-in-f14.1e100.net (172.217.174.110): icmp_seq=4 ttl=117 time=66.1 ms
^C
--- google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 40.121/56.953/69.763/11.801 ms
> curl http://google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>

ところがTLSHTTPS)接続を試してみると…。

> curl -vvv https://google.com
*   Trying 172.217.174.110:443...
* TCP_NODELAY set
* Connected to google.com (172.217.174.110) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* OpenSSL SSL_connect: Connection reset by peer in connection to google.com:443
* Closing connection 0
curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to google.com:443

このように途中でコネクションがリセットされてしまいます。

TLS接続だけかと思いきや、 apt install のような一部のHTTP通信でも通信できなくなる場合があります。

解決策

とりあえずWSLとVPNに関する通信関連のトラブルについて探してみると下のドキュメントがヒットします。

VPN に接続されると、bash のネットワーク接続が切断される
WindowsVPN に接続した後、bash のネットワーク接続が切断される場合は、bash 内からこの回避策を試してください。 この回避策により、/etc/resolv.conf を使用して DNS 解決を手動で上書きできます。

Windows Subsystem for Linux のトラブルシューティング | Microsoft Docs

症状的には似ていますが、どうやらDNSサーバを手動で設定する方法のようです。
今回の症状は、先に述べたようにpingやHTTPが通っているためドメイン名の名前解決自体は何の問題もなく行えています。
そのため、この対処法は今回のトラブルとは無関係でしょう。

さらに探しているとmicrosoft/WSLリポジトリのissueで次のようなコメントを見つけます。

May this could be a fix for you:
Set MTU to the value of the VPN interface:
sudo ifconfig eth0 mtu 1350

While connected to VPN the curl command hangs up · Issue #4517 · microsoft/WSL · GitHub

あー、なるほど。MTU値が原因ね。

というわけでMTU値を調べてみます。

まずはWindows側から

> netsh interface ipv4 show subinterfaces

   MTU  MediaSenseState  受信バイト   送信バイト  インターフェイス
------  ---------------  -----------  ----------  -----------------
  1376                1    1484402     847609  VPN
//...

ふむふむ。次はWSL側

# ip link show
//...
4: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
//...

はい、ビンゴです。
VPNによってMTU値が減少していたのが正しくWSL側に伝わっておらず、1376バイトを超えるパケットが経路上で破棄されていたため上手く通信できていなかったのでしょう。 pingやHTTPが通ったのはパケットのサイズが1376バイト以内に収まっていたからだと考えられます。
一方で、HTTP通信であるのも関わらず apt install が通らなかったのは、パケットのサイズが1376バイトを超えていたからでしょう。

対処法は上のコメントの通り、WSL側のインタフェースにも適切なMTU値を設定することです。
そうすることで、次のようにVPN経由でも問題なくインターネット通信が行えるようになりました。

# ip link set eth0 mtu 1376
# curl https://google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.com/">here</A>.
</BODY></HTML>

めでたしめでたし。

新しいEdgeでChromecastを使う

新しくなったEdge

2020年1月16日にChromiumベースの新しいEdgeがリリースされましたね.
自分はWindows Updateで適用されるのを待とうと思ってたのですが,どうも日本に対してはその稀有なIT事情*1に考慮して,Windows Updateでの配信は4月以後となるようです.
なので,早めに試してみたい場合は公式サイトからサクッとインストールしてしまいましょう.

Download New Microsoft Edge Browser | Microsoft

なんか環境によってLPは英語なのにライセンス条項がフランス語だったりするみたいですが,インストーラはちゃんと日本語のようです.

拡張機能

さて,ご存じの通りChromiumベースになったので,Chromeの機能が多数使えるようになっています.その中でも拡張機能は基本中の基本ですね.

パッと見Microsoft Storeにある拡張機能しかインストールできないような雰囲気ですが,拡張機能ページの左下にこっそりと配置されているスイッチをオンにすることで,なんとChrome Web StoreからGoogle Chrome向けの拡張機能を直接インストールできるようになります.

f:id:YDKK:20200118183645p:plain
f:id:YDKK:20200118183936p:plain

いやー,他社のインフラにそのままタダ乗りするスタイル.すごく良いですね.

ただし,ほとんどの拡張機能はそのまま動くようですが,Googleアカウントを用いた認証を行うような拡張機能*2などは上手く動作しないようです.おそらくその辺はChromiumではなくChrome側にある独自実装などと連携して動作しているのでしょうね.

Chromecast

さて,拡張機能の他にも,Chromiumベースになったことで使える機能に,Chromecastがあります.
ご存じの通り,Chromecastとはブラウザで再生してる動画や音声などをChromecastデバイスに対してキャストする機能です.例えば,SoundCloudの音楽を,Chromecast Audioを接続したスピーカで再生する,という風に便利に使えます.*3

ところが,どうも公式リリースされたEdgeでは,このChromecast機能が動作しないようです.

f:id:YDKK:20200118190028p:plain
SoundCloudのプレーヤ部分(上:Chrome,下:Edge)

自分はInsider版の頃から新しいEdgeを使っていたのですが,だいぶ前からChromecastには対応しており,技術的にEdgeがChromecastに対応可能であるのは間違いありません.

f:id:YDKK:20200118185547p:plain
Chromecast対応を伝えるInsider向け更新情報

単なるバグなのか,はたまたGoogle側と折り合いがつかなくなって削除されたのかなどは定かではありませんが,ともかく今のEdgeではChromecast機能が使えなくなっています.

EdgeでChromecastを使えるようにする

それでは不便なので,今回はEdgeで使えなくなっているChromecast機能を使えるようにする方法を探してみます.

とりあえずそれっぽいところを探してみると, edge://flags 内に #cast-media-route-provider という項目があり,コレを Enabled にすることで メニュー→その他のツール→メディアをデバイスにキャスト が使えるようになります.

f:id:YDKK:20200118190727p:plain

ただし,コレによって有効になるのはメディアのミラーリング*4のようで,WebサイトのCastボタンは相変わらず表示されないのでメディアのソースをキャストすること*5は出来ません.

ただ,ミラーリングだけでも使えるようになったことから,この辺のコンポーネントがChromecast機能に絡んでいることは間違いなさそうです.そこで,説明文中にある the Media Router component extension とやらについて調べてみます.

すると,どうもそのChrome Media Routerという組み込み拡張機能がChromecast機能の中核を担っているようです.
恐らく,Edgeではこの組み込み拡張機能が削除されたり,無効化されてしまっているのでしょう.

となれば話は早いもので,Chromecastが使える他のブラウザからこの Chrome Media Router 拡張機能を取ってくればいいのです.

Chromecastが使えるブラウザと言えばChromeですね.早速インストールしましょう.

Chrome内では,Chrome Media Router 拡張機能pkedcjkdefgpdelpbcmbmeomcjbeemfm というIDでインストールされているようです.Chromeのプロファイルディレクトリの中(たぶん %LocalAppData%\Google\Chrome\User Data\Default\Extensions あたり)を見てみると,同名のディレクトリが見つかると思います.

f:id:YDKK:20200118192608p:plain

このディレクトリを適当な場所にコピーしましょう.

そして,Edgeの 拡張機能 ページの左下から 開発者モード を有効化し,右上の 展開して読み込み ボタンからコピーした先のディレクトリ内にある 7919.1028.0.0_0 みたいな名前のフォルダ*6を指定して拡張機能を読み込みます.

f:id:YDKK:20200118193027p:plain

するとこんな感じで Chrome Media Router が読みこまれるので,この状態でChromecastに対応しているサイトにアクセスすればちゃんといつものCastボタンが表示され,きちんと機能するはずです.*7

ブラウザ起動時に表示される警告を抑制する

さて,コレで一件落着かと思いきや,ブラウザを再起動すると毎回こんなダイアログが表示されることに気が付くと思います.

f:id:YDKK:20200118193446p:plain

コレは文字通り,非正規の方法でインストールされた拡張機能*8は危険な可能性があるため,必要が無ければ無効化するように促すものです.

Chromeは少し前からChrome Web Store以外からの拡張機能のインストールを完全にブロックする方針であり,Chromiumを使ったEdgeもそのままこの実装に倣っています.先ほど Chrome Media Router をインストールした方法は,開発者が開発した拡張機能をテストするための方法であり,一般ユーザが野良拡張機能インストールの抜け道として使わないように毎回しつこく警告を出しているものです.

さて,このままでは毎回ブラウザを起動するたびにうっとおしいので,Chromecast機能が正式にサポートされるようになるまでのつなぎとして,少しでもこのダイアログの表示を抑制する方法を考えます.

調べてみると,Edgeの実行バイナリを書き換えて当該のダイアログを表示するコードを無効化する方法があるようですが,実行バイナリを書き換えた場合,DRMモジュールであるWidevineの呼び出し元バイナリの正当性検証が通らなくなってしまい,Amazon Prime VideoなどをはじめとするWidevine DRMを利用しているサイトのコンテンツが閲覧できなくなってしまいます.

なので,ここでは Chrome Media Router 拡張機能マニフェストを書き換えて,拡張機能をバックグラウンド実行するように改変します.

先ほどコピーしたディレクトリ内にある manifest.json ファイルを開き,permissions 配列の中に "background" を追加し,拡張機能をインストールし直しましょう.

f:id:YDKK:20200118195144p:plain

コレによって,ブラウザを閉じても Chrome Media Router 拡張機能がバックグラウンドで動作し続けるようになります.

f:id:YDKK:20200118195249p:plain

結果的に,警告の表示頻度がブラウザを起動する度たびから,PCを再起動するたびまで減るので,何とか実用に耐えうるんじゃないかと思います.

なお、Chromeの場合グループポリシで拡張機能のIDをホワイトリストに登録することでこのダイアログを抑制できるようですが、EdgeではグループポリシテンプレートがChromeのものと異なっており、同じ方法は使えませんでした。

フィードバックを送ろう

こんな面倒な作業をしなくても済むように,みんなで早く公式でChromecastをサポートするようにお願いするフィードバックを送りましょう.

f:id:YDKK:20200118195826p:plain

*1:日本のお客様に対しては、確定申告への影響を考慮し、Windows Updateを通じた新しいMicrosoft Edgeの配信は令和2年4月1日以降、順次開始される予定です。 https://blogs.windows.com/japan/2020/01/16/new-year-new-browser-the-new-microsoft-edge-is-out-of-preview-and-now-available-for-download/

*2:例えば,Google Keep Chrome 拡張機能など

*3:関係ないけどChromecast Audio再販してほしいな….

*4:ブラウザ内で再生した音声・動画を転送すること.帯域を多く使うし不安定.

*5:動画や音声のソースURLを送信して,Chromecast内で再生してもらうこと.

*6:多分一つしかないはず.

*7:edge://flags から設定を変更してた場合は元に戻しておきましょう.

*8:いわゆる野良拡張機能