『Cプログラミング診断室』目次次(第14章 メモリが足りない プロファイル)

第14章 メモリが足りない

巨大データ


まず、プログラムの内容について説明しましょう。

大きなサイズの画像データを処理します。まあ、50000×50000くらいの極めて大きな画像を扱う ことを考えてください。そして、各点には色がついています。こんな巨大な画像、ふつうの方法で はとても処理できません。MB単位ではなく、GB単位になるのですから、当然メモリ上に載るわ けがありません。

画像の圧縮が必要です。画像の圧縮には様々な方法があります。最近のファックスは2次元圧縮 をしているので、スキャナーで読み取ったデータを、約40分の1に圧縮してしまいます。この圧 縮方法のおかげで、鮮明かつ低料金が実現されています。しかし、この2次元圧縮のやり方は、そ う簡単ではありませんし、今回のプログラムが扱うタイプのデータには向きません。

このプログラムでは、1次元圧縮という、もっと簡単な方法を使っています。これはとても簡単 な方法です。画像を、水平ラインに分け、各水平ラインについて、どの色が何ドット続いているか を記憶します。

このソフトは、簡単にいえば、お絵描きソフトみたいに、筆の色を決めて、その筆を使って巨大 なキャンバス(50000×50000)に絵を描くのです。だから、水平ラインを見ていくと、ほとんどの場 合、隣どうしは同色になり、まれにしか色は変化しません。水平方向に50000ドットあっても、実 際には500回も色が変化することは少ないのです。

ここまでの説明に基づいて、リスト14−1を見てください。かなり分かるのではと思います。 本物のリストはもっと長かったのですが、必要部分だけをまとめ直しています。

リスト14−1 (低速版)

     1	#define ON              1
     2	#define OFF             0
     3	#define BASE_COLOR      0 
     4	
     5	extern  int     line_max;
     6	extern  int     y_size, x_size;
     7	
     8	typedef         short   WORD;
     9	typedef unsigned short  UWORD;
    10	typedef         long    LONG;
    11	
    12	typedef struct  {
    13	    UWORD       color_len;
    14	    unsigned    visible:1;
    15	    unsigned    overwrite:1;
    16	    unsigned    mono_color:1;
    17	    unsigned    plane:2;
    18	    unsigned    white:1;
    19	    unsigned    colorno:10;
    20	    }   ELEMENT_DATA;
    21	typedef ELEMENT_DATA    *PELEMENT_DATA;
    22	
    23	typedef struct  {
    24	    LONG        count;
    25	    ELEMENT_DATA        line_data[1];
    26	    }   LINE_DATA;
    27	typedef LINE_DATA       *PLINE_DATA;
    28	
    29	WORD    init_lines( lines )
    30	PLINE_DATA      *lines;
    31	{
    32	int             i;
    33	int             j;
    34	PLINE_DATA      prun;
    35	
    36	for( i = 0 ; i < y_size ; i++ )
    37	    {
    38	    prun = lines[i];
    39	    prun -> count = 1;
    40	    prun -> line_data[0].visible = ON;
    41	    prun -> line_data[0].overwrite = OFF;
    42	    prun -> line_data[0].mono_color = ON;
    43	    prun -> line_data[0].plane = 0;
    44	    prun -> line_data[0].white = ON;
    45	    prun -> line_data[0].colorno = BASE_COLOR;
    46	    prun -> line_data[0].color_len = x_size;
    47	    for( j = 1; j < line_max; j++ )
    48	        {
    49	        prun -> line_data[j].visible = OFF;
    50	        prun -> line_data[j].overwrite = OFF;
    51	        prun -> line_data[j].mono_color = OFF;
    52	        prun -> line_data[j].plane = 0;
    53	        prun -> line_data[j].white = OFF;
    54	        prun -> line_data[j].colorno = OFF;
    55	        prun -> line_data[j].color_len = 0;
    56	        }
    57	    }
    58	}

図14−1 画像データの構成

関数init_linesは、引数で渡された巨大な画像データ用のメモリ領域を初期化します。プログラ ム起動時だけでなく、実行の途中で画像データをクリアしたいときにも呼び出されます。

引数linesは、各ラインデータへのポインタの入った配列(サイズはライン数に一致)へのポイ ンタです。言葉では説明が難しいのですが、図を見れば直ぐに分かるでしょう(図14−1)。

アルゴリズムは超単純で、まず行数y_sizeだけループし、そのループの中で、1ライン分の処理 をします。水平ライン中で同一色が連続する部分を「要素、ELEMENT」と呼ぶことにします。 構造体LINE_DATAが1ライン分のデータです。

    typedef struct  {
        LONG        count;
        ELEMENT_DATA        line_data[1];
        }       LINE_DATA;
countは1ライン中の要素数です。各要素は、配列line_dataに入っています。この宣言では配列サ イズが1ですが、実際にはグローバル変数のline_maxの値分だけの要素の配列に相当するメモリ領 域がちゃんと確保されています。各ラインには、line_max個の要素ELEMENT_DATAがあるのですが、 そのうちの実際に有効なデータ個数はcountに入っています。

この関数は、初期化なので、全画像データを基本色BASE_COLORにしてしまいます。したがって、 各ラインの要素は1つだけです。だから、countは1にします(39行目)。

最初のデータline_data[0]については、きちんとした値を設定します。そして、47行目からの for文中で、残りの要素をゼロクリアしています。

■高速化■

このソフトは、巨大な画像データを対話的に処理するので、高速でなければなりません。

まず、ビットフィールドに代入するのは時間のかかる処理です。たんに整数型の代入よりも時間 がかかります。ysize = 50000, line_max = 500としたとき、内側のforループは50000×499回ルー プします。2495万回です。そして、各回にビットフィールドへの代入が6回、short型への代入が 1回あります。ということは、約2億回近い代入が必要になり、極めて高速なワークステーション でも数秒は確実にかかってしまいます。

さて、このプログラム、データの初期化という単純な作業に数秒もかかっていたのでは、ユーザ には使ってもらえません。ちょっと工夫して、高速なアルゴリズムを見つけださなければいけませ ん。

このforループは、要するに、ELEMENT_DATA型の全フィールドを0にしているだけです。という ことは、prun->line[1]からprun->line[line_max-1]までのメモリ領域を0にすればよいのです。 もう簡単ですね。memsetを使って、for文を

    memset( &(prun -> run_data[1]), 0, sizeof(RUN_DATA)*(run_max-1) );
に置き換えればよいでしょう。これで、一気に数十倍の速度になったでしょう。一応実用的な速度 になったはずです。

■むだ■

この程度の改善で満足してはいけません。大きなデータを扱うときには、ゼロクリアするだけで も相当な時間を食うので、むだなゼロクリアはしないように心がけなければいけません。

そもそも、39行目でcountに1を代入しているのは何でしょうか。有効データが1個だけという 意味なのです。だから、2個目以降のデータはゴミでもかまわないので、内側のforループはなく てもいいはずです。

もちろん、2個目以降のデータが初期化されていると、実際に次々とデータを使いだしたとき、 使用開始前にいちいち初期化をしなくて済むので、メリットが全然ないわけではありません。

リスト14−2に、ラインの第1要素の代入ももっとエレガントにしたのを示します。

リスト14−2 修正版(単純化された高速版)

     1	#define ON              1
     2	#define OFF             0
     3	#define BASE_COLOR      0 
     4	
     5	extern  int     line_max;
     6	extern  int     y_size, x_size;
     7	
     8	typedef         short   WORD;
     9	typedef unsigned short  UWORD;
    10	typedef         long    LONG;
    11	
    12	typedef struct  {
    13	    UWORD       color_len;
    14	    unsigned    visible:1;
    15	    unsigned    overwrite:1;
    16	    unsigned    mono_color:1;
    17	    unsigned    plane:2;
    18	    unsigned    white:1;
    19	    unsigned    colorno:10;
    20	    }   ELEMENT_DATA;
    21	typedef ELEMENT_DATA    *PELEMENT_DATA;
    22	
    23	typedef struct  {
    24	    LONG        count;
    25	    ELEMENT_DATA        line_data[1];
    26	    }   LINE_DATA;
    27	typedef LINE_DATA       *PLINE_DATA;
    28	
    29	WORD    init_lines( lines )
    30	PLINE_DATA      *lines;
    31	{
    32	        int             i;
    33	        PLINE_DATA      prun;
    34	        static  ELEMENT_DATA    pattern = { 0, ON, OFF, ON, 0, ON, BASE_COLOR };
    35	
    36	        pattern.color_len = x_size;
    37	
    38	        for( i = 0 ; i < y_size ; i++ ) {
    39	                prun = lines[i];
    40	                prun -> count = 1;
    41	                prun -> line_data[0] = pattern;
    42	        }
    43	}

この例では、どのラインの先頭要素もまったく同じなので、まずpatternという変数を用意し、 先頭要素に入れるべきパターンを作ってしまいます。color_len以外のフィールドは完全に固定な ので、初期値として設定し、color_lenだけを後で設定します。

先頭要素line_data[0]には、patternを代入するだけです。この代入文は、構造体の代入文です。 ここでの構造体のサイズはたった4バイトしかありませんが、どんな大きな構造体でも、代入文一 発で代入できます。これは忘れないでください。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第14章 メモリが足りない プロファイル)