チラシの裏からうっすら見える外枠の外のメモ書き

新聞に挟まってる硬い紙のチラシの裏からうっすら見える外枠の外に走り書きされたようなものです。思いついたときにふらふらと。

32bitのコンピューターって4GB使えないじゃないすか!やだー!

本当のタイトルは「AviUtlなどがメモリを多く確保するために使っているLargeAddressAwareを試してみた」だった。

Windowsのフリー動画編集ソフトの定番「AviUtl」は多くの日本人ユーザーが利用しています。
ニコニコ動画YouTubeなどに投稿される実況動画はほとんどがこのソフトを使っているのではないかと思います。
AviUtlの特徴は、無料で拡張機能を追加すれば多くの機能が利用できるという点にあります。
Adobe Premireなどはそれ単体で非常に高価ですし、拡張機能も有料でかなりのお値段がするものもあります。

このようにメリットの多いAviUtlですが、すでに更新が終了しています。
しかも32bitソフトウェアなのでメモリを最大2GBまでしか使用することができません。
最近の動画は高画質なものが増えてきており、2GBではメモリは全然足りません。
そこで、AviUtlにはメモリを2GB以上確保するための機能としてLargeAddressAwareを使用しています。

今回はこのLargeAddressAwareについて簡単に紹介します。

そもそも32bitと64bitとは

コンピューターはデータを有限の桁で表現します。
最初から有限である値(整数など)はそれだけの桁を用意すれば完璧に表現することができますが、無理数(円周率やネイピア数など)は永遠に数字が続いていくので表現し切ることはできません。
また、果てしなく長い有限の値も表現できません。なぜなら、それらの値を記憶しておくためのスペースが用意できないからです。
コンピューターが計算をするとき、計算を行うCPUはデータを自分の近くに用意します。メモリというたくさんデータがあるところからレジスタという近場に持ってくるわけです。
メモリはたくさんデータを記憶できる半面、CPUから半端なく遠い*1*2上にデータを取り出すのに時間がかかります。
コンピューターは超高速で計算し続けているので、人間にとっては全然遠くない距離でもとても遠くになってしまいます。そこで、CPUの内側にあるとても早くデータを取り出すことができるレジスタにデータを一旦記憶させておくのです。こうすることで高速に計算することができます。

さて、コンピューターの中には様々なデータのサイズに決まりがあります。
それは1回の処理の命令の長さであったり、1回の受信/送信で受け取る/送ることが可能な大きさであったりです。これらを個別にサイズを決めていくと、大小様々なサイズのデータを入れる場所を用意する必要があります。
それでは大変なので、「大体のデータのサイズはこれぐらいにしよう!」という大まかなルールができました。その暗黙のルールの一つが今回説明する32bitと64bitに関わります。
10年ぐらい前まで、この大まかなサイズは32bit(4バイト)でした。これはCPUを作る会社がようやく作ることのできたレジスタの大きさで、これによってそれ以前のコンピューターよりも一度に多くのデータをやり取りすることができるようになりました。*3最近では更に大型化し、64bit(8バイト)まで扱うことができるようになっています。
これって実はすごい進化なんですよ!*4ではどういうふうにメモリの制限に関わってくるのでしょうか。

なぜ32bitはメモリが2GBまでなの?

さて、前の項で32bit/64bitが大まかなデータのサイズの決まりということはお話しましたね。32bitのコンピューターがメモリを2GBしか扱えないのは、Windowsがメモリを使う方法が原因です。

まず、32bitのコンピューターでメモリを扱うとなると、32bitの長さのアドレス欄にアドレスを書いて、そのアドレスに対応するメモリの場所にアクセスするという方法をとります。これは普通の方法です。
ここで32bitのアドレス欄は2進数*5で表されているので、2の32乗個のアドレスを書き分けることができます。
2の32乗は4294967296。1バイトにつき1アドレス対応させるとしたら、大体4.2GBぐらい対応させることができます。ということでメモリは4.2GBまで使えることになりますね。
なのにソフトウェアは2GBしか使えません。どうして。

それはWindowsの仕様が原因です。

OSのOSによるOSのためのメモリ領域

Windowsに限らず、OSはプログラムです。なのでメモリが必要です。
しかし、OSに与えられるメモリの量が少ないとコンピュータ全体が不安定になります。 dic.nicovideo.jp そこで、OSにはガッツリと固定でメモリを与えておくことでどっしり構えてもらおうという作戦に出ました。
その代わりユーザーのプログラムのメモリ量は2GBまでです。仕方ないね。

お前メモリ2GBしかないの?俺メモリ128GB積んでるけど?

最近のコンピュータはハードウェアの進歩のおかげでどんどん性能が上がりました。
おかげでユーザーはメモリをじゃぶじゃぶ追加できるぐらいになっています。
今ではほとんどのユーザーがメモリ4GB以上あるんじゃないでしょうか。
ちなみに章タイトルのメモリはメモリ・ストレージのことであってメモリのことではありません。
こんな事言うのはスマートフォンしか持っていない人やコンピュータをよくわかってない人ぐらいでしょう。
たまに本当にコンピュータに128GBメモリを積んでいる人がいますがその人は変態なので避けましょう。
ちなみに上で述べたように32bitのコンピュータで4GBを超える量のメモリは意味がありません。

2GBの壁を超えるLargeAddressAware

メモリを沢山使えるようにするにはまず64bitのコンピュータである必要があります。大体これはWindowsが64bit版であるだけでOKです。
ただし、32bit時代に作られたソフトウェアは、コンピュータが32bitで動いていると思いこんでいるのでメモリを2GB以上使おうとしません。
そこで、32bitのソフトウェアがメモリをもっと扱えるようにしようとMicrosoftは考えました。
それがLargeAddressAwareオプションです。

これを有効にすると、32bitのWindows上ではメモリが3GBまで、64bitのWindowsの上ではメモリが4GBまで使えるようになります。
先ほど説明したOSのメモリ取り分を減らしてやることでよりたくさんのメモリを使えるようにしたわけです。
このオプションはプログラムのある位置を少し改変するだけで有効にでき、また行儀のいいソフトウェアは内部の変更を行う必要がないので手軽で強力です。

実際に試してみる

じゃあ実際にやってみないとわからないということで、次のようなプログラムを用意してみました。

include <iostream>

int main()
{
    int size = 10;
    int count = 0;
    while (true) {
        void* p = malloc(size * 1024 * 1024);
        if (p == NULL)
            break;
        count++;
    }
    std::cout << "Memory size is " << count * size << "MB";
}

参考:64bit Windowsを前提とした32bitアプリケーション延命法 ~ LAAオプションで32bitアプリケーションのメモリ不足問題を解消 | OPTPiX Labs Blog
これをx86*6をターゲットにビルドしてみると、次のようなメッセージが出ます。

Memory size is 1980MB

32bitなのでちゃんと2GB以下しかメモリを取得できていません。
正確に2GBではないのは、DLLなど他のライブラリがメモリを持っていってるからでしょう。

ではここでバイナリエディタを使ってLargeAddressAwareの設定に当たる部分をいじってみます。
Windowsでよく使われるStirlingで今のプログラムを開いてみると、このような感じになっています。 f:id:k-hyoda:20191027175809p:plain ここで、アドレス位置0x3Cから0x3Fを見てみると、F0 00 00 00という値が入っています。
アドレス位置の見方は、左が上位アドレス、上が下位のアドレスで、左に00000030、上に0Cと書いてあればそこは0x3Cになります。
0xと先頭についているのは、その数が16進数で表されていることを示します。

さて、0x3Cから0x3Fまでに並んでいた4つの値を、右から読んでいきます。いわゆるリトルエンディアンです。
トルエンディアンは、下位のビットを上から順番に書いていきます。なので読むときは逆に下から読む必要があるわけです。
F0 00 00 00ということは、逆から読めば00 00 00 F0になるので0x000000F0が探しているアドレスです。*7
このアドレスを0x16進めた先がLargeAddressAwareのオプションが書かれるアドレスです。*8

ここにはすでに値が入っています。今回は0x02と入っていますね。LargeAddressAwareを有効にするには、すでに入っている値に対して0x20を論理和すればOKです。
論理和は早い話が16進数での足し算のようなものです。桁上りしないので要注意!(詳しくは調べて)
https://wa3.i-3-i.info/word11663.htmlwa3.i-3-i.info この場合だと、0x02 OR 0x20なので、0x22が新しい値になります。
Stirlingで該当箇所を0x02から0x22に書き換えて保存しましょう。

そして再度実行してみると次のようになりました。

Memory size is 4030MB

ちゃんとメモリが4GB近くまで使えています。

AviUtlの場合は?

ちなみに、AviUtlの場合はこれをAviUtl自身で行っています。
管理者権限でAviUtlを起動し、設定からLargeAddressAwareを有効にするにチェックを入れて再起動すると自分で該当箇所を書き換えてくれます。賢いですね。

あとがき

この記事は途中で書く気が起きなくなってしまったのでしばらく放置していたんですが、そうしたらAviUtlの作者KENくんがもっといい感じにメモリを使えるように対応したバージョンを公開して「出遅れたぁ~!」な感じです。


現場からは以上です。

*1:大体20cm~10cmぐらい

*2:CPUの中だけを電子が移動する時間に比べるとCPUからメモリまで電子が移動する時間はとても長い時間になります

*3:これ以前は16bit(2バイト)のコンピュータが主流、更に遡ると8bitに、もっと遡れば4bitコンピュータ(マイコンと呼ばれるシロモノとか)たどり着きます。

*4:コンピューターの開発者たちの血と汗の結晶です

*5:0と1で数字を表したもので、これを使えば両手だけで1024まで数えることができます。やったね!

*6:32bitのこと

*7:F0がひっくり返らないのは2バイトずつ読むのがルールだからです。

*8:つまり0x106