Program Files の x64 EXE が ARM64 より多かった話
C:\Program Files の EXE 957本を PE ヘッダーで読むと、x64 が580本、ARM64 が315本だった(入口の体感とは違う並びになった)。
Surface Pro 11th Edition は ARM64 Windows なので、インストール済みの実行ファイルも ARM64 が多数派だろうと思っていた。ところが、2026-06-24 に普段の作業環境をそのまま数えてみると、期待とは逆の並びになる。メモリ 32GB、Snapdragon X 12-core X1E80100 @ 3.40GHz、Windows 11 Pro 10.0.26200 の組み合わせで、特別に検証用へ片づけた環境ではない(いつものアプリが入ったままの状態)。
もうひとつ引っかかったのが C:\Program Files (x86) だった。名前からは x86 の置き場に見えるのに、実際には ARM64 が99本、x86 が52本。フォルダ名と中身のアーキテクチャは、もう素直には対応しないものとして見たほうがよさそうだった。ここでいったん、自分の中にあった「ARM64 Windows なら Program Files もだいたい ARM64」という雑な見方を捨てることになった。
PE ヘッダーだけを読んだ
やったことは、対象フォルダの *.exe を列挙して、PE ヘッダーの Machine フィールドを読むだけ。Machine が 0xAA64 なら ARM64、0x8664 なら x64、0x014c なら x86 と分類した。実行はしていないので、インストーラーやアプリ本体の副作用は出ない(ここを混ぜると、調査というより棚卸し作業になってしまう)。
Get-ChildItem "C:\Program Files" -Recurse -Filter *.exe -ErrorAction SilentlyContinue |
ForEach-Object { Read-PeMachine $_.FullName }
同じ調べ方で、普段コマンドラインから呼ぶツールも見た。where.exe や Get-Command で入口を探し、そのファイルが PE なら Machine を読む。npm や VS Code のようなラッパーはこの段階で not-PE になるので、そこで一度止めている(実体まで追い切ると別記事の分量になる)。
$tools = "python", "node", "npm.cmd", "git", "dotnet", "ffmpeg", "ffprobe", "curl", "tar", "java", "javac", "code.cmd", "pwsh", "powershell", "docker", "gh", "winget", "wsl", "where.exe", "notepad"
$tools | ForEach-Object { Get-Command $_ | Select-Object Name, Source }
フォルダ走査には 4000本で打ち切る limit を入れていた。今回の count は C:\Program Files が957本、C:\Program Files (x86) が154本、LocalAppData\Programs が225本なので、手元の結果では上限の手前で収まっている。ただし、もっと大きい環境で同じスクリプトを回すなら、全数調査ではなく上限つきのサンプルになる可能性は残る。
よく使う20個はかなりARM64寄り
普段使っているコマンドラインツール20個を並べると、思っていたより ARM64 が多かった。python、node、git、dotnet、curl、tar、pwsh、powershell、gh、wsl、notepad は ARM64 で動いている。日常の体感が「だいたいネイティブっぽい」と感じていた理由は、たぶんこのへんにある。
一方で、動画系と Java まわりは x64 だった。ffmpeg と ffprobe は winget で入れた Gyan.FFmpeg 8.1.2-full_build、java と javac は Eclipse Adoptium jdk-8.0.492.9-hotspot。ここを見落として動画処理や古い Java の時間を測ると、ARM64 ネイティブのベンチマークだと思いながら、実際には x64 エミュレーション込みの数字を読んでしまう。
| ツール名 | アーキ | パス |
|---|---|---|
| python | ARM64 | C:\Users\akirasakai\AppData\Local\Programs\Python\Python312-arm64\python.exe |
| node | ARM64 | C:\Program Files\nodejs\node.exe |
| npm.cmd | not-PE | C:\Program Files\nodejs\npm.cmd |
| git | ARM64 | C:\Program Files\Git\cmd\git.exe |
| dotnet | ARM64 | C:\Program Files\dotnet\dotnet.exe |
| ffmpeg | x64 | C:\Users\akirasakai\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.2-full_build\bin\ffmpeg.exe |
| ffprobe | x64 | C:\Users\akirasakai\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.1.2-full_build\bin\ffprobe.exe |
| curl | ARM64 | C:\Windows\System32\curl.exe |
| tar | ARM64 | C:\Windows\System32\tar.exe |
| java | x64 | C:\Program Files\Eclipse Adoptium\jdk-8.0.492.9-hotspot\bin\java.exe |
| javac | x64 | C:\Program Files\Eclipse Adoptium\jdk-8.0.492.9-hotspot\bin\javac.exe |
| code.cmd | not-PE | C:\Users\akirasakai\AppData\Local\Programs\Microsoft VS Code\bin\code.cmd |
| pwsh | ARM64 | C:\Program Files\PowerShell\7\pwsh.exe |
| powershell | ARM64 | C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe |
| docker | not-PE | C:\Program Files\Docker\Docker\resources\bin\docker |
| gh | ARM64 | C:\Program Files\GitHub CLI\gh.exe |
| winget | not-PE | C:\Users\akirasakai\AppData\Local\Microsoft\WindowsApps\winget.exe |
| wsl | ARM64 | C:\Windows\System32\wsl.exe |
| where.exe | ARM64 | C:\Windows\System32\where.exe |
| notepad | ARM64 | C:\Windows\System32\notepad.exe |
この表だけを見ると、ARM64 Windows への移行はかなり進んでいるように見える。けれど、これは「自分がよく叩く入口」だけの話で、使う頻度とインストール本数は別物なので、ここを混ぜると判断を間違える(まさに最初の自分がそうだった)。
フォルダ全体では別の景色になる
フォルダ別に見ると、C:\Program Files だけが x64 優勢だった。LocalAppData\Programs は ARM64 が172本で、x64 の30本よりかなり多い。Microsoft Scout、GitHub Copilot、VS Code など、ユーザー単位で入るアプリが ARM64 側に寄っているのだろう(この差は、インストール場所ごとの性格も混ざっていそうだ)。
| フォルダ | 走査した EXE | ARM64 | x64 | x86 |
|---|---|---|---|---|
C:\Program Files |
957本 | 315本 | 580本 | 62本 |
C:\Program Files (x86) |
154本 | 99本 | 3本 | 52本 |
C:\Users\akirasakai\AppData\Local\Programs |
225本 | 172本 | 30本 | 23本 |
容量で見ると、C:\Program Files の ARM64 は2722562136 bytes、x64 は1365529288 bytes だった。件数では x64 が多いのに、サイズでは ARM64 が大きい。この逆転は少し気持ち悪いが、dotnet や GitHub CLI のような大きめのネイティブ実行ファイルが効いているのかもしれない。ファイル名ごとの内訳までは追っていないので、ここは推測止まり。
C:\Program Files (x86) の ARM64 には Google Update や Microsoft Copilot が入っていた。x86 フォルダという名前に引っ張られると勘違いする。Windows のフォルダ名は互換性のための約束であって、そこに置かれているバイナリのアーキを保証するものではなかった、というだけの話。
not-PE で一度止まった
小さな失敗は not-PE の扱いだった。npm.cmd、code.cmd、docker、winget は PE ファイルではない。バッチファイル、拡張子なしのシム、WindowsApps の実行エイリアスが混ざるので、ヘッダーを読むだけでは ARM64 か x64 かを決められない。
ここで無理に分類しなかったのはよかったと思っている。npm や code と打って動くからといって、その入口が実行ファイルとは限らないし、ラッパーの先にある実体まで見ないと本当の実行アーキは別の確認が必要になる。今回の記事では、わからないものを not-PE として残した(少し中途半端ではあるが、捏造するよりはまし)。
ffmpeg も一瞬だけ見誤りそうになった。パスが WinGet\Packages の下にあるので、winget 自体のアーキと混ぜて考えそうになる。実体の ffmpeg.exe を読むと x64 で、ffprobe も同じだった。動画処理のベンチマークを書くなら、この確認を先に入れておくほうが安全だ。
ベンチ前の確認手順を変えた
この測定のあと、ベンチマークを書く前の確認手順を少し変えた。対象コマンドの Get-Command を見て、PE ヘッダーを読み、not-PE ならラッパーの先を追ってみる。面倒ではあるが、ARM64 Windows ではここを飛ばすと、ネイティブ実行なのかエミュレーションなのかを取り違えやすい。
2026年6月24日の私の環境では、「よく使うコマンドは ARM64 が多いが、C:\Program Files 全体では x64 が多い」という分かれ方になった。体感が軽いから全部ネイティブだろう、とは置けない。逆に、x64 が580本あるから日常が全部エミュレーションというわけでもない。
次に測るなら、DLL とラッパーの先まで含めたプロセス単位の混在を見たい。今回の方法は EXE の Machine フィールドだけなので、アプリが実際に読み込んでいる DLL までは見えていない。そこまで見ると、ARM64 と x64 の境界はもう少しぐちゃっとした形になるのではないだろうか。