コピーオンライト

コピーオンライト (Copy-On-Write) とは、コンピュータプログラミングにおける最適化戦略の一種である。COWまたはCoWと略記することもある。

コンピュータ内部で、ある程度大きなデータをコピー(複製)する必要が生じたとき、愚直な設計では、直ちに新たな空き領域を探して割り当て、データ全体のコピー(ディープコピー)を実行する。ところが、もしコピーしたデータに対する書き換えがなければそのコピーは無駄だったことになる。

そこで、コピーを要求されても、コピーした振りをして、とりあえず原本をそのまま参照させるようにする。この状態でもデータの読み取りだけであれば問題なく、コピーのコストがほとんど発生しない。ただし、コピー完了後の原本に対する変更や、コピーに対する変更は本来、お互いに影響しないことが大前提であり、そのままで本当にデータを書き換えてしまっては問題がある。原本またはコピーのどちらかを書き換えようとしたときに、それを検出し、その時点ではじめて新たな空き領域を探して割り当て、コピーを実行する。これが「書き換え時にコピーする」、すなわちコピーオンライト (Copy-On-Write) の基本的な形態である。

基盤となる考え方は、複数の(何らかの)要求者がリソースを要求するときに、少なくとも当初はそれらの要求を区別する必要がないときに同じリソースを与える、というものである。これは要求者がリソースを「更新」しようとするまで保持され、「更新」が他者に見えないようにリソースの個別のコピーを必要になった時点で作成する。要求者からはこの一連の動きは見えない。第一の利点は要求者が全く更新しなければ、個別のコピーを作成する必要が生じないという点である。

仮想記憶への応用

コピーオンライトは主に仮想記憶方式のオペレーティングシステム (OS) で使用されている。 例えば、プロセスのコピーを作成 (fork) するとき、書き換えることのないメモリページは、両方のプロセス(元のプロセスと生成されたプロセス)で共用し、書き換える可能性のあるメモリページは、新たなメモリページを割り当ててコピーを作成する必要がある。 ここでコピーオンライトが使用される。一方のプロセスがメモリを更新すると、OSのカーネルがその操作を横取りし、メモリのコピーを作成してメモリ内容の更新が他方のプロセスから見えないようにする。

他にも、標準Cライブラリcalloc()関数の実装に使われることがある。calloc()はメモリ領域をゼロで初期化して確保する(malloc参照)。愚直な実装であればmalloc()で確保した領域に対してmemset()を呼び出すだけになるであろう。しかし最適化された実装では、ゼロで初期化されたページをシステムに1つ用意しておき、calloc()で確保した領域にはこのページをマッピングしてコピーオンライト機能を使用する[1][2]。このようにするとcalloc()呼び出し直後はたとえ何ページのメモリが必要でも物理的には1ページしか使われておらず、その領域の内容を更新しようとしたときに初めて個別のコピーが作成される。このような最適化はページサイズ以上の大きなアロケーションでのみ使用されるのが一般的である。

コピーオンライトは、MMUに対してプロセスのアドレス空間のある領域がリードオンリーであると通知することで実現する。その領域にデータを書き込もうとしたとき、MMUは例外を発生し、それをカーネルが処理する。カーネルは新たな物理ページを確保して、書き込みが行われた領域のマッピングを変更して、その新たな物理ページに対応させる。

COWの主な利点は、メモリを空間的にまばらに利用する可能性にある。データを格納したときだけ物理メモリ使用量が増えるので、若干メモリ使用量が増えるが非常に効率的なハッシュテーブルを実装することができる。しかし、そのような使い方をすると仮想空間を使い切ってしまう危険性もある(ハッシュテーブルに使われる仮想アドレス空間の領域はマッピングされる物理ページはまばらだが、他の用途に空間を利用できない)。カーネルレベルでのCOWの主な問題点はその複雑さにあるが、そのような懸念はスワップ方式などのもっと基本的な部分も同じである。カーネル自身がコピーオンライト制御されているページに書き込みを行った場合、やはりコピーが必要となる(訳注:例えばシステムコールの結果をユーザー空間に書き込む場合など。実際には書きこみの前にコピーオンライトかどうかをチェックし、必要に応じてコピー作成後に書き込みを行う)。

COWはカーネル以外でも、ライブラリアプリケーションやシステムコードなどでも使われる。C++Standard Template Libraryが提供するstd::stringクラスは、かつてISO/IEC 14882:2003(通称C++03)規格まではコピーオンライト型の実装が可能な設計(外部仕様)になっていた[注釈 1]。しかし、このような領域でのCOWはマルチスレッドコードで問題となる。コピー元のリソースを複数のスレッドで共有するにはロックが必要であり、COWの利点以上のオーバーヘッドとなる可能性が高い。

ファイルシステムへの応用

COWはファイルシステムにおけるスナップショット機能を実現するアルゴリズムとして利用される。特定時点のファイルシステムの状態を原本とし、その後ファイルまたはブロックの変更があった場合には新たにオリジナルのそれをコピーし、変更をそのコピーにのみ反映することで実現する。スナップショット機能を備える論理ボリュームマネージャやファイルシステムの多くがその実現にCOWを用いている。

単純なファイルのコピーも、COWで効率化できる可能性がある。ただし、ファイルシステムでのサポートとOSがファイルコピー用のAPIを提供し、アプリケーションがそれを使用することが条件となる。macOSiOSといったAppleのプラットフォームで、HFS+の後継として採用されているApple File System (APFS) は、COWをサポートしており、単純なファイルコピー自体は一瞬で終わるようになっている[3][4]Microsoft WindowsWindows NT系)で従来から使われてきたNT File System (NTFS) はCOWをサポートしていないが、Resilient File System (ReFS) はCOWをサポートしており、Microsoft Windows 11のバージョン22H2以降は開発ドライブ(Dev Drive)という機能と対応APIによってファイルのCOWが利用できるようになっている[5]。さらにWindows 11のプレビュー版では従来のWindows APIであるCopyFile()関数でも対応環境上であればCOWが利用できるようになるアーリーアクセスが提供されている[6]。なお、POSIXにはそういったファイルシステムレベルのサポートやシステムコールは存在しない。

ファイルシステムが重複排除機構を備えている場合は更に相性が良い。前述の通り、COWにてコピーされたファイルは元ファイルと同一のデータブロックを参照しているため、コピー終了時点で「重複排除済み」とみなすことができる。このためCOWコピーされたファイルに改めて重複排除処理を加える必要がなくなる(もしくは一瞬で完了する)。

また、BochsQEMUといった仮想マシンの仮想ディスク装置で使われている。同じディスクイメージを使って複数の仮想マシンを動作させれば、必要とされるディスク容量を劇的に減らすことができる。またディスクからの読み込みイメージがメモリにキャッシュされ、それが仮想マシン間で共有されるため、性能も向上する。

制限事項として、ファイル上の既存のデータを更新する場合、コピーオンライト処理のために一定の空き容量が必要となる。データ更新の処理中、一時的に更新前と後のデータの両方がファイルシステムに存在することに依る。コピーオンライト処理のために十分な空き領域がファイルシステムにない場合は、ファイルシステムに空き容量があるにもかかわらず容量不足のエラーが発生し、一見奇妙な問題となる。

脚注

注釈

  1. ^ その後の改訂であるISO/IEC 14882:2011(通称C++11)においては、規格の要求に従う場合そのような実装を行えなくなった。

出典

関連項目