Windowsでのメモリアドレッシングにまつわるお話

 x86系のIA-32アーキテクチャのCPUでは1プロセスでアクセスできるアドレスは4GBまでという認識が割と支配的だが、実はそうではない。川俣さんのIT都市伝説「32bit CPUはメモリを4GBまでしか使うことができない?」にあるように、(CPU以外の)ハードウェアの仕様、OSの仕様、開発ツールの仕様などによって制限されていることが多い。たとえば、デスクトップ用のチップセットでは2GB以下しかメモリが積めないことも珍しくない(私が使っているi815チップセットでは最大512MBだ)。

 今回例に出すのはIA-32版Windows Server 2003(以下Windowsとした場合は断りのない限りWindows Server 2003とする)。Windowsの場合、アクセスできる物理メモリはStandardで4GB、Enterpriseで8GB、Datacenterで64GBまでという仕様になっている。これらすべてのエディションのWindowsにおいて、何も考えずに作ったプログラムは1プロセスあたりでアクセスできるメモリアドレスは下位の2GBまでとなる(残りの上位2GBのアドレスはOSのモジュールが使用する)。

 しかし、2GBではどうしても足りないという場合がある。一般的に1プロセスで大量の動的なメモリを確保するようなプログラム、たとえばOracle,SQL ServerなどのデータベースやExchange Serverなどである。私はこれらのなかではOracleしか使っていないが、Oracleを専用サーバモード(デフォルト)で使った場合、1コネクション(プロセスが一つConnectを実行すると)あたり、1MBちょっとのメモリと1スレッドを使う。

 200接続なら200MB強と200スレッド。1つのプロセスであっても、200のプロセスであっても同じ、わかりやすい。当然、Oracleの作業領域(SGA)を大きくとっていると、その分同時接続数の上限は減る。ものすごく簡略化すれば、2GB - (1MB x 接続数 + SGAのサイズ + Oracleそのものが使っているコード)という計算式になる。

 ここで2GBを超えないような接続数を探さなくてはならいが、それよりはオンライントランザクションであれば共有サーバモードを使う。これだとOracleの1接続を複数のクライアントで共有するという形になり、メモリがとても助かる。

 それでも「専用サーバモードがいい」だの、「共有サーバモードですら足りない」といった場合、どうすればよいか?Windowsにはboot.iniのオプションで/3GBと指定すると、「OSの作業領域を1GBまでにして、残りの3GBをユーザプロセスでアドレスできるようにする」という仕組みがある(4GB RAMチューニング)。

 ただ、これは私の体験上「動くかどうかはやってみないとわからない」世界になる。カーネルのメモリを圧迫するため、カーネルの資源を使うようなもの(ドライバであったり、通常のプロセスであったり)が増えると動かなくなるというパターンだってあり得る。OracleやSQL Serverのみしか動かさないというような環境向けだと思う。実際に、ちょっと多くプロセスが起動しているサーバで評価中にカーネルがパニックを起こしてブルースクリーンになったことだってある。

 Windows 2000 Serverの/3GBオプションは2+2GBか1+3GBにしか設定できなかったが、Windows Server 2003では割合を少し細かく設定できるようになった(サポート技術情報:833721の/uservaオプションと技術情報:316739)。これも正解はないので、それぞれで試す必要があるだろう。なお、4GT(4GBチューニングのこと)を施した環境で正しく3GBのユーザエリアを使うためには実行可能ファイルのヘッダに「MAGE_FILE_LARGE_ADDRESS_AWARE」が指定されていなくてはならない。

 前出の川俣さんのIT都市伝説にあるように、IA-32アーキテクチャのCPUであっても4GB以上の物理メモリを認識することができる。その機能を有効にするのがWindowsでは/PAEというオプション。/3GBと同様、boot.iniに指定すればよい(技術情報:833721)。これは「一つずつはメモリをそんなに(2GB)は使わないけれど、大量のプロセスが稼働する」ような環境で、物理メモリをたくさん積みたいサーバ向け。

 もっとも、サービスとして大量のプロセスを稼働させると、おそらくデスクトップヒープが先になくなる(サポート技術情報:184802あれ、これにはWindows NT4.0 SP7とあるぞ(^^;)。デスクトップヒープがどのように消えているのかは非公開情報なので、普通はカーネルデバッガでちまちま調べなくてはならないが、現在はMicrosoft Support Professionals Toolkit for Windowsでも調べられる。デスクトップヒープはWindows 3.1時代のGDI/USERリソース同様に上限があるので、レジストリをいじって少し増やすくらいの余地しかない。

 では、プロセスが巨大なメモリを扱う方法がないのか?これもちゃんと用意されている(※1)。WindowsではAWEと呼ばれるAPI群を使用して、4GB超のメモリを扱う事ができる。しかし、これをやるとOracleでは同時接続数が少なくなってしまう(ページング領域として一部のメモリを3GB以下の領域に予約してしまうから)という副作用がある。SQL Serverではどうなっているか知らない。ページングの領域を大きくとれば当然接続数が減り、小さくしてしまうとページング(この場合3GB超の領域とのページング)が頻繁に発生することになる。もっとも、こんな苦労をするくらいなら今時はさくっとIA-64のマシン買った方がいいと思う(^^;。たぶんエンジニアが四苦八苦して、評価を繰り返すよりよっぽど安い。私もお仕事で大きなところだと「IA-64にしませんか?」とはしばしば言う(通ったためしないけど)。

※1:i386時代にDOS-Extenderを使用していたときのようだ

 最後に、論理アドレスのTIPS。Windows(この場合は32bit版のWindows全般)ではベースアドレスが決められている。たとえば、kernel32.dllをDependency walkerやdumpbin /headersでみてみればロードアドレスが指定されていることがわかる(image baseのところ)。通常dllを生成すると、初期値で指定されているアドレスにロードしようとして失敗→新しいアドレスを計算となってちょっぴりペナルティを食らう。これをあらかじめ指定しておくことにより、再計算が発生しなくなるのでプロセスのロードが早くなる。

 さらに、アプリケーションエラーが発生したときも、このアドレスとmapファイルがあれば(リンカに/mapをつけて生成する)どの関数で死んだか、あるいは異なる原因か位は少しの時間でわかる。もっといえば、デバッグシンボルを該当マシンに入れていれば、ワトソン博士にも関数名が出てくる(最近はhotfixにデバッグシンボル含めてくれないから、Windows APIで死んだら少し困るが)。

 お仕事で大きいプロダクトを作るような人は指定すればいろいろ幸せが待っている。これはPlathome SDKのrebaseコマンドで指定することができる。0x70000000から0x78000000はMSのDLLで使っているので、このアドレスをさければよい。

 C/C++で作っていれば、よっぽどでかい改造をしない限り、そう大きくずれて、全部のモジュールを再配置する事なんてないだろう。

(2008/2/5追記)
コメントを頂いて一般公開されたMicrosoft Support Professionals Toolkit for Windowsに変更。atlanさんコメントありがとうございます。

ブログ気持玉

クリックして気持ちを伝えよう!

ログインしてクリックすれば、自分のブログへのリンクが付きます。

→ログインへ

なるほど(納得、参考になった、ヘー)
驚いた
面白い
ナイス
ガッツ(がんばれ!)
かわいい

気持玉数 : 10

なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー)

この記事へのコメント

この記事へのトラックバック

  • メインPCリプレース案

    Excerpt: 自宅のメインPCは、数年前に作った Pentium4-2.4GHz & メモリ1... Weblog: gentlone's log racked: 2005-09-09 16:41