メモリ効率の向上に関するガイドライン (10000160i

メモリ効率の向上に
関するガイドライン
目次
概要 5
この書類の構成 5
仮想メモリシステムについて 6
仮想メモリについて 7
仮想メモリシステムの詳細 8
固定メモリ 9
カーネルのページリスト 10
ページアウト処理 11
ページイン処理 12
メモリ割り当てに関するヒント 13
メモリ効率改善のヒント 13
メモリの割り当てを遅延させる 13
メモリブロックを効率的に初期化する 14
一時メモリバッファを再利用する 14
使っていないメモリを解放する 15
メモリ割り当ての技法 15
オブジェクトを割り当てる 16
小容量のメモリブロックをmallocで確保する 16
大容量のメモリブロックをmallocで確保する 17
メモリを一括して割り当てる 18
共有メモリを割り当てる 19
mallocメモリゾーンを使う 19
mallocでメモリをコピーする 20
直接方式でメモリをコピーする 20
遅延方式でメモリをコピーする 21
小容量のデータをコピーする 21
データをビデオRAMにコピーする 21
メモリ不足の通知に対処する(iOS) 21
キャッシュと破棄可能なメモリ 23
キャッシュの概要 23
なぜキャッシュを使うか 23
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
2
キャッシュに伴う問題 23
解決策 23
破棄可能なメモリを使う 24
破棄可能なメモリの利点 24
破棄可能なメモリの実装方法 25
破棄可能なメモリとNSCache 26
破棄可能なメモリに関する若干の注意 26
破棄可能なメモリが有効な状況 26
メモリの使用状況の追跡 27
メモリの割り当て状況をInstrumentsで追跡する 27
「ObjectAlloc」でメモリの割り当て状況を分析する 28
共有メモリの使用状況を分析する 28
「Memory Monitor」で収集したデータを分析する 28
malloc_historyでメモリ割り当て状況を追跡する 28
「heap」でメモリを検査する 29
メモリリークの検出 30
Instrumentsでメモリリークを検出する 30
「leaks」ツールを使う 31
メモリリークの検出能力を向上するためのヒント 31
mallocのデバッグ機能 33
ガード型mallocを有効にする 33
mallocの環境変数を設定する 33
メモリの二重解放を検出する 35
ヒープの破損を検出する 35
メモリ内容の破壊を検出する 36
仮想メモリの使用状況の表示 37
仮想メモリの統計情報を表示する 37
Mach-Oのコードページを表示する 38
仮想メモリ領域を表示する 39
vmmapの出力例 39
vmmapの出力を解釈する 41
書類の改訂履歴 44
索引 45
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
3
表、リスト
仮想メモリシステムについて 6
表1
表2
VMオブジェクトのフィールド 8
ユーザレベルのソフトウェアが生成する資源に必要な固定メモリの容量 9
メモリ割り当てに関するヒント 13
リスト 1
リスト 2
メモリを遅延割り当てするアクセサ 14
vm_allocateでメモリを割り当てるコード例 18
mallocのデバッグ機能 33
表1
mallocに関係する環境変数 34
仮想メモリの使用状況の表示 37
表1
リスト 1
リスト 2
リスト 3
vmmapが出力する各欄の意味 41
vm_statの出力例 37
pagestuffの出力例(抜粋) 38
典型的なvmmapの出力 39
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
4
概要
メモリはあらゆるプログラムが必要とする重要なシステム資源です。プログラムの実行コードは、メ
モリ中に読み込んでから起動しなければなりません。また、実行中にも(明示的に、あるいは暗黙
に)追加のメモリ領域を確保して、データを格納、操作します。メモリ中にプログラムのコード領域
やデータ領域を確保するためには時間その他のシステム資源を要し、したがってシステム全体の性能
にも影響を及ぼします。メモリをまったく使わないわけにはいきませんが、システムへの影響を低く
抑える手段はいろいろ考えられます。
この資料では、OS XおよびiOSのメモリ管理系に関する背景知識と、効率よく利用する方法を説明し
ます。これを参考に、適切な時点で適正な量のメモリを確保するようにして、プログラムのメモリ効
率を改善してください。この資料では、メモリの使用に関連して発生する、処理性能を損なう問題の
検出法も紹介します。
この書類の構成
この資料は次の各章から成ります。
●
●
●
●
●
●
●
“仮想メモリシステムについて” (6 ページ)では、OS XやiOSの仮想メモリシステムについて、
主な用語を紹介しながら大づかみに説明します。
“メモリ割り当てに関するヒント” (13 ページ)では、メモリを効率的に割り当て、初期化、複
写する技法を説明します。また、iOSでメモリが不足したときの適切な対処法も解説します。
“キャッシュと破棄可能なメモリ” (23 ページ)では、キャッシュの利点と、これを実装したと
きに発生しうる問題の回避方法を説明します。さらに、キャッシュシステムに破棄可能なメモリ
を実装することの利点と、この技術をうまく実装する手法も解説します。
“メモリの使用状況の追跡” (27 ページ)では、アプリケーションのメモリ使用状況を分析する
ツールや技法について解説します。
“メモリリークの検出” (30 ページ)では、メモリリークを検出するツールや技法を説明します。
“mallocのデバッグ機能” (33 ページ)ではmallocの実行履歴をログ出力するための環境変数につ
いて説明します。メモリ分析ツールを利用するためには、あらかじめ該当する環境変数を設定し
ておく必要があります。
“仮想メモリの使用状況の表示” (37 ページ)では、アプリケーションのメモリ使用量を分析す
るツールや技法を解説します。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
5
仮想メモリシステムについて
OS XやiOSで動作する性能のよいコードを記述する上で、効率的なメモリ管理は重要です。必要なメ
モリ容量を減らせば、CPU時間を削減する効果もあります。効率的なコードになるよう調整するため
には、システムがメモリをどのように管理しているか、ある程度知っておかなければなりません。
仮想メモリシステムはOS XやiOSに完全統合されており、解除することはできません。いずれも4ギガ
バイトのアドレス空間があります(32ビットプロセスの場合)。OS Xの場合はさらに、64ビットプロ
セスで、約18エクサバイトのアドレス空間を管理することも可能です。4ギガバイト以上のRAMを搭
載している場合でも、単独のプロセスにすべて割り当てることはほとんどありません。
プロセスが4ギガバイトあるいは18エクサバイトのアドレス空間全体にアクセスできるよう、OS Xは
当面使用しないデータを、ハードディスク上に保持します。メモリが枯渇しそうになると、システム
は現在使用していないデータをハードディスクに追い出して、必要なデータを保持するためのメモリ
を確保します。そのためのハードディスク領域を、主メモリのバックアップ(待避)用ストレージと
して働くので、「バッキングストア」と呼びます。
OS Xにはバッキングストアの機能がありますが、iOSは未対応です。iPhoneアプリケーションの場合、
ディスク上に存在し、書き換えることのないデータ(コードページなど)は、単純にメモリから消去
し、必要になった時点で再び読み込むようになっています。一方、書き換え可能なデータを、オペ
レーティングシステムが自動的にメモリから消去することはありません。メモリが枯渇しそうになる
と、システムはアプリケーションにその旨を通知し、メモリを解放して空きを確保するよう要求しま
す。この要求に応じられなかった場合、停止してしまいます。
注意: UNIXベースの多くのオペレーティングシステムと違い、OS Xは事前にバッキングス
トア用のディスクパーティションを割り当てません。ブートパーティションの空きディスク
空間全体をこの用途に用いるようになっています。
以下の節では、OS XとiOSの両方について、仮想メモリシステムに関する主な用語を紹介しながら大
づかみに説明します。仮想メモリシステムの動作について詳しくは、『KernelProgrammingGuide 』を
参照してください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
6
仮想メモリシステムについて
仮想メモリについて
仮想メモリについて
仮想メモリは、物理的なRAMの容量に制限されることなく、オペレーティングシステムが稼働するた
めの仕組みです。仮想メモリ管理系は、論理アドレス空間(あるいは「仮想」アドレス空間)を各プ
ロセスに割り当て、この空間を一定の大きさのページ(page)に分割して管理します。プロセッサと
メモリ管理ユニット(MMU、Memory Management Unit)はページ表(page table)を管理し、プログ
ラムの論理アドレス空間上のページを、物理RAMのアドレスに対応づけます。プログラムコードがメ
モリ上のあるアドレスにアクセスしようとすると、MMUはページ表を参照して、論理アドレスを物理
アドレスに変換します。この変換は自動的に起こり、アプリケーション側が意識することはありませ
ん。
プログラムの側から見ると、論理アドレス空間上のアドレスにはいつでもアクセスできます。しか
し、物理RAM上にないアドレスにアクセスしようとすると、ページフォールト(page fault)が発生し
ます。すると仮想メモリシステムは、直ちに専用のハンドラを起動します。このハンドラは、まず実
行中のコードを停止し、物理メモリ上の空きページを見つけ、必要なデータを含むページをディスク
から読み込み、ページ表を更新した上で、プログラムコードに制御を返します。すると今度は、正常
にメモリアドレスにアクセスできます。この処理をページング(paging)と言います。
物理メモリ上に空きページがない場合、ハンドラは使用中のいずれかのページを解放して、新しい
ページ用の空間を確保しなければなりません。ページを解放する方法はプラットフォームによって違
います。OS Xの場合は、ページをバッキングストアに保存します。バッキングストア(backing store)
とは、あるプロセスが使っていたページのコピーを保存するために設けた、ディスク上の領域のこと
です。物理メモリからバッキングストアにデータを移動することをページアウト(page out)あるい
はスワップアウト、逆にバッキングストアから物理メモリに移動することをページイン(page in)あ
るいはスワップインと言います。一方、iOSにはバッキングストアが存在せず、ディスクにページア
ウトすることはありません。しかし読み込み専用のページは、必要に応じてディスクからページイン
するようになっています。
OS X、iOSともに、ページ長は4キロバイトです。したがって、ページフォールトが発生すると、シス
テムはディスクから4キロバイトを読み込むことになります。ページフォールトとそれに伴うページ
の読み書きが頻繁に発生し、コードの実行そのものよりも時間がかかってしまう現象を、ディスクス
ラッシング(disk thrashing)と言います。
ページングや、特にディスクスラッシングが発生すると、読み書きに時間がかかるため、処理性能に
悪影響が及びます。バッキングストアからページインすることになれば、RAMに直接アクセスできる
場合に比べて著しく時間がかかってしまうのです。あるページにアクセスするために別のページを
ディスクに書き込まなければならないとすると、処理性能に対する影響はさらに大きくなります。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
7
仮想メモリシステムについて
仮想メモリシステムの詳細
仮想メモリシステムの詳細
プロセスの論理アドレス空間は、マップされたメモリ領域から成ります。各領域は所定の個数の仮想
メモリページから成ります。各領域について、同一内容の他の領域を参照する旨、書き込み保護、固
定(wired)か否か(ページアウトできない状態か否か)、などの属性を管理します。領域は所定の
個数のページから成るので、その境界はページ境界に揃って(page-aligned)います。すなわち、領
域の先頭アドレスはあるページの先頭アドレスに一致し、また、領域の末尾アドレスは別のページの
末尾アドレスに一致します。
カーネルは論理アドレス空間の各領域に、VMオブジェクト(Virtual Memory Object、仮想メモリオブ
ジェクト)を対応づけます。このVMオブジェクトを使って、領域内のページが物理メモリ上にある
か否かを追跡、管理するのです。領域は、バッキングストアの一部、あるいはファイルシステム上の
メモリマップトファイルに対応づけることができます。VMオブジェクトはそれぞれマップを持って
おり、領域にデフォルトページャまたはvnodeページャを対応づけて管理します。デフォルトページャ
(default pager)はシステムに組み込まれた管理系で、バッキングストア内の非常駐仮想メモリペー
ジを管理し、必要に応じてページインする(物理メモリに取り込む)働きをします。一方、vnodeペー
ジャ(vnode pager)は、メモリマップトファイルへのアクセス手段を実装します。ページングの機
構を活用してアドレス空間をファイルに対応づけ、ファイルのある部分を、あたかもメモリ上に存在
するように読み書きできるようにしています。
VMオブジェクトは、領域をデフォルトページャやvnodeページャにマップするだけでなく、他のVM
オブジェクトにマップすることもあります。カーネルはこの自己参照技術を活用して、コピーオンラ
イト方式の領域を実装しています。これは、異なるプロセス(あるいは同じプロセス内の複数のコー
ドブロック)が同一内容のページを使う場合、プロセスごとにメモリ空間を確保する代わりに、ペー
ジを共有できるようにする方式です。あるプロセスがページを書き換えようとした時点で、当該プロ
セスの論理アドレス空間に、そのページのコピーを作成します。すると、当該プロセスは(他のプロ
セスと共有しない)自分だけのページを持てたことになるので、内容を書き換えても他のプロセスに
は影響がありません。したがって、大量のデータをメモリ上で効率的に共有でき、必要な場合には
ページを直接(しかも安全に)操作できることになります。このような領域は、システムフレーム
ワークからロードするデータページによく利用します。
各VMオブジェクトには表 1に示すようなフィールドがあります。
表1
VMオブジェクトのフィールド
フィールド
説明
Resident pages
この領域に属するページのうち、物理メモリ上に現在あるもののリスト。
Size
領域のバイト長。
Pager
バッキングストアにおけるこの領域のページを追跡、操作するページャ。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
8
仮想メモリシステムについて
固定メモリ
フィールド
説明
Shadow
コピーオンライト方式を実現するために使用。
Copy
コピーオンライト方式を実現するために使用。
Attributes
状態を表すフラグ(実装のために必要)。
VMオブジェクトをコピーオンライト方式(vm_copy)で扱う場合、ShadowおよびCopyには他のVMオ
ブジェクトの参照が入ります。そうでなければNULLになります。
固定メモリ
固定(wired)メモリ(あるいは常駐(resident)メモリ)は、ディスクに追い出すことができない、
カーネルのコードやデータ構造体などを格納します。アプリケーション、フレームワークなど、ユー
ザレベルのソフトウェアに、固定メモリを割り当てることはできませんが、その容量に影響を与える
ことはありえます。たとえば、スレッドやポートを生成する場合、これはカーネルの資源なので、暗
黙のうちに固定メモリを確保することになります。
表 2に、アプリケーションが生成する資源に必要な固定メモリの容量を示します。
表2
ユーザレベルのソフトウェアが生成する資源に必要な固定メモリの容量
資源
カーネルが確保する固定メモリ容量
プロセス
16キロバイト
スレッド
「継続(continuation)」にブロック化:5キロバイト、ブロック化:21キロバ
イト
Machポート
116バイト
マッピング
32バイト
ライブラリ
2キロバイト、およびこれを使うタスクごとに200バイト
メモリ領域
160バイト
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
9
仮想メモリシステムについて
カーネルのページリスト
注意: この容量はオペレーティングシステムの版によって変わる可能性があります。システ
ム資源のコストを大まかに見積もるための参考として参照してください。
このように、スレッドやプロセス、ライブラリを利用すれば、固定メモリの領域が消費されることに
なります。さらにカーネル自身も、次のような目的で固定メモリを使います。
●
VMオブジェクト
●
仮想メモリのバッファキャッシュ
●
I/Oバッファキャッシュ
●
ドライバ
固定データ構造体は、物理ページやマップ表(仮想メモリのマッピング情報を格納)とも連携します
が、いずれも利用可能な物理メモリ容量に応じて大きさが変わります。したがって、システムにメモ
リを追加搭載すれば、それだけで固定メモリの容量が増えます。コンピュータを起動してFinderを立
ち上げ、ほかのアプリケーションは稼働していない状態で、固定メモリの容量は、RAMが64メガバイ
トならば14メガバイト程度、128メガバイトならば17メガバイト程度です。
固定メモリページは、無効になってもすぐには空きページリストに移動しません。空きページ数が所
定の閾値を下回り、ページアウトが発生したときに、「ガベージコレクション」の機構が回収するよ
うになっています。
カーネルのページリスト
カーネルは次の3つのリストを利用して、システム全体の物理メモリページを管理します。
●
●
●
使用中ページリスト(active list)は、物理メモリ中にマップされており、最近アクセスされたペー
ジを保持します。
休止中ページリスト(inactive list)は、物理メモリ上にあるけれども、最近はアクセスされてい
ないページを保持します。内容は有効なデータですが、いつでもメモリ上から消去して構いませ
ん。
空きページリスト(free list)は、VMオブジェクトのアドレス空間に対応づけられていない物理メ
モリのページを保持します。必要に応じてどのプロセスにでも割り当てることができます。
空きページリストのページ数が(物理メモリ容量によって決まる)閾値を下回ると、ページャは休止
中のページの回収を試みます。そのために、休止中ページリストからページを取り出して調べます。
これが最近アクセスされたページであれば使用中と判断し、使用中ページリストの末尾に追加しま
す。OS Xの場合、休止中ページの内容をバッキングストアに保存していなければ、ページアウトして
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
10
仮想メモリシステムについて
ページアウト処理
から空きページリストに移動する必要があります(一方、iOSの場合、書き換えられたページは休止
中であってもメモリ中に残っているので、このページを所有するアプリケーション側が適切に処理し
なければなりません)。休止中のページが書き換えられておらず、常駐もしていない場合、仮想マッ
ピングが破損している状態なので、空きページリストに追加します。空きページリストの大きさが閾
値を超えれば、ページャの処理は終了です。
カーネルは、最近アクセスされていないページを、使用中ページリストから休止中ページリストに移
動します。一方、ソフトフォールト(後述)が発生すれば、休止中ページリストから使用中ページリ
ストに移動します(“ページイン処理” (12 ページ)を参照)。仮想ページをスワップアウトしたと
きには、対応する物理ページを空きページリストに置きます。さらに、プロセスが明示的にメモリを
解放した場合も、カーネルは該当するページを空きページリストに移動します。
ページアウト処理
OS Xでは、空きページリストのページ数が、あらかじめ計算しておいた閾値を下回ると、休止中ペー
ジをカーネルがスワップアウトすることにより、物理ページを回収して空きページリストに追加しま
す。そのために、使用中ページリストと休止中ページリストに載っている常駐ページすべてについ
て、次の処理を行います。
1.
使用中ページリストに載っているページのうち、最近アクセスされていないものを、休止中ペー
ジリストに移動します。
2.
休止中ページリストに載っているページのうち、最近アクセスされていないものについて、対応
するVMオブジェクトを検索します。
3.
VMオブジェクトにページが割り当てられたことがなければ、カーネルは初期化ルーチンを呼び出
して、デフォルトページャオブジェクトを生成し、対応づけます。
4.
VMオブジェクトのデフォルトページャは、ページの内容をバッキングストアに保存しようと試み
ます。
5.
この処理が成功した場合、カーネルはページが占有していた物理メモリを解放し、休止中ページ
リストから空きページリストに当該ページを移動します。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
11
仮想メモリシステムについて
ページイン処理
注意: iOSの場合、カーネルが自動的にページをバッキングストアに書き出すことはありま
せん。空きメモリ容量が、あらかじめ計算しておいた閾値を下回った場合、カーネルは、休
止中かつ書き換えられていないページを消去するとともに、アプリケーションに対して直
接、メモリを解放するよう通知します。この通知に応答する方法について詳しくは、“メモ
リ不足の通知に対処する(iOS)” (21 ページ)を参照してください。
ページイン処理
仮想メモリ管理の最終フェーズでは、ページデータを保存しているバッキングストアまたはファイル
から、ページを物理メモリに移動します。メモリアクセス時にページフォールトが発生すると、ペー
ジインの処理が始まります。ページフォールトは、仮想アドレス上のあるデータにアクセスしようと
したとき、それが物理メモリにマップされていなかった場合に発生します。これには次の2種類があ
ります。
●
●
ソフトフォールト(soft fault)は、参照されたアドレスのページが物理メモリ上にあるけれども、
このプロセスのアドレス空間にマップされていない場合に発生します。
ハードフォールト(hard fault)は、参照されたアドレスのページが物理メモリ上になく、バッキ
ングストアにスワップアウトされている(またはマップトファイルに収容されている)場合に発
生します。一般にはこれをページフォールトに呼びます。
いずれの場合もカーネルは、アクセスされた領域のマップエントリとVMオブジェクトを検索します。
次いで、VMオブジェクトの常駐ページリストを順に調べます。該当するページがこのリスト中にあ
ればソフトフォールト、見つからなければハードフォールトになります。
ソフトフォールトであれば、カーネルは当該ページを含む物理メモリをプロセスの仮想アドレス空間
にマップし、このページに「使用中」である旨の印をつけます。フォールトを引き起こしたのが書き
込みアクセスであった場合は、ページに「修正済み」である旨の印もつけて、後で解放する必要が生
じたとき、バッキングストアに保存できるようにします。
ハードフォールトであれば、VMオブジェクトのページャは、バッキングストアまたはディスク上の
ファイルから(ページャの種類に依存)、該当するページを検索します。マップ情報を適宜調整した
後、ページャはこのページを物理メモリ上に移動し、使用中ページリストに置きます。ソフトフォー
ルトと同様に、これを引き起こしたのが書き込みアクセスであれば、ページに「修正済み」である旨
の印をつけます。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
12
メモリ割り当てに関するヒント
メモリは重要なシステム資源なので、アプリケーションがどのようにメモリを使うか調べ、効率的な
割り当て方法を検討することが大切です。多くのアプリケーションは、特別な処理を必要としませ
ん。必要に応じてオブジェクトやメモリブロックを確保するだけであり、性能上の問題は起こらない
のです。しかし、大容量のメモリを使うアプリケーションの場合、割り当て戦略によって大きな違い
が生じます。
以下の各節では、メモリ割り当ての基本機能について説明し、効率的に割り当てるためのヒントを紹
介します。実際にメモリ効率の問題が発生しているかどうか見極めるためには、Xcodeのツールで、
動作中のメモリ割り当てパターンを調べる必要があるでしょう。その具体的な手順については、“メ
モリの使用状況の追跡” (27 ページ)を参照してください。
メモリ効率改善のヒント
コード設計時には常にメモリの使い方を意識しなければなりません。メモリは重要なシステム資源で
あり、できるだけ効率的に使わなければならないのです。処理に応じて適正な量のメモリを確保する
ことも大切ですが、以下の節では、メモリの使用効率を改善する他の手段を紹介します。
メモリの割り当てを遅延させる
メモリの割り当てはすべて、性能に影響を及ぼします。具体的には、論理アドレス空間にメモリを割
り当てる時間と、このアドレス空間に対応する物理メモリを確保する時間です。メモリブロックをす
ぐに使わないのであれば、実際に必要になるまで割り当てを遅延させてください。たとえば、アプリ
ケーションの起動に時間がかかる場合、その時点では最小限のメモリのみ確保してください。当初の
画面を表示し、ユーザ入力に応じるために、必要なオブジェクトの確保のみにとどめるのです。それ
以外の割り当て処理は、実際にユーザが操作を開始し、コマンドを起動してから行います。この遅延
割り当て方式を採用すると、処理時間を短縮できるだけでなく、本当に必要なメモリのみ割り当てる
ことになります。
遅延割り当て方式は、大域変数が関与する場合、ちょっとした技巧が必要になるかも知れません。ア
プリケーション全体から参照される変数なので、コードのどこから参照されても、あらかじめ確実に
初期化を済ませておかなければならないのです。そのための技巧としてよく使われるのが、あるコー
ドモジュール内に静的変数を用意して割り当て済みか否かを管理し、値の取得や設定は公開のアクセ
サ関数で行う、という方法です(リスト 1を参照)。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
13
メモリ割り当てに関するヒント
メモリ効率改善のヒント
リスト 1
メモリを遅延割り当てするアクセサ
MyGlobalInfo* GetGlobalBuffer()
{
static MyGlobalInfo* sGlobalBuffer = NULL;
if ( sGlobalBuffer == NULL )
{
sGlobalBuffer = malloc( sizeof( MyGlobalInfo ) );
}
return sGlobalBuffer;
}
この種のコードが複数のスレッドから呼び出されうる場合は注意が必要です。マルチスレッド環境で
は、アクセサメソッドのif文を、ロックで保護しなければなりません。この方式の欠点は、ロックの
獲得に相応の時間がかかり、しかも大域変数にアクセスするたびに必要となるため、新たな性能低下
の要因になりうることです。簡単な解決法としては、スレッドを生成する前に、アプリケーションの
主スレッドで大域変数をすべて初期化する、というやり方が考えられます。
メモリブロックを効率的に初期化する
小さなメモリブロックをmalloc関数で確保した場合、0で初期化されているという保証はありませ
ん。memset関数で初期化することも考えられますが、むしろcalloc関数を使った方がよいでしょう。
calloc関数は、要求されたメモリを仮想アドレス空間上に確保しますが、初期化は実際に使われる
段階で行います。これに対し、memset関数で0に初期化する方式は、仮想メモリシステムに対し、該
当するページを物理メモリにマップするよう強制することになります。calloc関数にはほかにも、
領域が複数のページにわたる場合に、一括ではなく実際に使う分のみ初期化する、という利点もあり
ます。
一時メモリバッファを再利用する
何らかの計算用に大容量の一時バッファを生成する関数を頻繁に呼び出す場合、その都度バッファを
確保するのではなく、再利用することを検討してください。バッファ長が可変であったとしても、
realloc関数を使えば、必要に応じて増やしていくことができます。マルチスレッドアプリケーショ
ンの場合、バッファを再利用するためには、スレッドローカルなストレージ上に確保するのがよいで
しょう。関数内の静的変数を使ってバッファを確保する方法もありますが、同時に複数のスレッドか
らこの関数を呼び出すことはできなくなります。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
14
メモリ割り当てに関するヒント
メモリ割り当ての技法
バッファをキャッシュすることにより、巨大なメモリブロックを頻繁に割り当てては解放する、とい
うオーバーヘッドを回避できます。もっとも、この技法が有効なのは、頻繁に呼び出される関数に限
ります。さらに、過剰にキャッシュしないよう配慮しなければなりません。アプリケーションのメモ
リ占有量を増やすことになるので、処理性能に対する効果を見極めた上で採用してください。
使っていないメモリを解放する
mallocで確保したメモリは、使い終わったらすぐに解放することが大切です。解放しないままでいる
とメモリリークが発生し、使用可能なメモリ量が減って、処理性能に悪影響が出るかも知れません。
最悪の場合、必要なメモリを確保できず、何もできなくなる可能性もあります。
注意: コンパイラのオプションで自動参照カウント(ARC、Automatic Reference Count)を
指定する場合、Objective-Cのオブジェクトを明示的に解放する必要はありません。必要なオ
ブジェクトに対して強い参照を保持するだけです。不要になったオブジェクトは、参照を外
せば自動的に解放されるようになっています。ARCの機能について詳しくは、『Transitioning
to ARC Release Notes 』を参照してください。
プラットフォームにかかわらず、メモリリークは避けなければなりません。mallocを使うコードにつ
いては、割り当てはできるだけ遅く、解放はできるだけ早く、というのが原則です。メモリリークの
発生位置を追跡するためにはInstrumentsが有効です。
メモリ割り当ての技法
メモリは基本的な資源なので、OS X、iOSともに、割り当ての手段をいくつか用意しています。用途
によって使い分けるのですが、いずれも内部的にはmallocを使って実装されています。Cocoaオブジェ
クトでさえ、割り当て処理を調べていくと、最終的にmallocに行き着きます。性能評価ツールはこの
事実を利用して、メモリの割り当て状況を追跡、出力するのです。
Cocoaアプリケーションでは、NSObjectのallocメソッドを呼び出す形でしか、メモリを割り当てな
いかも知れません。その場合でも、オブジェクトとして管理されるメモリブロックだけでは足りず、
他のメモリ割り当て技法が必要になることがあります。たとえば、mallocで直接メモリを確保し、
低レベルの関数に渡す、というような状況です。
以下の各節では、mallocライブラリや仮想メモリシステム、そしてそれがメモリを割り当てる方法に
ついて解説します。これは、それぞれの方法のコストがどの位か、見当をつけていただくのが目的で
す。メモリ割り当てのコードを最適化する参考にしてください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
15
メモリ割り当てに関するヒント
メモリ割り当ての技法
注意: 以下の節では、システムに組み込みのmallocライブラリを前提として解説します。独
自のmallocライブラリを使っている場合、適用できないかも知れません。
オブジェクトを割り当てる
Objective-Cベースのアプリケーションでは、オブジェクトを割り当てる技法が2つあります。クラス
メソッドであるallocと当該クラスの初期化メソッドを順に呼び出す方法と、やはりクラスメソッド
であるnewを呼び出す(その内部では、オブジェクトを割り当ててデフォルトの初期化メソッドであ
るinitを実行)方法です。
オブジェクト生成後は、ARCの機構が寿命(削除するべき時期)を管理します。新規に生成したオブ
ジェクトを破棄されないようにするためには、少なくとも1つ、当該オブジェクトに対する強い参照
を作らなければなりません。その後、必要に応じて、強い参照、弱い参照を追加してください。強い
参照がすべてなくなると、ARCの機構が働いて自動的に破棄されるようになっています。
ARCの詳細、特にオブジェクトの寿命を管理する仕組みについては、『Transitioning to ARC Release
Notes 』を参照してください。
小容量のメモリブロックをmallocで確保する
小容量(仮想メモリの数ページ未満)のメモリが必要な場合、malloc関数で、フリーブロックのリ
スト(プール)から割り当てます(フリーブロックの容量は随時増加)。その後、free関数で解放し
たブロックはプールに戻り、以後、再び必要になったときに、「ちょうどよい大きさのブロックを割
り当てる」という形で再利用されます。メモリプール自体はいくつかの仮想メモリページが集まって
できており、システムがvm_allocateルーチンで確保し、管理するようになっています。
小容量のメモリブロックをmallocで割り当てる場合、その最小単位(粒度)は16バイトです。すなわ
ち、割り当て可能な最小のブロックは16バイトであり、それ以上のブロックもすべて16バイトの倍数
になっているのです。たとえばmallocで4バイトのメモリブロックを要求すれば、16バイトのブロッ
クが返されます。24バイトを要求すれば32バイトになります。したがって、データ構造体を設計する
際は、できるだけ16バイトの倍数になるよう配慮してください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
16
メモリ割り当てに関するヒント
メモリ割り当ての技法
注意: 仮想メモリページの大きさ未満のメモリブロックは、当然ながら、ページ境界に揃っ
ていません。
大容量のメモリブロックをmallocで確保する
大容量(仮想メモリの数ページ以上)のメモリを要求すると、mallocは自動的にvm_allocateを呼
び出して、要求された容量のメモリを獲得します。vm_allocateルーチンは、現在実行中のプロセス
の論理アドレス空間における、あるアドレス範囲を新しいブロックとして割り当てます。ただし、
カーネルはすぐに物理メモリを割り当てず、次のように処理します。
1.
このプロセスの仮想アドレス空間に、あるメモリ範囲をマップするため、マップエントリ(map
entry)を生成します。これは領域の先頭/末尾アドレスを格納するだけの構造体です。
2.
このメモリ範囲はデフォルトページャが管理することになります。
3.
カーネルはVMオブジェクトを生成、初期化して、マップエントリに対応づけます。
この時点では、物理メモリ上にもバッキングストアにもページはありません。システム内で仮想的に
マップされているだけです。メモリブロック内のある部分にアクセスする(アドレスを指定して読み
書きする)と、そこには物理メモリがマップされていないので、ページフォールトが発生します。OS
Xのカーネルは、VMオブジェクトが当該アドレスに相当するページをバッキングストアに待避してい
ないことも認識します。そこでカーネルは、ページフォールトが発生する都度、次のような処理を行
います。
1.
空きページリストからページを取得し、0を充塡します。
2.
VMオブジェクトの常駐ページリストに、このページの参照を追加します。
3.
pmapというデータ構造体に適切な値を設定することにより、仮想ページを物理ページにマップ
します。pmapにはページ表が含まれます。プロセッサ(または独立したメモリ管理ユニット)
はこれを使って、仮想アドレスを実際のハードウェアアドレスにマップすることになります。
このメモリブロックの最小単位(粒度)は、仮想メモリページの大きさ、すなわち4096バイトです。
4096の倍数でない場合は自動的に切り上げます。したがって、メモリの無駄を避けるため、バッファ
の容量は4096バイトの倍数になるよう配慮してください。
注意: 大容量のメモリを割り当てると、そのブロックは必ずページ境界に揃います。
大容量のメモリが必要な場合、mallocを呼び出すよりも、vm_allocateを直接使って仮想メモリを
割り当てる方がよいかも知れません。リスト 2にvm_allocate関数の使用例を示します。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
17
メモリ割り当てに関するヒント
メモリ割り当ての技法
リスト 2
vm_allocateでメモリを割り当てるコード例
void* AllocateVirtualMemory(size_t size)
{
char*
data;
kern_return_t
err;
// デバッグ時はVMページ境界に
// 揃っていることを確認
check(size != 0);
check((size % 4096) == 0);
// VMから直接割り当て
err = vm_allocate(
(vm_map_t) mach_task_self(),
(vm_address_t*) &data,
size,
VM_FLAGS_ANYWHERE);
// エラーの検査
check(err == KERN_SUCCESS);
if(err != KERN_SUCCESS)
{
data = NULL;
}
return data;
}
メモリを一括して割り当てる
同じ容量のメモリブロックを複数割り当てる場合、malloc_zone_batch_malloc関数を使えば一括し
て処理できます。同じメモリを割り当てるのでも、mallocを繰り返し呼び出すより処理性能に優れ
ています。各ブロックが比較的小容量(4キロバイト以下)であれば特に有利です。この関数は、で
きるだけ要求された容量のメモリを確保しようとしますが、一部しか確保できないこともあります。
したがって、実際に確保されたブロック数を、戻り値で必ず確認してください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
18
メモリ割り当てに関するヒント
メモリ割り当ての技法
メモリブロックの一括割り当てが可能なのは、OS X 10.3以降およびiOSです。詳しくはヘッダファイ
ル(/usr/include/malloc/malloc.h)を参照してください。
共有メモリを割り当てる
共有メモリとは、複数のプロセスが読み書きできるメモリのことです。親プロセスから継承する、共
有メモリサーバが生成する、他のアプリケーションにエクスポートできるようアプリケーションが明
示的に生成する、といった手段で使えるようになります。次のような用途があります。
●
アイコン、音声など、大容量の資源を共有する
●
プロセス間で高速に通信する
共有メモリは不安定なので、より信頼性の高い手段がある場合、一般にお勧めできません。あるプロ
グラムが共有メモリの一部を破損すると、同じメモリを共有する他のプログラムにも影響が及びま
す。共有メモリ領域を生成、管理する関数については、ヘッダファイル(/usr/include/sys/shm.h)
を参照してください。
mallocメモリゾーンを使う
メモリブロックはすべて、mallocゾーン(あるいはmallocヒープ)内に確保されます。ゾーン(zone)
とは仮想メモリ上の可変長の領域で、メモリシステムはここからブロックを割り当てます。ゾーンに
はそれぞれ、空きページリストとメモリページのプールがあり、ゾーン内で割り当てたメモリ領域
は、そのいずれかのページに確保されます。これが有用なのは、アクセスパターンや寿命がよく似た
挙動を示すメモリブロックをいくつも生成しなければならない場合です。ゾーン内で割り当てた多数
のオブジェクトやメモリブロックを、個別に解放する代わりに、一括して破棄することができるから
です。理論上、ゾーンをこのように利用すれば、無駄な空間やページングの発生を最小限に抑えるこ
とができるはずです。実際には、ゾーン化に伴うオーバーヘッドのため、この利点が損なわれること
も少なくありません。
注意: 「ゾーン」という用語は、mallocルーチンでメモリを割り当てるという観点からは、
ヒープ、プール、アリーナという言葉の同意語です。
malloc関数でメモリを確保する場合、特に指定しなければデフォルトのmallocゾーンから割り当てる
ことになります。このゾーンは、mallocの最初の呼び出し時に生成するようになっています。通常
はお勧めできませんが、実測の結果、処理性能が向上するのであれば、追加のゾーンを生成してもよ
いでしょう。たとえば、(他とは切り離された)大量の一時オブジェクトを解放するために処理性能
が低下するのであれば、独自のmallocゾーン内にオブジェクトを生成(あるいはメモリブロックを確
保)し、
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
19
メモリ割り当てに関するヒント
mallocでメモリをコピーする
使い終わっても個別には解放せず、ゾーン全体をまとめて破棄すればよいのです。その場合、当該
ゾーン内のオブジェクトを参照している箇所が残らないよう注意してください。破棄したゾーン内に
アクセスしようとすると、メモリフォールトが発生し、アプリケーションはクラッシュします。
Warning: アプリケーションに割り当てられたデフォルトのmallocゾーンは決して破棄しない
でください。
mallocライブラリのレベルでは、ゾーンを管理する仕組みは/usr/include/malloc/malloc.hに定義
されています。独自にmallocゾーンを生成するためにはmalloc_create_zone関数、アプリケーショ
ンに割り当てられたデフォルトのゾーンを取得するにはmalloc_default_zone関数を使います。ゾー
ンを指定してメモリを割り当てるには、malloc_zone_malloc、malloc_zone_calloc、
malloc_zone_valloc、malloc_zone_reallocの各関数を使います。また、ゾーン内のメモリの解放
は、malloc_destroy_zone関数で行います。
mallocでメモリをコピーする
OS Xには、メモリ領域をコピーする方法として、直接方式と遅延方式があります。多くの場合、直接
方式の方が性能的にも優れていますが、遅延方式が有利な状況もあります。以下の各節では、2つの
方式の違いと、それぞれが有利な状況について説明します。
直接方式でメモリをコピーする
メモリ領域を直接コピーするルーチンとしては、memcpyやmemmoveがあります。いずれも、実際にコ
ピーする時点で、コピー元およびコピー先のブロックがメモリ上になければなりません。特に次のよ
うな状況に向いています。
●
ブロック長が比較的小さい(16キロバイト以下)場合。
●
コピー元またはコピー先を、コピー後直ちに使う場合。
●
いずれかのブロックがページ境界に揃っていない場合。
●
コピー元とコピー先のブロックが重なり合っている場合。
一方、コピー元/コピー先のデータをすぐには使わない場合、大容量のメモリブロックを直接コピー
すると、処理性能が著しく低下する恐れがあります。というのも、アプリケーションのワーキング
セットの容量が大きくなり、したがってページングが発生する可能性も増えるからです。実際、大容
量のメモリブロックを、ワーキングセット内で2回にわたり直接コピーすれば、ほぼ確実にページア
ウトが発生します。コピー元/コピー先のブロックにアクセスする時点で、ディスクから再び読み込
み直すことになるので、vm_copyで遅延コピーする方式に比べて非常に高価な処理になるのです。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
20
メモリ割り当てに関するヒント
メモリ不足の通知に対処する(iOS)
注意: コピー元とコピー先のブロックが重なり合っている場合、memcpyではなくmemmove
を使うようにしてください。OS Xのmemmoveは、重なり合っていても正しくコピーできるよ
う実装されていますが、memcpyはそうなっていません。
遅延方式でメモリをコピーする
何ページ分にもわたるメモリ領域をコピーするけれども、コピー元やコピー先をすぐには使わない、
という場合、vm_copy関数を使うとよいでしょう。memmoveやmemcpyと違い、vm_copyは実メモリを
書き換えません。仮想メモリマップを操作して、コピー先のアドレス範囲が、コピー元アドレス範囲
をコピーオンライト方式でコピーしたものである旨の印をつけるだけです。
vm_copyルーチンがmemcpyよりも効率よく動作するのは、かなり特殊な状況です。具体的に言うと、
コピー後もかなりの時間にわたって、コピー元やコピー先のメモリ領域にアクセスしない、という場
合です。vm_copyによる遅延コピーが効率的に働く理由は、カーネルがコピーオンライト方式でメモ
リを管理する方法にあります。カーネルは、コピー処理を実行するために、コピー元ページへの参照
をすべて、仮想メモリシステムから削除します。次にプロセスがコピー元ページ上のデータにアクセ
スすると、ソフトフォールトが発生します。そこでカーネルは、当該ページをプロセス空間に、「コ
ピーオンライト」ページとしてマップし直します。1回のソフトフォールトを処理するプロセスのコ
ストは、データを直接コピーする場合とほぼ同等です。
小容量のデータをコピーする
重なり合わない小容量のブロックをコピーする場合は、できるだけmemcpyを使ってください。この
ような場合、GCCコンパイラは、関数呼び出しをインライン命令に置き換え、値渡しでデータをコ
ピーするよう最適化します。memmoveやBlockMoveDataなど、他のルーチンは最適化できません。
データをビデオRAMにコピーする
データをVRAMにコピーする場合は、bcopyなどではなくBlockMoveDataUncached関数を使ってくだ
さい。bcopy関数はキャッシュ操作命令を使っているため、例外が発生することがあります。カーネ
ルはこれに対処しなければならないため、処理性能が著しく損なわれます。
メモリ不足の通知に対処する(iOS)
iOSの仮想メモリシステムはバッキングストアを使わないため、代わりにアプリケーション側が、オ
ブジェクトへの強い参照を適切に削除しなければなりません。空きページ数が、あらかじめ計算して
おいた閾値を下回ると、システムは長い期間にわたって書き換えられていないページをできるだけ解
放すると同時に、実行中のアプリケーションに対してその旨の通知を送ります。この通知を受け取っ
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
21
メモリ割り当てに関するヒント
メモリ不足の通知に対処する(iOS)
たアプリケーションは、警告に応じて適切に対処しなければなりません。すなわち、できるだけ多く
のオブジェクトに対する強い参照を削除する必要があります。たとえば、必要に応じていつでも再生
成できるデータキャッシュは、消去してしまっても構わないでしょう。
UIKitにはメモリ不足の通知を受け取る手段がいくつかあります。
●
●
アプリケーションデリゲートにapplicationDidReceiveMemoryWarning:メソッドを実装する方
法。
UIViewControllerのカスタムサブクラスで、didReceiveMemoryWarningメソッドをオーバーラ
イドする方法。
●
UIApplicationDidReceiveMemoryWarningNotification通知を受け取るように登録する方法。
いずれにしても、通知を受け取ったハンドラメソッドは、オブジェクトに対する強い参照を直ちに削
除しなければなりません。ビューコントローラは画面に現れていないビューへの参照を自動的に削除
しますが、それに加えてdidReceiveMemoryWarningメソッドをオーバーライドし、不要になった他
の参照も削除するようにしなければなりません。
消去しても構わない資源を持つカスタムオブジェクトが少数であれば、そのオブジェクトも
UIApplicationDidReceiveMemoryWarningNotification通知も受け取るよう登録し、参照を削除す
るとよいでしょう。一方、消去可能なオブジェクトが多数ある、あるいは選択的に消去しなければな
らない場合は、アプリケーションデリゲートを使って、保持する必要があるかどうか判断する、とい
う方法があります。
Important: システムアプリケーションと同様、テスト中にメモリ不足の警告を受け取ることがな
かったとしても、いつでも対処できるよう備えておく必要があります。システムアプリケーショ
ンはさまざまな要求を処理するために、少量のメモリを必要とします。メモリ不足を検出すると、
システムはその旨の警告を稼働中のプログラムすべてに送信し、(必要ならば)バックグラウン
ドアプリケーションをいくつか停止して、状況を緩和しようとします。充分な量のメモリを解放
できなかった(メモリリークが起こっている、なお大量のメモリを消費している、など)場合、
システムがアプリケーションを強制停止することがあります。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
22
キャッシュと破棄可能なメモリ
キャッシュや破棄可能なメモリは、大量のメモリや計算時間を要する巨大なオブジェクトを扱う、あ
るいはRAM不足のためデータをディスクに退避しなければならず本来の処理が進まない、という状況
において、まさに慈雨の恵みと言えるでしょう。
キャッシュの概要
キャッシュはオブジェクトやデータのコレクションの一種で、処理性能を大幅に向上する効果があり
ます。
なぜキャッシュを使うか
頻繁にアクセスするオブジェクトで、そのデータを計算するための負荷が高い場合、キャッシュの技
法が有効です。2回目からは値を計算する必要がないので、処理性能を改善できます。しかしこのオ
ブジェクトは、なくなってもアプリケーション処理にとって大きな問題にはならないので、メモリが
不足すれば破棄されることになります。その場合、次に必要になった時点で再計算しなければなりま
せん。
キャッシュに伴う問題
キャッシュはうまく活用すれば性能が大幅に向上しますが、いくつか短所もあります。中でも問題な
のは、大容量のメモリを占有しうることです。巨大なデータオブジェクトを大量にキャッシュする
と、他のアプリケーションに割り当てるRAMが不足し、そのデータをディスクに退避して空きメモリ
を確保する必要が生じるため、本来の処理が進まなくなってしまいます。
解決策
CocoaのNSCacheオブジェクトは、キャッシュ対象を収容する便利なストレージコンテナとして動作
し、しかも先に述べたメモリ管理の問題にも対処しています。NSCacheクラスはNSDictionaryクラ
スと、キーと値の組を保持する、という点で非常によく似ています。しかしNSCacheオブジェクトに
は、「能動的な破棄」を行う性質があります。すなわち、メモリが潤沢である間は、与えられたデー
タを「貪欲に」キャッシュします。しかし枯渇してくると、自動的に一部を破棄して、他のアプリ
ケーション用にメモリを解放するのです。その後、再びこのデータが必要になったときには、改めて
計算しなければなりません。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
23
キャッシュと破棄可能なメモリ
破棄可能なメモリを使う
NSCacheにはほかにも2種類の、有用な「制限」機能があります。キャッシュする要素数の制限と、
キャッシュ内の要素すべてにわたる総コストの制限です。要素数の制限は、setCountLimit:メソッ
ドで設定します。たとえばcountLimitが10であるキャッシュに11個の要素を追加しようとすると、
自動的に1個が破棄されます。
要素を追加する際、キーと値の組ごとに、cost値を指定できます。setTotalCostLimit:メソッド
は、この値の総和の上限を設定します。すると、ある要素を追加した結果、totalCostの値が
totalCostLimitを上回った場合、自動的にいずれかの要素を破棄して、この閾値を下回るようにし
ます。もっとも、閾値を超えることはない、という保証はないので、想定する動作になるようcost値
を下手にいじると、逆に性能に悪影響が出る可能性があります。この機能が不要であれば、costとし
て0を渡すか、引数としてコストを渡す必要がないsetObject:forKey:メソッドを使ってください。
注意: 要素数、総コストとも、制限が厳格に適用されるわけではありません。制限を超えて
も即座に破棄されるとは限らず、しばらく経ってから破棄されたり、最後まで破棄されな
かったりすることがあり、これはキャッシュの実装詳細に依存します。
破棄可能なメモリを使う
CocoaフレームワークにはNSPurgeableDataクラスも組み込まれており、アプリケーションが過剰に
メモリを使わないよう支援する働きがあります。NSPurgeableDataクラスはNSDiscardableContent
プロトコルに従っており、あらゆるクラスに、そのインスタンスに対するアクセスが終了したとき、
自動的にメモリを破棄する仕組みを実装できます。使い捨てのサブコンポーネントがあるオブジェク
トを作成する場合、NSDiscardableContentプロトコルを実装しなければなりません。また、
NSPurgeableDataクラスはNSCacheと切り離して使っても構いません。破棄の機能だけでも実装でき
るのです。
破棄可能なメモリの利点
破棄可能な(purgeable)メモリを活用すると、必要な時点で迅速にメモリを回収できるため、処理性
能も向上します。破棄可能という印がついたメモリ領域は、仮想メモリシステムが働いても、ページ
アウトの対象としてディスクに書き出すという、処理時間を要する処理が必要ありません。単に破棄
するだけであり、後で必要になれば再計算することになります。
破棄可能なメモリは、アクセスに先立ち、メモリブロックをロックする必要があることに注意してく
ださい。これは、アクセス中に破棄されてしまうことを避けるためです。ロック機構にはこれと同様
に、仮想メモリシステムがデータを破棄しないよう保証する働きもあります。NSPurgeableDataには
非常に単純なロック機構が実装されており、読み込み中はデータが安全である(破棄されない)こと
が保証されます。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
24
キャッシュと破棄可能なメモリ
破棄可能なメモリを使う
破棄可能なメモリの実装方法
NSPurgeableDataクラスはNSDiscardableContentプロトコルを実装しているだけなので、使い方は
非常に単純です。「カウンタ」変数に、NSDiscardableContentオブジェクトの寿命を管理する中核
的役割があります。このオブジェクトが管理するメモリ領域の読み込み中(あるいは必要である間)、
カウンタ変数の値は1以上になります。使い終わって破棄しても問題ない状態になると、0になりま
す。
メモリが枯渇してくると、カウンタ値が0である領域が破棄されます。これは当該オブジェクトの
discardContentIfPossibleメソッドで行います。カウンタ値が0ならばメモリを解放するメソッド
です。
NSPurgeableDataオブジェクトを生成した時点ではカウンタ値が1であり、安全にアクセスできま
す。このメモリ領域にはbeginContentAccessメソッドでアクセスします。このメソッドはまず、
データが破棄されてしまっていないか確認します。破棄されていなければ、カウンタ値を増やして、
読み込み中メモリが保護されるようにした上で、YESを返します。一方、破棄されてしまっていれば
NOを返します。データへのアクセスが終了したら、endContentAccessを呼び出してください。カウ
ンタ値を減らし、必要ならばシステムがメモリを破棄できるようにするメソッドです。このようにカ
ウンタ変数の状態を追跡し、beginContentAccessメソッドの戻り値がYESである場合にのみアクセ
スするようにしてください。
システムオブジェクトやクライアントオブジェクトは、メモリが枯渇しそうになると、
discardContentIfPossibleメソッドを呼び出して破棄可能なデータを破棄します。このメソッド
は、カウンタ値が0のデータを破棄し、それ以外はそのまま残します。最後に、isContentDiscarded
メソッドは、メモリが破棄されていればYESを返します。
NSPurgeableDataオブジェクトの消長を表すコード例を以下に示します。
NSPurgeableData * data = [[NSPurgeableData alloc] init];
[data endContentAccess]; //当面このデータは必要ないので、破棄しても構わないという印をつける
//後で必要になりそうなデータをキャッシュに入れておく
...
if([data beginContentAccess]) { //データが破棄されていなければ、カウンタ値を増やしてYESを返
す
...Some operation on the data...
[data endContentAccess] //データを使い終わったので、破棄しても構わないという印をつける
} else {
//データは破棄されてしまっているので、再計算した上で処理を行う
data = ...
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
25
キャッシュと破棄可能なメモリ
破棄可能なメモリが有効な状況
[data endContentAccess]; //データを使い終わった
}
//この時点では、メモリが枯渇しそうであればデータを破棄しても構わない
破棄可能なメモリとNSCache
NSDiscardableContentプロトコルを実装したオブジェクトをNSCacheに入れると、キャッシュはこ
のオブジェクトに対する強い参照を保持します。しかし、その内容が破棄され、キャッシュの
evictsObjectsWithDiscardedContentの値がYESになると、オブジェクトは自動的にキャッシュの
管理対象外になり、検索しても見つからなくなります。
破棄可能なメモリに関する若干の注意
破棄可能なメモリを直接使えるのは、大容量のオブジェクトやメモリチャンクに限ります。関係する
APIは、仮想メモリの複数ページにわたるオブジェクトに対して作用するので、小さなキャッシュ要
素に対して破棄可能という印をつけるのは困難だからです。そこで、キャッシュ処理APIが領域管理
を行い、小さな要素でも破棄可能なメモリを利用できるようにしています。同様に、APIを直接使っ
てキャッシュ要素にメモリを割り当てるのが難しいこともあります。オブジェクトの割り当てに便宜
メソッドを使った、あるいはキャッシュ対象とは別のレイヤに割り当てた、というような状況です。
このような場合、破棄可能なメモリを利用することはできません。
破棄可能なメモリが有効な状況
破棄可能なメモリが有効なのは、破棄に伴うコスト(データ値の再計算に要するコストと、当該デー
タが再利用される確率の積)が、ページングのコストに比べて小さい場合です。キャッシュの多く
は、投機的に保存しておくという性質上、再び使われることがほとんどないので、この条件に当ては
まります。再計算が容易なデータも同様に、再計算のために性能が大きく損なわれることはないの
で、破棄可能なメモリを利用する有力な候補です。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
26
メモリの使用状況の追跡
メモリが効率的に使われているかどうか疑わしい場合は、検討の基礎となるデータをまず収集しなけ
ればなりません。Appleが提供する性能計測ツールでメモリの使用状況を調べれば、問題が起こりう
る箇所、さらに調査が必要な箇所を特定できます。以下の各節では、メモリの分析によく使われる
ツールや、それが有効な状況について説明します。
メモリの割り当て状況をInstrumentsで追跡する
性能分析には、何よりもまずInstrumentsアプリケーションを利用してください。これは統合データ収
集環境で、「instrument」という特別なモジュールを使って、プロセスに関するあらゆる情報を収集
します。配布用のバイナリファイルに対しても適用でき、特別なモジュールをコンパイルしてアプリ
ケーションに組み込む必要はありません。Instrumentsアプリケーション用のライブラリとして、メモ
リ関係のデータ収集に特化したモジュールが4つあります。
●
●
「ObjectAlloc」というinstrumentは、アプリケーション起動以降の、メモリ割り当ての履歴をすべ
て記録、表示します。
「Leaks」というinstrumentは、どこからも参照されていないメモリ領域を検出します(“Instruments
でメモリリークを検出する” (30 ページ)を参照)。
●
「Shared Memory」というinstrumentは、共有メモリ領域を開く/閉じる処理を監視します。
●
「Memory Monitor」というinstrumentは、システム全体のメモリ使用状況を計測、記録します。
必要に応じて上記のinstrumentをいくつでもトレース文書に追加し、それぞれ同時にデータを収集で
きます。したがって、各instrumentが収集したデータを関連づけて分析することも可能です。たとえ
ば、「Leaks」を「ObjectAlloc」と組み合わせて、割り当て状況を追跡しながらメモリリークを検出す
る、という使い方が考えられます。
データを収集したら、次にこれを分析しなければなりません。以下、メモリ関係のinstrumentで収集
したデータを分析する際のヒントをいくつか紹介します。「Leaks」instrumentの使い方については、
“Instrumentsでメモリリークを検出する” (30 ページ)を参照してください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
27
メモリの使用状況の追跡
malloc_historyでメモリ割り当て状況を追跡する
「ObjectAlloc」でメモリの割り当て状況を分析する
「ObjectAlloc」instrumentを使うと、アプリケーションのメモリ割り当て状況を追跡できます。追跡
する期間は、アプリケーションを起動してから、停止を指示するまでの間です。その間、生成された
オブジェクトの数および容量(または型)を表示するようになっています。アイコン表示モードに切
り替えると、ヒストグラムが実時間で表示されるので、オブジェクト数の変化や傾向が即座に分かり
ます。また、割り当てと解放の履歴を保持しているので、あるオブジェクトが割り当てられた箇所を
遡って調査できます。
「ObjectAlloc」instrumentが表示する情報を実際に記録するのは、Core Foundationフレームワークに組
み込まれた統計機能の役割です。この機能が有効になっている間、メモリの割り当て/解放をすべて
記録するようになっています。
共有メモリの使用状況を分析する
「Shared Memory」instrumentはMacアプリケーションを対象として、システムの共有メモリ領域を開
くshm_open関数、閉じるshm_unlink関数の呼び出しを追跡します。この情報をもとに、アプリケー
ションが共有メモリ領域の参照を取得する位置や頻度を分析できます。詳細ペインには、個々の関数
呼び出しと、その時点の呼び出し環境に関する情報が表示されます。呼び出し環境とは、具体的に言
うと、呼び出し元である実行ファイル名と、関数に渡された引数の値です。さらに「Extended Detail」
ペインには、呼び出し時のスタックトレースが表示されます。
「Memory Monitor」で収集したデータを分析する
「Memory Monitor」instrumentはMacアプリケーションを対象として、システムメモリの使用状況統計
を表示します。アプリケーション内、あるいはシステム全体の、メモリ使用の傾向を把握するために
有用です。たとえば、使用中、休止中、固定、空きの各メモリ容量が分かります。さらに、ページイ
ン/ページアウトされたメモリ容量も表示されます。他のinstrumentが収集した情報と組み合わせる
と、特定の処理に使ったメモリ量を追跡することも可能です。
malloc_historyでメモリ割り当て状況を追跡する
OS Xの場合、malloc_historyツールを使って、mallocやfreeを呼び出した時点のバックトレース
データを表示できます。アドレスを指定してmalloc_historyを実行すれば、当該アドレスで発生し
たメモリ割り当てのみを追跡できます。オプションとして「-all_by_size」や「-all_by_count」
を指定すれば、すべての割り当てが、容量の順、あるいは回数の順に表示されます。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
28
メモリの使用状況の追跡
「heap」でメモリを検査する
malloc_historyツールを適用するためには、環境変数MallocStackLoggingを1と設定することによ
り、mallocライブラリのログ機能を有効にする必要があります。さらに環境変数
MallocStackLoggingNoCompactも設定すれば、解放済みブロックに関する情報も記録できます。こ
れらの環境変数について詳しくは、“mallocのデバッグ機能” (33 ページ)を参照してください。
malloc_historyツールが最も有効なのは、メモリブロックの以前の所有者を知りたいという状況で
しょう。あるデータが何らかの原因で破損した場合、その時点のブロックのアドレスを印字する処理
を、コードを追加してください。すると、malloc_historyを使って、当該ブロックを誰が所有して
いるか調べ、いわゆる「ステイルポインタ」(既に解放したメモリ領域を別名で指しているポイン
タ)を特定できます。
malloc_historyツールは、「Sampler」や「MallocDebug」が使えない状況にも有効です。たとえば、
遠隔コンピュータから分析したい、あるいはプログラムの動作に対する影響を最小限にしたい、とい
う状況にも適用できるのです。
malloc_historyツールの使い方について詳しくは、malloc_historyのマニュアルページを参照し
てください。
「heap」でメモリを検査する
OS Xでは、「heap」というコマンドラインツールを使って、mallocで割り当てたメモリのスナップ
ショットを表示し、あるいは特定のプロセスのアドレス空間を調べることができます。Cocoaアプリ
ケーションであれば、Objective-Cのオブジェクトを名前で特定することも可能です。メモリブロック
やオブジェクトに関する情報をヒープ単位で把握し、同じヒープに属するものを一括して表示でき
る、という特徴があります。
heapツールで調べられる情報の多くは「ObjectAlloc」instrumentのそれと同じですが、調査対象に対
する影響はより小さくて済みます。遠隔セッションからも利用できますし、Instrumentsを使うと処理
性能が落ち、結果が変わってしまうような状況にも適用可能です。
heapツールの使い方について詳しくは、heap(1)のマニュアルページを参照してください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
29
メモリリークの検出
メモリリークとは、割り当て済みのメモリブロックのうち、プログラムから参照されなくなったもの
のことです。決してアクセスされないので空間の無駄になっているばかりでなく、ページングが余計
に発生するため、処理時間の無駄にもなります。その結果、メモリを適切に回収していれば不要で
あったはずの、余分な仮想メモリページを割り当てることになってしまいます。
mallocを使うアプリケーションの場合、メモリリークはバグであって、修正しなければなりません。
Objective-Cのオブジェクトのみ使うアプリケーションであれば、ARCの機構により自動的に解放され
るので、一般にメモリリークの問題は回避できます。しかし、Objective-CのオブジェクトとCベース
の構造体が混在するアプリケーションでは、オブジェクトの所有関係をきちんと管理して、リークを
回避しなければなりません。
mallocライブラリで確保したメモリを回収するためには、明示的に回収処理を実行する必要がありま
す。mallocその他、メモリを割り当てるルーチンに対応して、freeも呼び出さなければなりません。
メモリリークが発生する典型的な状況として、データ構造体に埋め込まれたポインタで参照されるメ
モリ領域を解放し損なう、というものがあります。構造体の要素にポインタがある場合、構造体自身
を解放する前に、当該ポインタで参照されるメモリ領域を解放しなければなりません。
もうひとつ、あるポインタにメモリを割り当てた後、解放せずに別の値を代入する、というバグもよ
くあります。上書きした結果、メモリブロックへの参照が失われ、解放できなくなってしまうので
す。
Instrumentsでメモリリークを検出する
Instrumentsアプリケーションのメモリリーク検出機能は、OS XとiPhoneのどちらにも適用できます。
Instrumentsで文書テンプレートを生成し、「Leaks」instrumentを追加してください。このinstrument
のメモリリーク検出機能は、コマンドラインツール「leaks」と同等です。アプリケーションで発生
する割り当てイベントをすべて記録し、書き込み可能なメモリ、レジスタ、スタックを定期的に調べ
て、使用中のメモリブロックを参照しているかどうか確認します。どこからも参照されていないブ
ロックがあれば、メモリリークと看做して、関連情報とともに「Detail」ペインに表示するようになっ
ています。
「Detail」ペインを表モードまたはアウトラインモードにすると、解放漏れメモリブロックの表示に
なります。表モードでは、解放漏れのブロックが、大きさの順にすべて表示されます。いずれかのエ
ントリを選択し、メモリアドレスに添えられた矢印ボタンをクリックすると、当該アドレスのメモリ
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
30
メモリリークの検出
「leaks」ツールを使う
ブロックの割り当て履歴が表示されます。この割り当て履歴のエントリを選択すると、文書ウインド
ウの「Extended Detail」ペインに、当該イベントのスタックトレースが表示されます。一方、アウト
ラインモードに切り替えると、呼び出し木に沿った形でメモリリークが表示されるので、コードの特
定の枝における発生状況が分かります。
Instrumentsアプリケーションの使い方、特に「Leaks」instrumentについては、『Instruments User Guide 』
を参照してください。
「leaks」ツールを使う
OS Xの場合、「leaks」というコマンドラインツールで、プロセスの仮想メモリ空間のうち、malloc
で確保したけれどもどこからも参照されなくなったバッファを検出できます。見つかったバッファそ
れぞれについて、leaks次の事項を表示するようになっています。
●
解放漏れメモリのアドレス
●
その容量(バイト単位)
●
その内容
leaksはさらに、Objective-CやCore Foundationのオブジェクトであることを検出した場合、その名前
も表示するようになっています。バッファの内容表示が不要な場合は、leaksの起動の際に
「-nocontext」オプションを指定してください。環境変数MallocStackLoggingを設定した上で、
gdb上でアプリケーションを実行すれば、leaksでスタックトレースを表示し、バッファの割り当て
箇所を調べることも可能です。mallocのデバッグ機能については、“mallocのデバッグ機能” (33 ペー
ジ)を参照してください。
注意: 「leaks」コマンドラインツールは/usr/bin以下にあります。
メモリリークの検出能力を向上するためのヒント
メモリリークを容易に検出できるよう、次のガイドラインに従ってください。なお、このガイドライ
ンの多くはleaksツールを想定していますが、MallocDebugその他に適用可能なものもあります。
●
●
ユニットテストの際にleaksを実行してください。ユニットテストでは、あらゆるコードの経路
を繰り返し実行するので、稼働環境でテストする場合に比べてメモリリークを検出しやすくなり
ます。
leaksに「-exclude」オプションを指定すれば、リークがあると分かっている関数を除外して調
べることができます。leaksの表示量を減らして、未知のリークを発見しやすくする効果があり
ます。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
31
メモリリークの検出
メモリリークの検出能力を向上するためのヒント
●
leaksがリークを検出したりされなかったりする場合は、該当するコードの経路を囲む形でルー
プを組み込み、数百~数千回繰り返してみてください。リークが安定して検出しやすくなりま
す。
●
gdb上で、libgmalloc.dylibを結合してプログラムを実行してみてください。このライブラリに
はデバッグ機能を高めたmallocがあり、潜在的なバグを検出しやすくなっています。詳しくは、
libgmallocのマニュアルページを参照してください。
●
Cocoa/iPhone用アプリケーションで、メモリリークを修正した結果、プログラムがクラッシュす
るようになった場合、解放済みのオブジェクトやメモリバッファにアクセスしようとしているこ
とが考えられます。環境変数NSZombieEnabledをYESと設定し、解放済みオブジェクトに関する
メッセージが表示されないか調べてください。
ユニットテストの多くは、所定の経路に沿ってコードを実行した後、終了するようになっています。
これはまったく正常な動作ですが、leaksツールがプロセスメモリ空間を分析するには相応の時間が
かかるので、テストが終わってもすぐには終了しないようにしなければなりません。たとえば無限に
スリープするプロセスを起動して、アプリケーションが終了しないようにしてください。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
32
mallocのデバッグ機能
メモリ関係のバグは、闇雲に調べても無駄に時間がかかるだけです。該当するメモリ領域を操作して
も、その直後に問題が顕在化するわけではないことが、状況をさらに面倒にしています。幸いXcode
には、実際に問題が発生した箇所の前後に、メモリ関係の問題がないか調べる機能があります。
ガード型mallocを有効にする
ガード型mallocは、mallocライブラリの特別版で、デバッグの際、通常のライブラリを置き換える形
で使います。メモリエラが発生した箇所で、強制的にアプリケーションをクラッシュさせる技法を利
用しています。たとえば、同じ仮想メモリページ上にいくつかメモリ領域を割り当てた後、あるメモ
リ領域を解放する際に、誤ってページ全体を破棄してしまったとしましょう。その後、当該領域にア
クセスしようとすると、直ちにメモリ例外が発生します。たまたまそこにあった他のデータに、それ
と気づかずにアクセスしてしまうことはありません。アプリケーションがクラッシュすれば、デバッ
ガで該当箇所付近を調べ、問題を特定できるでしょう。
この方法でデバッグするためには、Xcodeのスキームエディタで、ガード版mallocを使って実行する
ようプロジェクトを設定する必要があります。Macアプリケーションのほか、シミュレータ上で動作
するiOSアプリケーションにも適用可能です。
ガード型mallocで検出できる問題については、『OS X Man Pages 』のlibgmallocに関するページを参
照してください。
mallocの環境変数を設定する
mallocライブラリには、メモリの内容破壊、ヒープの破損、解放済みメモリの参照、バッファオー
バーランなどを検出するためのデバッグ機能があり、環境変数で有効にすることができます。
MallocCheckHeapStartおよびMallocCheckHeapEachを除き、環境変数の値そのものは何でも構い
ません。「Terminal」上で環境変数の設定を解除するには、unsetコマンドを使います。表 1に、主な
環境変数と、これで有効になる機能を示します。mallocのマニュアルページにはこのような環境変
数がすべて載っています。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
33
mallocのデバッグ機能
mallocの環境変数を設定する
表1
mallocに関係する環境変数
変数
説明
MallocStackLogging
mallocは割り当て処理の都度、コールスタックをログに記録する
ようになります。
MallocStackLoggingに似ていますが、割り当てたバッファの容
MallocStackLoggingNoCompact
量や寿命にかかわらず、すべてログに記録します。
MallocScribble
freeを実行したとき、解放したブロックの各バイトに0x55を書き
込むようになります。
mallocを実行したとき、確保したブロックの各バイトに0xAAを書
MallocPreScribble
き込むようになります。新規に割り当てた領域が特定の値になっ
ていることを前提としたプログラムは、処理に失敗する可能性が
高くなります。
mallocが大容量のページを割り当てる際、その前後にガードペー
MallocGuardEdges
ジを追加するようになります。
MallocDoNotProtectPrelude
MallocGuardEdgesの動作を修正し、mallocが割り当てたページ
の前にはガードページを置かないようにします。
MallocDoNotProtectPostlude
MallocGuardEdgesの動作を修正し、mallocが割り当てたページ
MallocCheckHeapStart
mallocを、この変数で表される所定の回数だけ実行した後、ヒー
プの検証を始めるようになります。変数値が未設定ならば、malloc
の前にはガードページを置かないようにします。
は検証を行いません。
mallocを、この変数で表される回数だけ実行する都度、ヒープを
検証するようになります。変数値が未設定ならば、mallocは検証
MallocCheckHeapEach
を行いません。
スタックのログ出力機能とヒープの検証機能を有効にしてアプリケーションを起動するシェルコマン
ドの例を示します。MallocCheckHeapStartの値は1になっていますが、実際の値は何でも構いませ
ん。シェルのスタートアップファイルで変数値を設定することも可能ですが、その場合は忘れずに
exportしてください。
% MallocStackLogging=1
% MallocCheckHeapStart=1000
% MallocCheckHeapEach=100
% ./my_tool
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
34
mallocのデバッグ機能
mallocの環境変数を設定する
プログラムをgdb上で動作させる場合、環境変数はXcodeのデバッグコンソール上で、コマンド「set
env」を使って設定できます(次の例を参照)。
% gdb
(gdb) set env MallocStackLogging 1
(gdb) run
性能計測ツールの中には、この環境変数を設定しなければデータを収集できないものがあります。た
とえばmalloc_historyで、あるブロックがどこで割り当てられたか特定するためには、
MallocStackLoggingの設定が必要です。さらに、環境変数MallocStackLoggingNoCompactを設定
すれば、あるアドレスに以前割り当てられていたブロックを調べることも可能です。コマンドライン
ツール「leaks」も、MallocStackLoggingを設定することにより、リークしたバッファがどこで割
り当てられたか調査できるようになっています。詳しくはleaksおよびmalloc_historyのマニュア
ルページを参照してください。
メモリの二重解放を検出する
mallocライブラリは、解放済みのバッファに対して重ねてfreeを呼び出すと、その旨を通知するよう
になっています。環境変数MallocStackLoggingNoCompactを設定すると、スタック情報がログに出
力されるので、2回目のfreeの呼び出しがコード上のどこにあるか見つけることができます。これを
もとに、適切な位置にブレークポイントを設定し、デバッガで追跡してみればよいのです。
mallocライブラリの通知は、stderrに出力されます。
ヒープの破損を検出する
ヒープの検査を有効にするためには、環境変数MallocCheckHeapStartおよびMallocCheckHeapEach
に適切な値を設定してください。必ず両方の変数を設定しなければなりません。MallocCheckHeapStart
の値は、mallocが何回呼び出された後にヒープを検証するか、を表します。MallocCheckHeapEachの
値は、mallocが何回呼び出されるごとに検証を実施するか、を表します。
MallocCheckHeapStartは、ヒープが破損するまでにmallocが何回呼び出されるか分かっている場合
に有用です。それ以降は、メモリ割り当てに関するメッセージを、「Terminal」のウインドウに出力
するようになっています。mallocの回数を調べることにより、コード上のどこでヒープが破損する
か、おおよその見当をつけることができます。MallocCheckHeapStartおよびMallocCheckHeapEach
の値をうまく調整すれば、破損が起こる位置をさらに絞り込めるかも知れません。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
35
mallocのデバッグ機能
mallocの環境変数を設定する
メモリ内容の破壊を検出する
メモリ内容が破壊されるバグを検出したいときは、環境変数MallocScribbleに適当な値を設定して
ください。解放済みのメモリブロックに無効なデータを書き込んで、実行時に例外を発生させる働き
があります。なお、環境変数MallocStackLoggingおよびMallocStackLoggingNoCompactも設定し
て、例外の発生箇所をログに出力するようにしてください。例外が発生すれば、malloc_historyコ
マンドで、当該メモリブロックを割り当てたコードを詳しく調べることができます。それをもとに
コードを精査すれば、解放されたはずのブロックを指しているポインタが見つかるでしょう。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
36
仮想メモリの使用状況の表示
仮想メモリの使用状況をより詳しく把握したい場合は、top、vm_stat、pagestuff、vmmapなどのコ
マンドラインツールで、Macアプリケーションの動作を分析するとよいでしょう。ツールで得られる
情報は、システムのプロセス全体を要約したものから、特定のプロセスについて詳しく記述したもの
までさまざまです。
以下の各節では、vm_stat、pagestuff、vmmapを使って、詳細なメモリ使用状況を調べる方法を解
説します。Instrumentsでメモリを分析する方法については、『Instruments User Guide 』およびこの資
料の該当する章を参照してください。また、topの使い方については、『PerformanceOverview 』を参
照してください。
仮想メモリの統計情報を表示する
vm_statというツールは、システム全体の仮想メモリの使用状況を、大まかな統計情報として表示し
ます。特に指定しなければvm_stat統計情報を1回表示して終了しますが、間隔(秒単位)を指定し
て、定期的に更新させることも可能です。ツールの使い方についてはvm_statのマニュアルページを
参照してください。
リスト 1にvm_statの出力例を示します。
リスト 1
vm_statの出力例
Mach Virtual Memory Statistics: (page size of 4096 bytes)
Pages free:
3194.
Pages active:
34594.
Pages inactive:
17870.
Pages wired down:
"Translation faults":
Pages copy-on-write:
9878.
6333197.
81385.
Pages zero filled:
3180051.
Pages reactivated:
343961.
Pageins:
33043.
Pageouts:
78496.
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
37
仮想メモリの使用状況の表示
Mach-Oのコードページを表示する
Object cache: 66227 hits of 96952 lookups (68% hit rate)
Mach-Oのコードページを表示する
pagestuffというツールは、「Mach-O」実行ファイル形式のファイルについて、指定した論理ページ
に関する情報を表示します。指定した各コードページについて、シンボル(関数名、静的データ構造
体名)を表示するようになっています。ページ番号を指定しなければ、__TEXT, __textセクション
の全ページが対象になります。
リスト 2に、TextEditアプリケーションを対象とした、pagestuffの出力例(抜粋)を示します。「-a」
オプションを指定して、実行ファイルのコードページすべての情報を出力しています。各ページの仮
想アドレス位置と、当該ページにある情報の種類が分かります。
リスト 2
pagestuffの出力例(抜粋)
File Page 0 contains Mach-O headers
File Page 1 contains Mach-O headers
File Page 2 contains contents of section (__TEXT,__text)
Symbols on file page 2 virtual address 0x3a08 to 0x4000
File Page 3 contains contents of section (__TEXT,__text)
Symbols on file page 3 virtual address 0x4000 to 0x5000
File Page 4 contains contents of section (__TEXT,__text)
Symbols on file page 4 virtual address 0x5000 to 0x6000
...
File Page 22 contains contents of section (__TEXT,__cstring)
File Page 22 contains contents of section (__TEXT,__literal4)
File Page 22 contains contents of section (__TEXT,__literal8)
File Page 22 contains contents of section (__TEXT,__const)
Symbols on file page 22 virtual address 0x17000 to 0x17ffc
File Page 23 contains contents of section (__DATA,__data)
File Page 23 contains contents of section (__DATA,__la_symbol_ptr)
File Page 23 contains contents of section (__DATA,__nl_symbol_ptr)
File Page 23 contains contents of section (__DATA,__dyld)
File Page 23 contains contents of section (__DATA,__cfstring)
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
38
仮想メモリの使用状況の表示
仮想メモリ領域を表示する
File Page 23 contains contents of section (__DATA,__bss)
File Page 23 contains contents of section (__DATA,__common)
Symbols on file page 23 virtual address 0x18000 to 0x18d48
0x00018000 _NXArgc
0x00018004 _NXArgv
0x00018008 _environ
0x0001800c ___progname
...
「-a」オプションを指定したため、各ページがエクスポートしているシンボルも表示されています。
特定のページのシンボルのみ調べたい場合は、「-a」の代わりにページ番号を指定してください。
pagestuffの詳細や指定可能なオプションについては、pagestuffのマニュアルページを参照してく
ださい。
仮想メモリ領域を表示する
vmmapおよびvmmap64というツールで、あるプロセスに割り当てられた仮想メモリ領域を表示できま
す。それぞれ、32ビットアプリケーション、64ビットアプリケーションの仮想メモリが対象です。あ
るアドレスにあるメモリの目的や使用状況を把握するために役立ちます。仮想メモリ領域それぞれに
ついて、ページの種類、開始アドレス、大きさ(キロバイト単位)、読み書き権限、共有モード、当
該領域内の各ページの目的が表示されます。
以下の各節では、vmmapの出力にどのような意味があるかを解説します。vmmapおよびvmmap64につ
いて詳しくは、vmmapおよびvmmap64のマニュアルページを参照してください。
vmmapの出力例
リスト 3にvmmapの出力例を示します(要点のみ抜粋)。
リスト 3
典型的なvmmapの出力
==== Non-writable regions for process 313
__PAGEZERO
0 [
4K] ---/--- SM=NUL ...ts/MacOS/Clock
__TEXT
1000 [
40K] r-x/rwx SM=COW ...ts/MacOS/Clock
__LINKEDIT
e000 [
90000 [
4K] r--/rwx SM=COW ...ts/w/Clock
4K] r--/r-- SM=SHM
340000 [3228K] r--/rwx SM=COW 00000100 00320...
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
39
仮想メモリの使用状況の表示
仮想メモリ領域を表示する
789000 [3228K] r--/rwx SM=COW 00000100 00320...
Submap
90000000-9fffffff r--/r-- machine-wide submap
__TEXT
90000000
__LINKEDIT
900e9000
__TEXT
90130000 [ 740K] r-x/r-x SM=COW .../Versions/A/CoreFoundation
__LINKEDIT
901e9000 [ 188K] r--/r-- SM=COW .../Versions/A/CoreFoundation
__TEXT
90220000 [2144K] r-x/r-x SM=COW .../Versions/A/CarbonCore
__LINKEDIT
90438000 [ 296K] r--/r-- SM=COW .../Versions/A/CarbonCore
[ 932K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib
[ 260K] r--/r-- SM=COW /usr/lib/libSystem.B.dylib
[...data omitted...]
==== Writable regions for process 606
__DATA
18000 [
4K] rw-/rwx SM=PRV /Contents/MacOS/TextEdit
__OBJC
19000 [
8K] rw-/rwx SM=COW /Contents/MacOS/TextEdit
MALLOC_OTHER
1d000 [ 256K] rw-/rwx SM=PRV
MALLOC_USED(DefaultMallocZone_0x5d2c0)
5d000 [ 256K] rw-/rwx SM=PRV
9d000 [ 372K] rw-/rwx SM=COW 33320000 00000020 00000000
00001b84...
VALLOC_USED(DefaultMallocZone_0x5d2c0)
ff000 [
36K] rw-/rwx SM=PRV
MALLOC_USED(CoreGraphicsDefaultZone_0x10
108000 [ 256K] rw-/rwx SM=PRV
MALLOC_USED(CoreGraphicsRegionZone_0x148
148000 [ 256K] rw-/rwx SM=PRV
[...data omitted...]
Submap
a000b000-a012ffff r--/r-- process-only submap
__DATA
a0130000 [
Submap
a0137000-a021ffff r--/r-- process-only submap
__DATA
a0220000 [
Submap
a0225000-a048ffff r--/r-- process-only submap
__DATA
a0490000 [
28K] rw-/rw- SM=COW .../Versions/A/CoreFoundation
20K] rw-/rw- SM=COW .../Versions/A/CarbonCore
12K] rw-/rw- SM=COW .../IOKit.framework/Versions/A/IOKit
Submap
a0493000-a050ffff r--/r-- process-only submap
__DATA
a0510000 [
36K] rw-/rw- SM=COW .../Versions/A/OSServices
b959e000 [
4K] rw-/rw- SM=SHM
b95a0000 [
4K] rw-/rw- SM=SHM
b9630000 [ 164K] rw-/rw- SM=SHM
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
40
仮想メモリの使用状況の表示
仮想メモリ領域を表示する
b965a000 [ 896K] rw-/rw- SM=SHM
bff80000 [ 504K] rw-/rwx SM=ZER
STACK[0]
__DATA
bfffe000 [
4K] rw-/rwx SM=PRV
bffff000 [
4K] rw-/rwx SM=PRV
c000c000 [
STACK[1]
4K] rw-/rwx SM=PRV .../Versions/A/ApplicationEnhancer
f0001000 [ 512K] rw-/rwx SM=PRV
ff002000 [12272K] rw-/rw- SM=SHM
==== Legend
SM=sharing mode:
COW=copy_on_write PRV=private NUL=empty ALI=aliased
SHM=shared ZER=zero_filled S/A=shared_alias
==== Summary for process 313
ReadOnly portion of Libraries: Total=27420KB resident=12416KB(45%)
swapped_out_or_unallocated=15004KB(55%)
Writable regions: Total=21632KB written=536KB(2%) resident=1916KB(9%)
swapped_out=0KB(0%) unallocated=19716KB(91%)
「-d」オプション(および秒単位の値)を指定すると、vmmapはその間隔で、仮想メモリの使用状況
を2回取得し、その違いを次の3つに分けて表示するようになります。
●
個々の領域の違い
●
1回目にのみ存在した領域
●
2回目にのみ存在した領域
vmmapの出力を解釈する
vmmapの出力には見出しがないので、書式に基づいて各欄の情報を解釈しなければなりません。表 1
に、各欄の意味を示します。
表1
vmmapが出力する各欄の意味
欄番
号
例
説明
1
__TEXT、__LINKEDIT、
MALLOC_USED、STACKな
メモリの用途。Mach-Oのセグメント名、あるいはメモリ
割り当て方法の名前。
ど
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
41
仮想メモリの使用状況の表示
仮想メモリ領域を表示する
欄番
号
例
説明
2
(DefaultMallocZone_0x5d2c0)
ある場合、割り当てに用いたゾーン。
3
4eee000
領域の仮想メモリアドレス。
4
[ 124K]
領域の大きさ(キロバイト単位)
5
rw-/rwx
領域の読み取り/書き込み/実行権限。前半は現在の権限、
後半は与えることができる最大の権限を表します。ダッ
シュ(-)は該当する権限がないことを表します。
6
SM=PRV
領域の共有モード。COW(コピーオンライト)、PRV(プ
ライベート)、NUL(空)、ALI(エイリアス)、SHM(共
有)のいずれかです。
7
...ts/MacOS/Clock
仮想メモリのこの領域にマップされた実行ファイルを表
すパスの末尾部分。領域がスタックやヒープメモリの場
合は空欄になります。
第1欄はメモリの用途を表します。__TEXTセグメントには読み取り専用のコードやデータ、__DATAセ
グメントには読み書き可能なデータが入ります。実行時に割り当てるデータ領域については、当該メ
モリの割り当て方法(スタック上に割り当て、mallocで割り当て、など)が分かります。ライブラ
リからロードする領域については、一番右の欄で該当するライブラリ名が分かります。
仮想メモリ領域の大きさ(第4欄)は、当該領域に対して予約された総容量を表します。実際に割り
当て済みのメモリページ数とは一致しないこともあります。たとえば、vm_allocateを呼び出せばメ
モリページをいくつか確保できますが、実際にアクセスするまで、物理メモリは割り当てられませ
ん。同様に、メモリマップトファイルは一連のページを確保しますが、当該ファイルに対する読み書
きイベントが発生しない限り、実際にページをロードすることはありません。
保護モード(第5欄)は、メモリ領域に対するアクセス権限を表します。読み取り/書き込み/実行のそ
れぞれについて、独立に設定できます。また、仮想メモリ領域ごとに、「現在の」権限と、与えるこ
とができる「最大の」権限が設定可能です。vmmapの出力は、前半が「現在の」権限、後半が「最大
の」権限を表します。したがって、権限がたとえば「r--/rwx」であれば、「現在」は読み取り専用
ですが、読み取り/書き込み/実行のすべての権限を与えることも可能、ということになります。「現
在の」権限としては、領域の書き込み権限を与えないのが通常です。しかし状況によっては変更する
こともあります。たとえばデバッガは、ブレークポイントを設定する目的で、ページに対する書き込
み権限を要求します。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
42
仮想メモリの使用状況の表示
仮想メモリ領域を表示する
注意: 「Mach-O」実行ファイルを構成するページは、通常、書き込み禁止になっています。
先頭ページ(__PAGEZERO、先頭アドレスは0x00000000)は、あらゆる操作が禁止になって
います。したがって、NULLポインタを参照しようとすると、直ちにエラーが発生します。
スタックの直前のページも同様に保護されているので、スタックがオーバーフローすれば、
アプリケーションは直ちにクラッシュします。
共有モード(SM=欄)は、ページがプロセス間で共有されているか、書き換えられたとき何が起こる
か、を表します。プライベートなページ(PRV)は、当該プロセスに対してのみ可視であり、実際に
使用するときに割り当てられます。ページアウトの対象にもなりえます。コピーオンライトのページ
(COW)は、複数のプロセスにより(あるいは単一プロセス内の各所から)共有されます。ページを
書き換えようとすると、当該ページはコピーされ、以後、書き込みプロセスはこの新しいページを対
象にするようになります。空(NUL)のページを共有するとは、ページが実際には物理メモリ上にな
いことを表します。エイリアス(ALI)や共有(SHM)メモリは、プロセス間で共有されます。
共有モードは通常、領域全体に対するモードを表します。たとえば、コピーオンライトのページを書
き換えると、当該アプリケーションにとってプライベートなページになります。しかし、このプライ
ベートなページを含む領域そのものは、ページがすべてプライベートになるまで、なおコピーオンラ
イトのままです。全ページがプライベートになれば、領域もプライベートに変わります。
vmmapの出力中には、サブマップを記述している行があります。これは仮想メモリページの記述を共
有できるようにしたもので、オペレーティングシステムは同じ記述を、複数のプロセス間で再利用し
ます。たとえば0x90000000から0xAFFFFFFFまでのメモリは、各アプリケーションが頻繁に利用す
る、ダイナミックライブラリを収容するサブマップです。仮想メモリ領域の記述が1回で済むので、
オペレーティングシステムのメモリ使用量を節約できます。プロセスすべてが共有するサブマップ
(machine-wide)と、特定のプロセスのみにローカルなサブマップ(process-only)があります。前者
のサブマップを書き換える(たとえば、デバッガがダイナミックライブラリ用のメモリセクションを
書き込み可能にして、デバッグ用のトラップを挿入できるようにする)と、このサブマップはローカ
ルになり、カーネルはこれを収容するためのメモリを確保します。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
43
書類の改訂履歴
この表は「メモリ効率の向上に関するガイドライン 」の改訂履歴です。
日付
メモ
2013-04-23
Technote "Caching and Purgeable Memory" の内容を新たな章として
盛り込みました。
2008-07-02
全体の構成を見直し、iOSへの対応状況に合わせて書き直しました。
2006-06-28
「leaks」コマンドラインツールの入手方法を明確にしました。
2005-07-07
libMallocDebugやmallocゾーンの使い方に関する記述を改訂しまし
た。
2005-04-29
細かな誤りをいくつか訂正しました。メモリの一括割り当て、自動
解放されたオブジェクトのメモリリークの検出に関する節を追加し
ました。
メモリリークをより簡単に検出するためのヒントを追加しました。
資料名を変更しました。旧名は『Memory Performance 』です。
2003-07-25
処理性能に関する、Carbonに特有のヒントを追加しました。
2003-05-15
初版。内容の一部は『Inside OS X:Performance 』にも載っていま
す。
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
44
索引
A
I
accessors for static variables 13
active page lists 10
inactive page lists 10
L
B
leaks tool
31
leaks, finding
backing store, defined 7
batch allocations 18
bcopy function 21
BlockMoveData function 21
BlockMoveDataUncached function 21
32
logical address space. See virtual address space
libgmalloc.dylib
M
malloc debugging 32, 33
malloc heaps. See malloc zones
malloc zones 19
MallocCheckHeapEach environment variable 34
MallocCheckHeapStart environment variable 34
MallocDoNotProtectPostlude environment variable
34
MallocDoNotProtectPrelude environment variable
34
MallocGuardEdges environment variable 34
MallocPreScribble environment variable 34
MallocScribble environment variable 34
MallocStackLogging environment variable 29, 34
MallocStackLoggingNoCompact environment
variable 29, 34
malloc_history tool 28
malloc_zone_batch_malloc function 18
map entries 17
memcpy function 20
memmove function 20
memory management unit (MMU) 7
memory objects. See VM objects
C
calloc function
14
copy-on-write 8
D
debugging memory problems 33–36
default pager 8
disk thrashing 7
F
file mapping 8
free page lists 10
H
hard faults 12
heap tool 29
heaps. See malloc zones
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
45
索引
memory pools. See malloc zones
memory-mapped files 8
memory
See also virtual memory
access faults 12
accessors and 13
allocating 15–19
allocation tips 13
batch allocations 18
blocks 19
copying 20–21
debugging problems 33–36
deferring allocations 13
inheriting 19
initializing 14
large allocation granularity 17
malloc zones 19
performance costs 13
shared 19
small allocation granularity 16
thread safety and 14
wired 9
memset function 14
MMU (memory management unit) 7
pagestuff tool
38
paging in 7, 12
paging out 7, 11
paging, defined 7
pmap structure 17
R
resident memory 9
S
shared memory 19
soft faults 12
T
tools
29
leaks 31
heap
malloc_history
28
ObjectAlloc 28
pagestuff 38
vmmap 39, 43
vm_stat 37
V
N
NSZombieEnabled environment variable
virtual address space
defined 7
virtual memory
accessing 17
debugging
overview 7–10
paging in 12
paging out 11
VM objects 8–9
vmmap tool 39–43
vm_copy function 9, 21
vm_stat tool 37
vnode pager 8
32
O
ObjectAlloc 28
P
page alignment 8
page faults 7, 12
page lists 10–11
page size 7
page tables 7, 17
pages, defined 7
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
46
索引
VRAM, copying data to 21
W
wired memory 9
Z
zones. See malloc zones
2013-04-23 | Copyright © 2003, 2013 Apple Inc. All Rights Reserved.
47
Apple Inc.
Copyright © 2003, 2013 Apple Inc.
All rights reserved.
本書の一部あるいは全部を Apple Inc. から書
面による事前の許諾を得ることなく複写複製
(コピー)することを禁じます。また、製品
に付属のソフトウェアは同梱のソフトウェア
使用許諾契約書に記載の条件のもとでお使い
ください。書類を個人で使用する場合に限り
1 台のコンピュータに保管すること、またそ
の書類にアップルの著作権表示が含まれる限
り、個人的な利用を目的に書類を複製するこ
とを認めます。
Apple ロゴは、米国その他の国で登録された
Apple Inc. の商標です。
キーボードから入力可能な Apple ロゴについ
ても、これを Apple Inc. からの書面による事
前の許諾なしに商業的な目的で使用すると、
連邦および州の商標法および不正競争防止法
違反となる場合があります。
本書に記載されているテクノロジーに関して
は、明示または黙示を問わず、使用を許諾し
ません。 本書に記載されているテクノロジー
に関するすべての知的財産権は、Apple Inc.
が保有しています。 本書は、Apple ブランド
のコンピュータ用のアプリケーション開発に
使用を限定します。
本書には正確な情報を記載するように努めま
した。 ただし、誤植や制作上の誤記がないこ
とを保証するものではありません。
Apple Inc.
1 Infinite Loop
Cupertino, CA 95014
U.S.A.
Apple Japan
〒106-6140 東京都港区六本木 6
丁目10番1号 六本木ヒルズ
http://www.apple.com/jp
Offline copy. Trademarks go here.
Apple Inc. は本書の内容を確認しておりますが、本
書に関して、明示的であるか黙示的であるかを問わ
ず、その品質、正確さ、市場性、または特定の目的
に対する適合性に関して何らかの保証または表明を
行うものではありません。その結果、本書は「現状
有姿のまま」提供され、本書の品質または正確さに
関連して発生するすべての損害は、購入者であるお
客様が負うものとします。
いかなる場合も、Apple Inc. は、本書の内容に含ま
れる瑕疵または不正確さによって生じる直接的、間
接的、特殊的、偶発的、または結果的損害に対する
賠償請求には一切応じません。そのような損害の可
能性があらかじめ指摘されている場合においても同
様です。
上記の損害に対する保証および救済は、口頭や書面
によるか、または明示的や黙示的であるかを問わ
ず、唯一のものであり、その他一切の保証にかわる
ものです。 Apple Inc. の販売店、代理店、または従
業員には、この保証に関する規定に何らかの変更、
拡張、または追加を加える権限は与えられていませ
ん。
一部の国や地域では、黙示あるいは偶発的または結
果的損害に対する賠償の免責または制限が認められ
ていないため、上記の制限や免責がお客様に適用さ
れない場合があります。 この保証はお客様に特定
の法的権利を与え、地域によってはその他の権利が
お客様に与えられる場合もあります。