0

元 C プログラマーで現在の Erlang ハッカーとして、1 つの疑問が浮かびました。

Erlang データ構造のメモリ スコープを見積もるにはどうすればよいですか?

C に 1k の整数の配列があり、これのメモリ需要を見積もるのは簡単です。配列のサイズに整数のサイズを掛けると、1k の 32 ビット整数は 4kb またはメモリを占有し、一定量のポインタとインデックス。

しかし、erlang では、メモリ使用量の推定はやや複雑です。erlangs 配列構造のエントリはどのくらいのメモリを占有しますか?動的にサイズ変更される整数のサイズを推定するにはどうすればよいですか?

配列内の整数のスキャンは erlang ではかなり遅いことに気付きました。約 1M の整数の配列をスキャンするには、erlang ではほぼ 1 秒かかりますが、単純な C コードでは約 2 ミリ秒で実行されます。これはおそらく次の理由によるものです。データ構造が占有するメモリの量。

私がこれを尋ねているのは、私がスピードマニアだからではなく、少なくとも私の経験では、メモリの見積もりがソフトウェアのスケーラビリティを判断する良い方法だったからです。


私のテストコード:

最初に C コード:

#include <cstdio>
#include <cstdlib>
#include <time.h>

#include <queue>  
#include <iostream>

class DynamicArray{
protected:
  int* array;
  unsigned int size;
  unsigned int max_size;

public:
  DynamicArray() {
    array = new int[1];
    size = 0;
    max_size = 1;
  }

  ~DynamicArray() {
    delete[] array;
  }

  void insert(int value) {
    if (size == max_size) {
      int* old_array = array;
      array = new int[size * 2];
      memcpy ( array, old_array, sizeof(int)*size );

      for(int i = 0; i != size; i++)
        array[i] = old_array[i];
      max_size *= 2;
      delete[] old_array;
    }
    array[size] = value;
    size ++;
  }

  inline int read(unsigned idx) const {
    return array[idx];
  }

  void print_array() {
    for(int i = 0; i != size; i++)
      printf("%d ", array[i]);
    printf("\n ");

  }

  int size_of() const {
    return max_size * sizeof(int);
  }    

};


void test_array(int test) {
  printf(" %d ", test);
  clock_t t1,t2;
  t1=clock();
  DynamicArray arr;
  for(int i = 0; i != test; i++) {
    //arr.print_array();
    arr.insert(i);
  }
  int val = 0;
  for(int i = 0; i != test; i++)
    val += arr.read(i);
  printf(" size %g MB ", (arr.size_of()/(1024*1024.0)));  
  t2=clock();
  float diff ((float)t2-(float)t1);
  std::cout<<diff/1000<< " ms" ;
  printf(" %d \n", val == ((1 + test)*test)/2);
}

int main(int argc, char** argv) {
  int size = atoi(argv[1]);
  printf(" -- STARTING --\n");
  test_array(size);
  return 0;
}

そしてアーランコード:

-module(test).

-export([go/1]).

construct_list(Arr, Idx, Idx) ->
    Arr;
construct_list(Arr, Idx, Max) ->
    construct_list(array:set(Idx, Idx, Arr), Idx + 1, Max).

sum_list(_Arr, Idx, Idx, Sum) ->
    Sum;
sum_list(Arr, Idx, Max, Sum) ->
    sum_list(Arr, Idx + 1, Max, array:get(Idx, Arr) + Sum ).

go(Size) ->
    A0 = array:new(Size),
    A1 = construct_list(A0, 0, Size),
    sum_list(A1, 0, Size, 0).

C コードのタイミング:

bash-3.2$ g++ -O3 test.cc -o test
bash-3.2$ ./test 1000000
 -- STARTING --
 1000000  size 4 MB 5.511 ms 0 

そしてアーランコード:

1> f(Time), {Time, _} =timer:tc(test, go, [1000000]), Time/1000.0.
2189.418
4

3 に答える 3

3

まず、Erlang 変数は常に 1 つの単語 (マシンによって 32 ビットまたは 64 ビット) です。ワードの 2 ビット以上がタイプ タグとして使用されます。剰余は、「fixnum」整数、アトム、空のリスト ([])、または Pid などの「即時」値を保持できます。または、ヒープに格納されたデータへのポインター (タプル、リスト、"bignum" 整数、浮動小数など) を保持できます。タプルには、そのタイプと長さを指定するヘッダー ワードがあり、その後に要素ごとに 1 つのワードが続きます。のリスト セルは、head 要素と tail 要素の 2 つの単語のみを使用します (そのポインターは既に型をエンコードしています)。

例: A={foo,1,[]} の場合、A は「私は 3 タプルです」というヒープ上の単語を指す単語であり、その後にアトム foo、fixnum 1 を含む 3 つの単語が続きます。それぞれ空のリスト。A=[1,2] の場合、A は、最初のセルの先頭の単語 (fixnum 1 を含む) を指す「私はリスト セル ポインタです」という単語です。セルの次のテール ワードは、さらに別のリスト セル ポインターであり、2 を含むヘッド ワードを指し、その後に空のリストを含むテール ワードが続きます。float は、ヘッダー ワードと 8 バイトの倍精度浮動小数点データで表されます。bignum またはバイナリは、ヘッダー ワードにデータを保持するために必要な数のワードを加えたものです。等々。詳細については、たとえばhttp://stenmans.org/happi_blog/?p=176を参照してください。

サイズを見積もるには、タプルとリストに関してデータがどのように構造化されているかを知る必要があり、整数のサイズを知る必要があります (大きすぎる場合、fixnum の代わりに bignum が使用されます。制限は 28 ビットです)。 32 ビット マシンでは符号、64 ビット マシンでは 60 ビットを含む)。

編集: https://github.com/happi/theBeamBookは、BEAM Erlang 仮想マシンの内部に関する新しい優れたリソースです。

于 2013-10-20T21:27:51.740 に答える
2

これは、あなたの望むことですか?

1> erts_debug:size([1,2]).
4

これを使用すると、少なくとも用語の大きさを把握できます。返されるサイズは単語です。

于 2013-10-18T23:25:23.563 に答える
0

Erlang は「配列」として整数を持っているため、c と同じ方法で実際に推定することはできません。整数の長さを予測し、それらを格納するために必要な平均バイト数を計算することしかできません。

チェック: http://www.erlang.org/doc/efficiency_guide/advanced.htmlerlang:memory()関数を使用して実際の金額を決定できます

于 2013-10-18T20:55:27.507 に答える