web-dev-qa-db-ja.com

可変長配列を使用するためのオーバーヘッドはありますか?

可変長配列を使用することによるオーバーヘッドはありますか?配列のサイズは、実行時にコマンドライン引数を介して渡すことができますか?配列を自動で動的に割り当てるのに比べて、なぜ導入されたのですか?

46
Tim

VLAにはある程度のオーバーヘッドがあります(「通常の」名前のコンパイル時サイズの配列と比較して)。

まず、実行時の長さがありますが、言語は実行時に配列の実際のサイズを取得する手段を提供します(sizeofを使用)。これはすぐに、配列の実際のサイズをどこかに格納する必要があることを意味します。これにより、アレイごとのメモリオーバーヘッドがわずかになります。ただし、VLAは自動オブジェクトとしてのみ宣言できるため、このメモリオーバーヘッドは誰もが気付くことはありません。これは、整数型の追加のローカル変数を宣言するのと同じです。

次に、VLAは通常スタックに割り当てられますが、サイズが可変であるため、通常、メモリ内の正確な場所はコンパイル時にわかりません。このため、基礎となる実装は通常、メモリブロックへのポインタとして実装する必要があります。これにより、(ポインタ用の)追加のメモリオーバーヘッドが発生しますが、上記の理由により、これもまったく重要ではありません。また、実際の配列を見つけるためにポインタ値を読み取る必要があるため、パフォーマンスのオーバーヘッドがわずかに発生します。これは、malloc- ed配列にアクセスするときに発生するオーバーヘッドと同じです(名前付きのコンパイル時サイズの配列では取得できません)。

VLAのサイズは実行時の整数値であるため、もちろん、コマンドライン引数として渡すことができます。 VLAは、そのサイズがどこから来たのかを気にしません。

VLAは、割り当て/割り当て解除のコストが低いランタイムサイズのアレイとして導入されました。これらは、「通常の」名前付きのコンパイル時サイズの配列(割り当てと割り当て解除のコストは実質的にゼロですが、サイズは固定です)とmalloc- ed配列(実行時のサイズはありますが、割り当てが比較的大きい)の間に収まります。割り当て解除コスト)。

VLAは、自動(つまりローカル)オブジェクトと[ほぼ]スコープに依存するライフタイムルールに従います。つまり、一般的に、malloc- ed配列を置き換えることはできません。これらの適用範囲は、通常の自動有効期間を備えた高速ランタイムサイズのアレイが必要な状況に限定されます。

43
AnT

可変長配列には実行時のオーバーヘッドがありますが、それを測定するにはかなりの労力を費やす必要があります。 vlaが可変長配列の場合、sizeof(vla)はコンパイル時定数ではないことに注意してください。

配列のサイズは、実行時に関数に渡すことができます。コマンドライン引数からサイズを取得して整数に変換し、実行時に関数に渡すことを選択した場合は、それで問題ありません。

変数は自動的に正しいサイズに割り当てられ、関数の終了時に自動的に解放されるため、可変長配列が使用されます。これにより、スペースの過剰割り当て(ほとんどの場合、最小サイズで作業する場合に可能な最大サイズに十分なスペースを割り当てる)が回避され、メモリのクリーンアップに関する問題が回避されます。

さらに、多次元配列では、[〜#〜] afaik [〜#〜] Fortranのように動作します-すべての固定サイズに固執するのではなく、すべての次元を動的に構成できます配列の先頭の次元。


VLAの実行時オーバーヘッドの具体的な証拠-少なくともSPARC(Solaris 10)のGCC4.4.2では。

以下の2つのファイルについて考えてみます。

vla.c-可変長配列を使用

#include <assert.h>
#include <stddef.h>
extern size_t identity_matrix(int n, int m);

size_t identity_matrix(int n, int m)
{
    int vla[n][m];
    int i, j;
    assert(n > 0 && n <= 32);
    assert(m > 0 && m <= 32);
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < m; j++)
        {
            vla[i][j] = 0;
        }
        vla[i][i] = 1;
    }
    return(sizeof(vla));
}

fla.c-固定長配列を使用

#include <assert.h>
#include <stddef.h>
extern size_t identity_matrix(int n, int m);

size_t identity_matrix(int n, int m)
{
    int fla[32][32];
    int i, j;
    assert(n > 0 && n <= 32);
    assert(m > 0 && m <= 32);
    for (i = 0; i < n; i++)
    {
        for (j = 0; j < m; j++)
        {
            fla[i][j] = 0;
        }
        fla[i][i] = 1;
    }
    return(sizeof(fla));
}

コンパイルとオブジェクトファイルのサイズ

比較のために、ローカル配列の名前は異なり(vlafla)、宣言されたときの配列の次元は異なります。それ以外の場合、ファイルは同じです。

私は以下を使用してコンパイルしました:

$ gcc -O2 -c -std=c99 fla.c vla.c

オブジェクトファイルのサイズは多少異なります。「ls」と「size」の両方で測定されます。

$ ls -l fla.o vla.o
-rw-r--r--   1 jleffler rd          1036 Jan  9 12:13 fla.o
-rw-r--r--   1 jleffler rd          1176 Jan  9 12:13 vla.o
$ size fla.o vla.o
fla.o: 530 + 0 + 0 = 530
vla.o: 670 + 0 + 0 = 670

オーバーヘッドのどれだけが固定され、どれだけが可変であるかを確認するための広範なテストは行っていませんが、VLAの使用にはオーバーヘッドがあります。

28

可変長配列を使用することでオーバーヘッドが発生するのではないかと思います。

いいえ、

配列のサイズは、実行時にコマンドライン引数を介して渡すことができますか?

はい。

配列を自動で動的に割り当てるのに比べて、なぜ導入されたのですか?

自動割り当てでは、コンパイル時に既知の固定サイズのみが許可されます。

malloc)を動的に割り当てると、配列はヒープに格納されます。ヒープには大きなメモリスペースがありますが、アクセスに時間がかかります。

VLAは、配列をスタックに配置することで機能します。これにより、割り当てとアクセスが非常に高速になります。butスタックは通常(数KB)小さく、VLAがスタックをオーバーフローすると、無限再帰と区別できなくなります。

4
kennytm

VLAのオーバーヘッドはごくわずかであるはずです(せいぜいスタックポインターに追加されるはずです)。動的割り当てには手動のメモリ管理が必要であり、VLAのスタックベースの割り当てよりも低速です。また、配列の「自動」宣言には、配列サイズのコンパイル時の式が必要です。ただし、スタックオーバーフローが発生すると、未定義の動作が発生するため、VLAを比較的小さくしてください。

コマンドライン引数を使用して配列のサイズを渡すこともできますが、それを処理するコードを自分で作成する必要があります。

1
rlbond