3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

カーネルの深淵を覗いた男、USBハブを抜いて帰る

3
Last updated at Posted at 2026-06-20

VRChatで顔トラッキングを使うとCPUの1コアが99%占有されてゲームが14fpsに落ちた。「カーネルレベルの問題だ」と確信して、割り込みアフィニティ、MSI-X、バイナリパッチ、コールドブート、USBポート総入れ替え……Windowsカーネルの深淵を6時間さまよった。見つけた答えは「USBハブを抜く」だった。

高度な技術的調査がケーブル抜き差しに帰結する、ITあるあるの極致の記録。

環境

CPU: AMD Ryzen 9 9950X3D(デュアルCCD、V-Cache 96MB)、GPU: RTX 5080、HMD: Bigscreen Beyond(内蔵カメラ2台+多段USBハブの魔窟)、顔トラッキング: Baballonia (ProjectBabble)。デバッグ要員としてClaude Code (Opus) と Grok (Composer 2.5) を投入した。

異変

ある日、Baballoniaで顔トラッキングを有効にしたらCore 1のDPCが99%に張り付いた。

Core 1 DPC: avg=51.2% max=99%
VRChat FPS: 55fps → 14fps → 55fps (20秒周期でガクガク)

犯人はusbvideo.sys。Windows標準のUVCカメラドライバ。「ソフトウェアの問題だ。レジストリで直せる」。そう思った。

割り込みアフィニティという名の幻想

まず、USBコントローラの割り込みをV-Cache CCD (Core 0-15) から追い出そうとした。

# CCD1(Core 16-31)に割り込みを固定する、完璧な計画のはずだった
Set-ItemProperty $path -Name 'DevicePolicy' -Value 4
Set-ItemProperty $path -Name 'AssignmentSetOverride' -Value ([byte[]](0x00,0x00,0xFF,0xFF))

再起動。計測。Core 1のDPC、変わらず。

おかしい。割り込みは確かにCCD1に移動した。なのにDPCだけがCore 1に居座っている。

ここで知った。割り込みとDPCは別物だ。割り込みがどこで処理されようが、usbvideo.sysは「DPCはCore 1でやる」と内部で決めていた。アフィニティはDPCには届かない。

0勝1敗。

MSI-X、お前もか

「MSI-Xを有効にすればアフィニティが効くかもしれない」。レジストリにMSISupported=1を追加してコールドブート。

MSI-Xは確かに有効になった。IRQ番号がMSI-Xベクター範囲に入ったし、割り込みはきれいにCCD1に分散された。

DPCはCore 1に引きこもったまま。

AssignmentSetOverrideが4バイトなのが悪いのか? 64ビットWindowsだから8バイト要る?」。修正。コールドブート。

変わらず。

0勝3敗。

禁断のバイナリパッチ

方向を変えた。Baballoniaの中を覗くと、OpenCVのDirectShowバックエンドが口カメラを120fps、目カメラを90fpsでストリーミングしていた。合計210 DPC/秒。そりゃCore 1も死ぬわ。

MSMFバックエンドなら30fpsでネゴシエートされる。だがBaballoniaはDirectShowをハードコードしていて、設定ファイルにバックエンド指定はない。

仕方ない。9KBの.NET DLLをバイナリパッチした。

# IL命令 ldc.i4 を直接書き換え: DSHOW(700) → MSMF(1400)
$bytes[634] = 0x78  # was 0xBC
$bytes[635] = 0x05  # was 0x02

口カメラのDPCは激減した。が、目カメラのBigeyeは90fps固定のファームウェア仕様でFPS制限が効かない。Bigeye込みだとまだ60%。

ファームウェアには勝てないのか。

0.5勝3.5敗。半分だけ効いた。

ポートガチャ

「コントローラを変えればDPCのターゲットコアが変わるかも!」

背面Type-AからType-Cに移動したら、コントローラがDEV_15B7からDEV_15B6に変わった。計測……4.9%!

と思ったらカメラのストリーミングがまだ始まってなかっただけだった。始まったら96%に戻った。

USBポートの入れ替えを繰り返して分かったのは、このマザーボードのUSB 3.0 Type-Aポートが全部同じコントローラだということ。別コントローラのポートはUSB 2.0しかない。

2.0に挿したらDPCは下がったけど、HMDに「3.0に挿せ」と怒られた。

0.5勝5.5敗。

2体のAIが「無理」と言った

ここまでにClaude CodeとGrokを並行投入し、カーネルの仕様書を読み漁り、Web検索を回し、X (Twitter) まで漁った。

Grokの最終回答は冷酷だった。

usbvideo.sysのDPCターゲットはドライバ内部の設計。レジストリやOS設定では変更不可。Bigeyeのファームウェアに30fpsモードがない限り、ソフトウェアでの解決は不可能。

Claude Codeも同じ結論。2体のAIが揃って「無理」と言った。

人間の直感

全てのソフトウェア的アプローチが尽きた後、ふと思いついた。

「ハブを経由しないで、直接口のカメラを挿してみよう」

Core 1 DPC: 96% → 4.2%
VRChat FPS: 14fps → 75fps 安定

は?

HMDの中にはUSBハブが5段カスケードされていて、その最深部にあるVIA製ハブ (VID_214B) を通るとisochronous転送にジッターが発生してDPCが爆発していた。

PC ──[USB]──> Microchip Hub x4段
               ├── Bigeye (目) ← 問題なし
               ├── Beyond Audio
               └── VIA Hub (VID_214B) ← こいつ
                    └── 口カメラ ← ここを通ると死ぬ

VIAハブを迂回して口カメラをMicrochipハブ側に直接つないだだけ。それだけで全部直った。

6時間の戦果

やったこと 効いたか 所要時間
割り込みアフィニティ 無駄 1時間
MSI-X有効化 無駄 30分
8バイトKAFFINITY + コールドブート3回 無駄 1時間
.NET DLLバイナリパッチ 半分だけ 1時間
USBポート総入れ替え 無駄 1時間
Grok相談3回 知見は得た 30分
Web/X検索 前例なし 30分
USBハブを抜く 完全解決 10秒

割り込みアフィニティの仕組み、MSI-XとIOAPICの違い、DPCのターゲットコア決定メカニズム、UVCのisochronous転送の帯域制約、.NET ILのオペコード体系。全部勉強になった。なったけど。

次からはまずハブを疑う。

技術的に得たもの

笑い話だけで終わるのはもったいないので、せっかくだから書いておく。

DPCと割り込みは独立している

割り込み(ISR): MSI-Xで複数コアに分散できる
DPC: ドライバが KeSetTargetProcessorDpc で明示指定する → 割り込みアフィニティでは動かない

ユーザー空間からは見えないけど、「割り込みを移動すればDPCも移動する」というのは間違い。ISRが完了したコアで自動的にDPCが走るわけではなく、ドライバが自分でターゲットを指定する。

カメラのFPSがそのままDPC頻度になる

UVCカメラのフレーム完了1回につきusbvideo.sysのDPCが1回走る。120fps + 90fps = 210回/秒。これが1コアに集中すると飽和する。DirectShowはデフォルトでカメラの最大FPSをネゴシエートするが、MSMFは30fpsを選ぶ傾向がある。この違いだけでDPCが7分の1になった。

USBハブの品質がカーネル負荷に直結する

USB 2.0ハブのisochronous帯域は上流480Mbpsを全ポートで共有する。多段カスケード + 複数カメラ + オーディオが重なると、フレーム完了タイミングにジッターが入り、DPC処理時間が膨れる。VIA製ハブ (VID_214B) はUVCカメラとの相性が特に悪かった。

AIデバッグは強いが、ハードウェアは抜き差ししないとわからない

Claude CodeとGrokの2体を投入して、レジストリ操作からバイナリパッチ、USB記述子の解析まで自動化した。カーネル仕様の理解はAIの方が圧倒的に速い。でも最終的に問題を解いたのは「ハブ外してみたら?」という人間の手だった。

AIはソフトウェアの問題空間では無敵に近い。でも「このハブの品質が悪い」なんてことは、物理的に抜いてみないと誰にもわからない。AIとデバッグするなら、ソフトウェアに深く潜る前にハードウェアの変数を先に潰した方がいい。6時間かけて学んだ、一番大きな教訓がこれ。


作業セッション記録をもとにClaudeCodeが作成した記事です。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?