第2回 Win32アプリケーションを実行するWOW64:64bit Windows時代到来(2/2 ページ)
互換性が重視されるWindow OSでは、64bit版Windows OSでも32bitアプリケーションのサポートが必須。それを可能にするWOW64とは何か? x64 CPUについても解説する。
64bit CPUとは何か?
64bit OSを利用するためにはAMD64やIntel 64といった64bit CPUが必須である。具体的には、インテルの「Intel 64」かAMDの「AMD64」という機能を備えたCPUが必要となる。ローエンドのCPUを除き、最近ではこれら64bit命令セットをサポートしたCPUが多く出荷されているし、すでに使っているユーザーも多いかもしれない。
64bit CPUとは一般的に、汎用レジスタ(数値データやポインタなどを格納しておくための一時的な領域)のデータ幅が64bitになっているCPUのことを指す。32bitのx86アーキテクチャを拡張して設計されたx64アーキテクチャの場合は、次のようになっている。
64bit CPU(x64アーキテクチャ)のレジスタ・セット
x64アーキテクチャのCPUをプログラマの視点から見た場合のモデル。64bit幅の汎用レジスタが16本利用できる(x86では32bit幅のレジスタが8本しかなかった)。汎用レジスタにはポインタや整数データを格納できる。ポインタとして利用する場合は64bit全体を利用するが、数値データを格納する場合は8bitや16bit、32bitだけを利用することも可能。なお悪名高きセグメント・レジスタも未だに残っているが、64bitモードで使用している場合はその役割は非常に限定的である。SIMDレジスタは浮動小数点データやパック形式の整数データを格納し、マルチメディア・データや3DグラフィックスなどのSIMDデータ処理で利用される。ただしSIMD用レジスタやSIMD命令はx86やx64という基本的なアーキテクチャとは関係なく、CPUの進化に合わせて、随時機能強化されている。
以下、x64アーキテクチャにおける強化点などについて見ていこう。
■拡張された汎用レジスタ群
32bitのx86アーキテクチャと比較した場合の一番の違いは、各レジスタのデータ幅が32bitから64bitに拡大され、さらにレジスタの本数も8本から16本に増えているところにある(R8〜R15は64bitモードでのみ利用可能)。
レジスタ幅が64bitになっているため、ポインタとして利用すると、理論的には2の64乗bytes(16Exabytes)のアドレス空間を1つのレジスタで指し示すことができるし、数値データを格納すれば、64bit幅のデータを1つの命令で処理(四則演算や論理演算などが)できる。4Gbytes以上のメモリをアクセスするためには64bit幅のポインタは必須だし、32bitを超えるデータ(64bitのデータ)でも1つの命令で処理できる。ただし必要に応じてレジスタを8bitや16bit、32bitにも分割して利用できるようになっている。図中に記述されているALやAX、EAXは64bitのRAXレジスタを分割して利用する場合の呼び名である(場合によってはAXレジスタの上位8bitだけを使うといったことも可能)。
またレジスタの本数が増えているため、外部メモリを使わずにレジスタ内だけで処理できるケースが増え、その分パフォーマンスの向上が期待できる。従来のx86アーキテクチャでは汎用レジスタは8本しかないため、関数内で少し複雑な処理を行おうとすると(C言語でいえば、関数内の処理コードが長くなると)どうしても処理中にいくつかのレジスタの内容をメモリに退避しなければならなかった。CPUには高速なキャッシュ・メモリが装備されているとはいえ、メモリに対して読み書きを行うと、結局は(キャッシュと比較すると)低速なメイン・メモリへのアクセスを引き起こすし、マルチコア・システムであれば、CPUやコア間でのアクセスの調停などのオーバーヘッドが必要になる。レジスタが多ければ、メモリへのアクセスを抑えることで、同じ処理であってもx86の場合よりも高速に実行できる可能性が高くなる。
■強化された命令セット
x64アーキテクチャは、32bitで主流であったx86アーキテクチャをベースにして、64bitレジスタの追加や64bit演算が可能なように拡張したアーキテクチャである。元のx86の命令セットをそのままにして64bit処理命令などを付け加えているため、煩雑な命令コード体系などはほぼそのままだが、アドレッシング・モードを整理するなどして少し改良されている。もっとも、現在では手動でアセンブリ言語のコードを書くこともないため、あまり気にする必要はないが。
■64bit版Windows OSにおけるデータ・モデル
64bit CPUだからといってすべてのデータを64bit幅として扱う必要はない。例えばC/C++言語のchar型は8bitで構わないし、int型も32bitで十分なことが多い。intやlong型を無理に64bit幅に拡大してしまうと、データを格納するのに必要なメモリ領域も増えるし、メモリ・アクセス時のデータ転送量も増え、結果的にパフォーマンスの低下を招く可能性がある。そこで64bit版のWindows OS(Win64)では、いわゆる「LLP64」というデータ・モデルを採用している。
モデル | short | int | long | long long | ポインタ | OS |
---|---|---|---|---|---|---|
― | 16bit | 32bit | 32bit | 64bit | 32bit | Win32 |
LLP64 | 16bit | 32bit | 32bit | 64bit | 64bit | Win64 |
LP64 | 16bit | 32bit | 64bit | 64bit | 64bit | UNIXなど |
ILP64 | 16bit | 64bit | 64bit | 64bit | 64bit | UNIXなど |
64bit CPUにおけるデータ・モデルの種類 32bitアーキテクチャから64bitアーキテクチャへの移行に当たって利用されるデータ・モデル。64bit CPUへの移行はWindows OSだけでなく、UNIXやLinuxなど、ほかのOSでも直面する問題である。64bit CPUなのだからint型やlong型も64bitにするのは自然な発想であり、UNIXやLinuxなどではこのモデルを採用していることが多い。これに対してWindows OSの場合は過去との互換性を最大限重視して、int/long型を32bit幅のままにしている。Iはint、Pはポインタ、Lはlong、LLはlong longの略。例えばLLP64とは、long longとポインタが64bitという意味。 |
LLP64とは、long long型(Visual C++でいうと__int64型)と、ポインタは64bitにするが、int型とlong型は32bit版のWindows OS(Win32)の場合と同様に、32bitのままにするというデータ・モデルである。64bitプログラムであってもintは32bitで十分なことが多いので、このようなモデルが使われることがある。Windows OSの場合は、32bitのプログラム(Win32アプリケーション)との互換性を重視して、LLP64モデルが採用されている。LLP64モデルでは、int型もlong型も32bitの場合と同じである。そのためソース・コードに対してほとんど修正を加えることなく、ほぼそのまま利用できる。特に、Win64のAPI呼び出しのパラメータもそのまま利用できるので、移植性が高くなる(ただしポインタは64bitに拡大されているので、ポインタをバイナリ・データとして取り扱う場合などは修正が必要)。
■Windows OSにおける関数の呼び出し規約
関数の呼び出し規約とは、アプリケーション内の関数呼び出しやDLL呼び出しなどにおいて、パラメータ(引数データ)をどのようにして渡すか、結果をどのようにして返すかなどを決めた規定である。これが異なっているとプログラムを相互に呼び出すことができないし、外部のライブラリを呼び出すこともできなくなる。
64bit版のWindows OSでは、x64アーキテクチャで増加したレジスタを活用し、パラメータをなるべくレジスタ経由で受け渡すことにしている。32bit版のWindows OSの場合はスタック(メモリ)を使ってパラメータや結果を受け渡ししていたので、どうしてもメイン・メモリへのアクセスを避けることができなかった。このような呼び出し規約の改善の結果、若干だが64bit版の方がパフォーマンスが向上する可能性がある。
項目 | 32bit Windows OS | 64bit Windows OS |
---|---|---|
関数内で保存しなくてもよいレジスタ | eax、ecx、edx | rax、rcx、rdx、r8、r9、r10、r11 |
関数内で保存すべきレジスタ | ebx、esi、edi、ebp | rbx、rsi、rdi、rbp、r12、r13、r14、r15 |
__cdecl呼び出し | ・引数は右から左へ評価して、スタックへプッシュ ・呼び出し側がスタックを戻す |
・最初の4つのパラメータはrcx、rdx、r8、r9で渡す ・最初の4つの浮動小数点数はxmm0〜xmm3で渡す ・最初の4つのパラメータ分のスタックは呼び出し側で予約しておく ・5つ目以降はスタックへプッシュ ・呼び出し規約は1種類のみ |
Win32(__stdcall)呼び出し | ・引数は右から左へ評価して、スタックへプッシュ ・呼び出された側がスタックを戻す |
|
結果の戻し方 | eaxもしくはedx:eax | raxもしくはxmm0 |
32bitと64bitのWindowsプログラムにおける関数の呼び出し規約 関数呼び出しにおける引数や結果の渡し方、および関数内で保存するべきレジスタはOSや環境によって決まっている。この規約のおかげで、関数やDLL、言語によらず相互に呼び出しが可能になっている。64bit版のWindows OSでは引数や結果はなるべくレジスタを使って受け渡すことにより、メモリへのアクセスを抑え、パフォーマンスの向上を図っている。 |
64bitコードの例
64bitプログラムでは実際にどのようなコードが利用されているのか、その一部を見てみよう。これはデバッガでメモ帳(notepad.exe)のコードを表示させたところである。
■32bit版コードの例
■64bit版コードの例
これは32bit版と64bit版のメモ帳のコード例。デバッガで(Debugging Tools for Windows。Windowsの開発者向けツールに含まれている)、Windows 7の32bit版と64bit版のメモ帳の一部を逆アセンブルしている。
少々分かりづらいかもしれないが、32bit版の方はメモリへのアクセスが多いが(関数呼び出しのcall命令の前に、引数渡しのためにpush命令が多く使われている)、64bit版の方はレジスタを多く使い、メモリへのアクセスは少なくなっている。メモ帳のようなインタラクティブなアプリケーションでは差はほとんどないが、x64 CPUで増加したレジスタをうまく使うことにより、CPUバウンドな計算処理ならパフォーマンスが大きく向上する可能性がある。
64bitプログラムは32bitプログラムよりも速いのか?
さて大幅に機能強化されている64bit CPUと、高速化を考慮しているWindows OSの64bit呼び出し規約であるが、64bitネイティブのアプリケーションを実行するとどの程度高速化されるのか、気になるところである。そこでCINEBENCH R11.5というベンチマーク・プログラムを実行してみた。
項目 | 32bit | 64bit | 64bit÷32bit |
---|---|---|---|
CINEBENCH R11.5 OpenGLベンチ | 57.42fps | 60.10fps | +4.7% |
CINEBENCH R11.5 CPUベンチ | 3.72pts | 4.04pts | +8.6% |
ベンチマーク結果 Core i7-930 2.8GHz(HTTオフ)、メモリ12Gbytes、Radeon HD5770、Windows 7 Ultimate x64 Editionというシステム上で、32bit版と64bit版のプログラム(CINEBENCH)を実行して測定。32bit版と64bit版のアプリケーションの違いを見るのが目的なので、どちらも同じ64bit版のWindows 7上で測定している。 |
32bit版と64bit版のアプリケーションの違いを見るのが目的なので、どちらも同じ64bit版のWindows 7上で、CINEBENCHというベンチマーク・プログラムを実行している。
OpenGLベンチはグラフィックス処理が多く、CPUのbit数はあまり関係しない処理である。64bit化による速度の向上は5%弱しかない。CPUベンチはレイトレーシングによるグラフィックスの計算を行う処理である。1割弱だが64bitの方が速くなっていることが分かる。もっとメモリを使ったり、CPUによる計算を多用したりするアプリケーションならばこの差はもう少し開くと思われる。
ただし、64bit CPUにしたからといって、32bit CPUの2倍速くなるといったことはまずない。せいぜい1割から2割高速化する程度である。ほかにもいくつかベンチマークなどを実行してみたが、そもそもクライアント用途向けでは32bit OSでも十分なプログラムやアプリケーションが多く、64bit OSでなければ実行できないといったプログラムはほとんどない。となると、現状ではやはりオーバー4Gbytesのメモリが利用できる、という点が64bit Windowsの一番のメリットといえるだろう。なおサーバ用途ではすでに64bit専用アプリケーションがいくつか登場しているし、大容量のメモリを要求する処理も少なくないので、サーバ環境では64bitシステムの優位性が揺らぐことはないだろう。
Win64で広がるプロセス・アドレス空間
次は64bit Windows環境におけるプロセス・モデルについてみてみよう。前回述べたように、64bit Windowsを利用する大きなメリットの1つとして、アプリケーションで利用可能なメモリが増加するという点がある。具体的には、次のようにユーザー・プロセス空間が拡大する。
アドレス空間 | 32bit | 32bit /3GB | 64bit |
---|---|---|---|
ユーザー空間 | 2Gbytes | 3Gbytes | 8Tbytes |
カーネル空間 | 2Gbytes | 1Gbytes | 16Ebytes*1 |
Windows OSで利用可能な最大アドレス空間サイズ 「ユーザー空間」は、1プロセスごとに利用可能なメモリ・アドレス空間。1つのプロセスごとに例えば32bitのWindows OSでは最大2Gbytesのアドレス空間が利用できる。「32bit」は32bit Windows OS(Win32)、「64bit」は64bit Windows OS(Win64)におけるアドレス空間割り当て。「32bit /3GB」は「/3GB」オプションを有効した32bit版Windows OS。このオプションを有効にすると、ユーザー空間が1Gbytes拡大される。これは32bit版Windowsでのみ指定可能なオプション(関連記事のTIPS参照)。 *1 64bit版のWindows OSではその潤沢なアドレス空間を利用して、64bitアドレス空間の末尾に256Tbytes程度の領域を確保し(アドレス0xffff0800_00000000以降)、その中に各種カーネル・コンポーネントなどを分散して配置している。 |
従来の32bit Windowsでは、1プロセス当たりのユーザー空間は最大2Gbytesであったものが、64bit Windowsでは8Tbytes(8192Gbytes)にまで拡大している。もちろんこれだけのサイズのプログラム・コードやデータを作成するわけではない。実際にはユーザー・プロセス空間には、プログラム・コードやデータ、スタックなどが置かれるだけでなく、ファイル・マッピングや共有メモリ、DLLなど、さまざまなものが配置されるので、広すぎるということはない。
なお64bit CPUなのだから、最大で16Ebytes(2の64乗)のプロセス空間をサポートすることも不可能ではないだろうが、実装上の理由などにより、現在ではこのサイズに制限されている。そもそもWindows OSで管理可能な最大物理メモリ・サイズは2Tbytesしかないし(Windows Server 2008 R2 Enterprise Editionにおける制限。上述の関連記事参照)、現在販売されている実際のCPUでは、物理アドレス・バス幅は最大でも48bitしか出力されておらず、256Tbytes(2の48乗)までしかアドレッシングできない。仮想アドレス空間を広くすると、ページングのための管理用メモリ領域(仮想アドレス空間を指し示すためのページ・テーブル・エントリなど)が多く必要になり、メモリの利用効率が悪くなる。そのため、現在のWindows OSではこのような制限が設けられている。
今回は、64bit Windows上でWin32アプリケーションを実行するためのWOW64や、64bit CPUおよび64bitのプロセス・モデルについて取り上げた。次回は互換性問題について取り上げる。
Copyright© Digital Advantage Corp. All Rights Reserved.