noocyte のプログラミング研究室 〜プログラムは楽しげに走らねばならない♪〜

アラインメントの大きなメモリ領域を用いて,
高速かつメモリ効率の良い多数の集合を実現する方法

公開:2006/10/08(日)
最終更新:2010/06/12(土)

●「アラインメント」で検索してここにいらした方へ

↓こちらのページの方が適当かもしれません.


1.はじめに

アラインメントの大きなメモリ領域を確保する方法」では, glibc malloc() がアラインメントの大きなメモリ領域を確保するために使用している方法について解説したが, ここではその領域を使って何をしているかということを, 問題を一般化した形で解説する.


2.問題

非常に多数の要素 (子) を持つ集合 (親) を実現するデータ構造を考えよう. 例えば次のようなものである.

使用するデータ構造はどれでも (これら以外でも) よい. 同じデータ構造で実現された集合が多数あるとして, 一つの子 child のアドレスを知っているときに, その親 parent のアドレスをすぐに得られるようにしたい,としよう. もちろん,できるだけメモリ効率が良く高速でなければならない.

glibc malloc() の場合,その動機は次のとおりである.

glibc malloc() は複数のヒープ (親) を管理しており, そのうちの一つからブロック (子) を切り出してアプリケーションに提供する. アプリケーションがそれを free() した時, glibc free() が知っているのは (free() の引数として渡される) 子のアドレスだけで,親のアドレスはわからない. したがって free() は,そのブロックが属するヒープを特定し, そこにブロックを返さなければならない.


3.ダメ解答1

誰でもすぐに思いつくのは,親へのポインタを子に持たせる方法である. 連結リストによる実装の場合,子のデータ構造は次のようになる.

typedef struct Parent Parent_t;
typedef struct Child Child_t;

struct Child {
  Child_t *next;        /* 連結リスト内の次の子   */
  Child_t *previous;    /* 連結リスト内の直前の子 */
  Parent_t *parent;     /* 親 */

  /* 以下,子自身のデータ */
  :
  :
};

ポインタ1個 (4バイト) 増やすだけなので最も簡単だし,速度も申し分ないが, 子の数が非常に多いときは増やしたポインタ分のメモリがもったいない. 子自身のデータのサイズがほんの数バイトしかないときは論外である. しかも,同じ親を持つ子は多数存在し, それらはすべて同じ親へのポインタを持つことになる. 非常に多数の子が皆同じデータを持つなんて,一層もったいない感が強い. もっとなんとかならんのか!


4.ダメ解答2

次に思いつく方法は,データ構造はそのままで,すべての親のすべての子を列挙し, child と一致するものを探す方法である. 親と子のメモリアドレスについて何の仮定もしない場合, 最悪すべての親のすべての子と child を比較する必要がある. これは余分なメモリを必要としない反面,現存する子の総数に比例する手間がかかる. 極端にのんびりしたアプリケーションでない限り,即却下である.

glibc free() のような場合はもっとマシで, あるブロックがあるヒープに含まれているかどうかはアドレスを2回大小比較するだけで判定できるから, 最悪ヒープの総数 NHeaps に比例する手間で済む. さらに,ヒープのアドレスをソートした配列を用意して二分探索すれば log(NHeaps) まで改善できるが,ヒープを追加/削除した場合に配列を作り直す必要がある. できればもっと高速化したいよね〜.


5.正解

さて,いよいよ真打ち登場である.この方法では,次の前提条件をおく.

話をわかりやすくするため,ここではサイズを glibc malloc() のヒープと同じ 1MB (0x00100000 バイト) としよう. 親のアドレスは 1MB 境界にアラインされているので, 0xPPP00000 ('P' は任意の16進数字) になる. そしてその子が存在するアドレス範囲は 0xPPP00000 〜 0xPPPFFFFF である. したがって子のアドレス 0xPPPQQQQQ ('Q' は任意の16進数字) が与えられたとき, 下位20ビットを0にすれば,親のアドレスが瞬時に得られる. しかも余分なメモリは全く必要ない.めでたしめでたし.


6.参考図書

省メモリプログラミング

楽天で買う

価格:4,410円(税込、送料別)

省メモリプログラミング―メモリ制限のあるシステムのためのソフトウェアパターン集 (Software patterns series)
ジェイムズ ノーブル チャールズ ウィアー
ピアソンエデュケーション
売り上げランキング: 80302
おすすめ度の平均: 5.0
5 メモリ制限のあるシステム
5 分類が上手い
5 組み込み向けのデザインパターンとしてはまともです。
5 すべての設計者・プログラマに必須

「省メモリ」とあるが,メモリ管理の高速化についても参考になる技法が解説されている. 時々「malloc 高速(化)」などで検索して来る人がいるが, malloc の速度をこれ以上大きく改善する余地はあまりないと思う (あるとしても非常に難しいだろう).その理由は,

それでもなお malloc の高速化それ自体を目指したい人には「(悲愴な顔で) 頑張ってください」としか言えないが, 「アプリケーションのメモリ管理を高速化したいから高速な malloc が欲しい」というのならあまりにも芸がなさすぎる. そういう人はこの本の「第5章 Memory Allocation:メモリ割当て」を読んで反省してください.(笑)

アプリを高速化したいなら,できるだけ malloc/free を呼び出す頻度を減らすこと. そのためには1回の malloc で確保した大きな領域 (メモリプール) に多数のオブジェクトを詰め込む必要がある (これは省メモリにもなる) が, どのオブジェクトを同じ領域に入れるべきかはオブジェクトの寿命 (extent),サイズ, アラインメントなどを考慮して決める必要がある. 特に,寿命を知っているのはアプリケーションだけだ. 目的に合ったメモリプールならば,malloc/free をそのまま使用する場合に比べて数十倍以上速くなることもある.

■参考

メモリー管理の内側  動的アロケーションの選択肢とトレードオフ、そして実装 (原文)

ところで,省メモリが高速化につながる場合も多い.昔からメモリと速度のトレードオフ (高速でメモリを大量に使用するアルゴリズム (例えばテーブル参照) を使うか, それとも低速でメモリを少ししか使用しないアルゴリズムを使うか) がよく問題になるので,省メモリと高速化は両立しないと思い込んでいる人もいるだろう. しかし最近の CPU は命令実行速度に比べてメモリアクセス速度がはるかに遅いので, 無駄なメモリを削減したり,メモリ上のデータ配置を変える (同時期に頻繁に使用するデータをなるべく少数のキャッシュラインに集中させる) と高速化されることも多い. (1970年代以前の CPU は命令実行とメモリアクセスが同期していたので同程度の速度だった.)

さて,ここで問題.次のコードで大きな2次元配列 (例えば画像データ) をコピーする場合,(1) と (2) のどちらが速いか.またその理由を述べよ. (理由を書かなければ0点)

int src[M][N], dest[M][N];
unsigned i, j;

// (1)
for(i = 0;  i < M;  i++)
  for(j = 0;  j < N;  j++)
    dest[i][j] = src[i][j];

// (2)
for(j = 0;  j < N;  j++)
  for(i = 0;  i < M;  i++)
    dest[i][j] = src[i][j];



ハッカーのたのしみ―本物のプログラマはいかにして問題を解くか
ジュニア,ヘンリー・S. ウォーレン
エスアイビーアクセス
売り上げランキング: 27265
おすすめ度の平均: 5.0
5 ビットの楽しみ
5 たのしみ? たしなみ?
5 ちゃんと読むと得した気分になれます
5 最後の頑張りに効きます
5 Hackっていうのは、こういうコトさ

主に2進整数やビットパターンのさまざまな演算技法について解説している. 基本的には (特定のプログラミング言語に依存しない) 数学的な解説が中心だが,C言語によるサンプルコードも示している.

アラインメントやオフセットの計算に使える「2の冪乗の倍数への切り上げ/切り下げ」や, メモリブロックの管理に使える「次の2の冪乗への切り上げ/切り下げ」, (ビット/バイト) エンディアン変換や FFT (高速フーリエ変換) で使われるビットリバース (ビット逆順) などを含む「ビットやバイト単位の並べ替え」など.




珠玉のプログラミング

楽天で買う

価格:3,570円(税込、送料別)

珠玉のプログラミング―本質を見抜いたアルゴリズムとデータ構造
ジョン ベントリー
ピアソンエデュケーション
売り上げランキング: 7857
おすすめ度の平均: 4.5
5 アルゴリズムについて勉強したい人には必読の本です
5 楽しく読めるプログラミングの本
5 「プログラミング」と言う作業を見つめなおすのに最適。「設計する」と言う概念がよく分からない初級プログラマにも
5 納得!アルゴリズムは重要
5 プログラマなら読むべき本



プログラミング作法

楽天で買う

価格:2,940円(税込、送料別)

プログラミング作法
プログラミング作法
posted with amazlet at 10.06.20
ブライアン カーニハン ロブ パイク
アスキー
売り上げランキング: 20088
おすすめ度の平均: 4.5
5 良著です。
5 入門書の次の次の次くらいに!
3 繰り返し読む必要あり
5 絶対にお勧めの本です
5 良いプログラマになりたいあなたに



ガベージコレクションのアルゴリズムと実装

楽天で買う

価格:3,360円(税込、送料別)

ガベージコレクションのアルゴリズムと実装
中村 成洋 相川 光
秀和システム
売り上げランキング: 4312
おすすめ度の平均: 3.5
2 擬似コードのバグは見て見ぬふり
5 GCの入門書として今のところ最強!



BINARY HACKS

楽天で買う

価格:3,360円(税込、送料別)

Binary Hacks ―ハッカー秘伝のテクニック100選
高林 哲 鵜飼 文敏 佐藤 祐介 浜地 慎一郎 首藤 一幸
オライリー・ジャパン
売り上げランキング: 35295
おすすめ度の平均: 5.0
5 組み込み系の開発者は必携です
5 ハードコア?なソフトウエア
5 大工さんにおける電動工具の紹介本
5 当然教科書ではない。でも、とても参考になります。
5 バイナリアンの基本



【送料無料】リンカ・ローダ実践開発テクニック

楽天で買う

価格:2,940円(税込、送料別)

著者サポートページ




Linkers & loaders

楽天で買う

価格:3,360円(税込、送料別)

Linkers & Loaders
Linkers & Loaders
posted with amazlet at 10.06.17
John R. Levine
オーム社
売り上げランキング: 139529
おすすめ度の平均: 3.0
4 プログラムが実行される仕組みが良く分かる
1 概要が書かれた本
1 ひどい訳
5 dllのしくみがわかる!
5 パッケージソフト開発者の必読書



実践デバッグ技法

楽天で買う

価格:2,940円(税込、送料別)

実践 デバッグ技法 ―GDB、DDD、Eclipseによるデバッギング
Norman Matloff Peter Salzman
オライリージャパン
売り上げランキング: 79214



デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language)
ジョナサン・B. ローゼンバーグ
アスキー
売り上げランキング: 295384
おすすめ度の平均: 5.0
5 デバッグできないとき
5 普通に読んでいくだけでも面白い
5 デバッカの理論には必読



【送料無料】いかにして問題をとくか第11版

楽天で買う
価格:1,575円(税込、送料別)

いかにして問題をとくか
G. ポリア
丸善
売り上げランキング: 41

7.サイト内関連ページ


8.更新履歴

  1. 2006/10/08(日) 公開
  2. 2007/02/03(土) 『「アラインメント」で検索してここにいらした方へ』を追記.
  3. 2007/07/29(日) アドレス (値) のアラインメントを返すマクロへのリンクを追加.
  4. 2010/06/12(土) 参考図書追加.


Copyright © 2006-2010 noocyte, All rights reserved.
E-mail: relipmoced (a) yahoo.co.jp
  (" (a) " を半角のアットマークに書き替えてください.)
リンクはご自由に.
「noocyte のプログラミング研究室」トップページに戻る.