Rのデータ構造とメモリ管理
    一人 R 勉強会 #1 (2012/11/25)
                    @a_bicky


             2012/12/15 改訂版
自己紹介

• Takeshi Arabiki
   ‣   Web 業界の底辺エンジニア

   ‣ Twitter & はてな: @a_bicky & id:a_bicky

• 興味など
  機械学習、自然言語処理、R

• ブログ
  あらびき日記 http://d.hatena.ne.jp/a_bicky/
R関係の主な発表

      Tokyo.R #16                               Tsukuba.R #9                             Rユーザ会 2011




http://www.slideshare.net/abicky/r-9034336 http://www.slideshare.net/abicky/r-10128090 http://www.slideshare.net/abicky/rtwitter
注意事項


 C 言語書けません&読めません
そんな エンジニアのメモ書きです
 間違いは @a_bicky 宛に連絡していただけると喜びます
Rのデータ構造
2つのデータ構造
    SEXPREC

たぶん Symbolic EXPression RECord (S-EXPression RECord) の略
*SEXP = SEXPREC
VECTOR_SEXPREC 以外のノード(オブジェクト)


VECTOR_SEXPREC

SEXPREC の Vector 版(メモリを少し節約)
*VECSEXP = VECTOR_SEXPREC
データ部分は直後のアドレスに格納
raw, logical, integer, numeric, complex, character, list, expression 等
SEXPREC
// cf. src/include/Rinternals.h
typedef struct SEXPREC {
  struct sxpinfo_struct sxpinfo;     //   詳細は後述
  struct SEXPREC *attrib;            //   属性情報
  struct SEXPREC *gengc_next_node;   //   GC で使用
  struct SEXPREC *gengc_prev_node;   //   GC で使用
  // データ部分
  union {
    struct primsxp_struct primsxp;
    struct symsxp_struct symsxp;
    struct listsxp_struct listsxp;
    struct envsxp_struct envsxp;
    struct closxp_struct closxp;
    struct promsxp_struct promsxp;
  } u;
} SEXPREC, *SEXP;
VECTOR_SEXPREC
// cf. src/include/Rinternals.h
typedef struct VECTOR_SEXPREC {
  struct sxpinfo_struct sxpinfo;   // 詳細は後述
  struct SEXPREC *attrib;          // 属性情報
  struct SEXPREC *gengc_next_node; // GC で使用
  struct SEXPREC *gengc_prev_node; // GC で使用
  struct vecsxp_struct vecsxp; // length, truelength
} VECTOR_SEXPREC, *VECSEXP;

// データ部分のアライメントをするための変数?
typedef union {
  VECTOR_SEXPREC s;
  double align; } SEXPREC_ALIGN;

// データ部分の先頭アドレスの定義
#define DATAPTR(x)	 (((SEXPREC_ALIGN *) (x)) + 1)
VECTOR_SEXPREC
          VECTOR_SEXPREC のイメージ

                                     VECTOR_SEXPREC

ノード
sxpinfo               attrib       gengc_next_node
gengc_prev_node    vecsxp_struct

          データ部分 sizeof(VECREC) * length
sxpinfo_struct
// cf. src/include/Rinternals.h
struct sxpinfo_struct {
   SEXPTYPE type      : 5; // ノードのタイプ
   unsigned int obj   : 1;
   unsigned int named : 2; // コピーの際の挙動を制御
   unsigned int gp    : 16;
   unsigned int mark : 1; // Mark-and-Sweap の mark
   unsigned int debug : 1;
   unsigned int trace : 1; // tracemem 等で使用
   unsigned int spare : 1; // もう使われていないらしい
   unsigned int gcgen : 1; // GC の世代情報
   unsigned int gccls : 3; // サイズに応じたクラス番号
};
Rのメモリ管理
基礎知識
• メモリ管理の上でノードは3種類に分かれる
 ‣   non-vector (gccls = 0)

 ‣   small vector(gccls = 1, 2, 3, 4, 5, 6)

 ‣   large vector (gccls = 7)

• GC は世代別 GC
 ‣   old 世代は gcgen = 0, 1 の2つ存在

 ‣   old 世代からより若い世代への参照は参照が発生する度
     にリストに追加する形で管理(デフォルトオプション)
R の世代別 GC
           各世代は双方向リストとなっている
           → 挿入・削除を定数時間で行える
   R_GenHeap[gccls].New                  R_GenHeap[gccls].Old[gen]
= &R_GenHeap[gccls].NewPeg            = &R_GenHeap[gccls].OldPeg[gen]


                    gengc_prev_node

                      gengc_next_node



  node3           node1


                                          node2        node1
          node2
                                               gen th
 New generation                           Old generation
R の世代別 GC の手順
1. old 世代のうち何番目の世代までを GC 対象とするか決定

2. GC 対象の old 世代から参照されているノードを参照元世代に移す

3. GC 対象の old 世代の世代 (gcgen) をインクリメントして unmark
   した上で new 世代に移す

4. GC 対象外の old 世代から参照されているノードを mark し old 世
   代に移す

5. root からたどって参照のあるノードを mark し old 世代に移す

6. large vector の new 世代に存在するノードを解放

7. 場合によっては non-vector, small vector のうち unmarked な
   ノードを解放

cf. RunGenCollect (src/main/memory.c)
gc 関数の結果の意味
> gc()  # 全世代を対象とした GC (full GC)
         used (Mb) gc trigger (Mb) max used (Mb)
Ncells 180515 9.7      407500 21.8   350000 18.7
Vcells 287939 2.2      905753 7.0    877008 6.7


                  used                      Mb
         使用中のノードの数。                sizeof(SEXPREC) x
         non-vector, small         ノードの数 / 1024 /
Ncells
         vector, large vecotor 全   1024
         てを含む。
         全 vector ノードのデータ          ← を個数ではなく Mb で
         部分(ヘッダ部分を除                表した値。
Vcells
         く)が VECREC 何個分の           つまり sizeof(VECREC)
         サイズに相当するか                 x 個数 / 1024 / 1024
メモリ確保の仕組み
• small vector
 1. データ部分のサイズが VECREC 何個分相当かを算出

 2. 1 の結果が1以下であれば gccls = 1、2以下は2、4以下は3、6
    以下は4、8以下は5、16以下は6という具合に gccls をセット

 3. 対応する gccls の使用されていないノードを割り当てる

 4. 使用されていないノードが存在しない場合は一定量のノードを新
    しく生成 (GetNewPage)


• non-vector
 ‣   ↑の手順のうち gccls を 0 とする
メモリ確保の仕組み
• large vector
 1. データ部分のサイズが VECREC 何個分相当かを算出

 2. 1 の結果が16より大きければ gccls を 7 にセット

 3. 必要なサイズをその都度 malloc で確保




いずれの場合もメモリが足りない場合は GC を実行したり
ヒープサイズを調整する
tracemem の仕組み
// cf. src/main/duplicate.c
SEXP duplicate(SEXP s){
  SEXP t;

    duplicate_counter++;
    t = duplicate1(s);
    // (s)->sxpinfo.trace フラグが立っていればレポートを出力
    if (RTRACE(s) && !(TYPEOF(s) == CLOSXP     ||
                       TYPEOF(s) == BUILTINSXP ||
                       TYPEOF(s) == SPECIALSXP ||
                       TYPEOF(s) == PROMSXP    ||
                       TYPEOF(s) == ENVSXP)){
      memtrace_report(s,t);
      // コピーしたノードにもフラグを立てる
      SET_RTRACE(t,1);
    }
    return t;
}
tracemem の挙動
duplicate が呼ばれないコピーはトレースできない

> tracemem(a <- 1:5)
[1] "<0x103011a48>"
> b <- a
> b[1] <- 1 # そのまま代入した場合はトレースされる
tracemem[0x103011a48 -> 0x101b41760]:
tracemem[0x101b41760 -> 0x103063188]:
> c <- a[1:5]
> c[1] <- 1 # インデックスを指定した場合はトレースされない
> tracemem(a <- list(a = 1:5))
[1] "<0x1064c0958>"
> b <- a
> a$a <- 1   # そのまま代入した場合はトレースされる
tracemem[0x1064c0958 -> 0x1067e9598]:
> c <- a$a
> c[1] <- 1 # 一部の要素の代入はトレースされない
終
参考文献

•   R Internals - 1.1 SEXPs
    http://cran.r-project.org/doc/manuals/R-ints.html#SEXPs

•   RObjectModel - r-optimization-engine - Optimizing the
    performance of R - Google Project Hosting
    http://code.google.com/p/r-optimization-engine/wiki/
    RObjectModel

•   R 2.15.2 のソースコード
    主に src/include/Rinternals.h, src/main/include/Defn.h,
    src/main/memory.c
変更履歴
•   2012/12/15
    世代別 GC の旧世代の世代数が2なのに3になっていたのを訂正

Rのデータ構造とメモリ管理

  • 1.
    Rのデータ構造とメモリ管理 一人 R 勉強会 #1 (2012/11/25) @a_bicky 2012/12/15 改訂版
  • 2.
    自己紹介 • Takeshi Arabiki ‣ Web 業界の底辺エンジニア ‣ Twitter & はてな: @a_bicky & id:a_bicky • 興味など 機械学習、自然言語処理、R • ブログ あらびき日記 http://d.hatena.ne.jp/a_bicky/
  • 3.
    R関係の主な発表 Tokyo.R #16 Tsukuba.R #9 Rユーザ会 2011 http://www.slideshare.net/abicky/r-9034336 http://www.slideshare.net/abicky/r-10128090 http://www.slideshare.net/abicky/rtwitter
  • 4.
    注意事項 C 言語書けません&読めません そんなエンジニアのメモ書きです 間違いは @a_bicky 宛に連絡していただけると喜びます
  • 5.
  • 6.
    2つのデータ構造 SEXPREC たぶん Symbolic EXPression RECord (S-EXPression RECord) の略 *SEXP = SEXPREC VECTOR_SEXPREC 以外のノード(オブジェクト) VECTOR_SEXPREC SEXPREC の Vector 版(メモリを少し節約) *VECSEXP = VECTOR_SEXPREC データ部分は直後のアドレスに格納 raw, logical, integer, numeric, complex, character, list, expression 等
  • 7.
    SEXPREC // cf. src/include/Rinternals.h typedefstruct SEXPREC { struct sxpinfo_struct sxpinfo; // 詳細は後述 struct SEXPREC *attrib; // 属性情報 struct SEXPREC *gengc_next_node; // GC で使用 struct SEXPREC *gengc_prev_node; // GC で使用 // データ部分 union { struct primsxp_struct primsxp; struct symsxp_struct symsxp; struct listsxp_struct listsxp; struct envsxp_struct envsxp; struct closxp_struct closxp; struct promsxp_struct promsxp; } u; } SEXPREC, *SEXP;
  • 8.
    VECTOR_SEXPREC // cf. src/include/Rinternals.h typedefstruct VECTOR_SEXPREC { struct sxpinfo_struct sxpinfo; // 詳細は後述 struct SEXPREC *attrib; // 属性情報 struct SEXPREC *gengc_next_node; // GC で使用 struct SEXPREC *gengc_prev_node; // GC で使用 struct vecsxp_struct vecsxp; // length, truelength } VECTOR_SEXPREC, *VECSEXP; // データ部分のアライメントをするための変数? typedef union { VECTOR_SEXPREC s; double align; } SEXPREC_ALIGN; // データ部分の先頭アドレスの定義 #define DATAPTR(x) (((SEXPREC_ALIGN *) (x)) + 1)
  • 9.
    VECTOR_SEXPREC VECTOR_SEXPREC のイメージ VECTOR_SEXPREC ノード sxpinfo attrib gengc_next_node gengc_prev_node vecsxp_struct データ部分 sizeof(VECREC) * length
  • 10.
    sxpinfo_struct // cf. src/include/Rinternals.h structsxpinfo_struct { SEXPTYPE type : 5; // ノードのタイプ unsigned int obj : 1; unsigned int named : 2; // コピーの際の挙動を制御 unsigned int gp : 16; unsigned int mark : 1; // Mark-and-Sweap の mark unsigned int debug : 1; unsigned int trace : 1; // tracemem 等で使用 unsigned int spare : 1; // もう使われていないらしい unsigned int gcgen : 1; // GC の世代情報 unsigned int gccls : 3; // サイズに応じたクラス番号 };
  • 11.
  • 12.
    基礎知識 • メモリ管理の上でノードは3種類に分かれる ‣ non-vector (gccls = 0) ‣ small vector(gccls = 1, 2, 3, 4, 5, 6) ‣ large vector (gccls = 7) • GC は世代別 GC ‣ old 世代は gcgen = 0, 1 の2つ存在 ‣ old 世代からより若い世代への参照は参照が発生する度 にリストに追加する形で管理(デフォルトオプション)
  • 13.
    R の世代別 GC 各世代は双方向リストとなっている → 挿入・削除を定数時間で行える R_GenHeap[gccls].New R_GenHeap[gccls].Old[gen] = &R_GenHeap[gccls].NewPeg = &R_GenHeap[gccls].OldPeg[gen] gengc_prev_node gengc_next_node node3 node1 node2 node1 node2 gen th New generation Old generation
  • 14.
    R の世代別 GCの手順 1. old 世代のうち何番目の世代までを GC 対象とするか決定 2. GC 対象の old 世代から参照されているノードを参照元世代に移す 3. GC 対象の old 世代の世代 (gcgen) をインクリメントして unmark した上で new 世代に移す 4. GC 対象外の old 世代から参照されているノードを mark し old 世 代に移す 5. root からたどって参照のあるノードを mark し old 世代に移す 6. large vector の new 世代に存在するノードを解放 7. 場合によっては non-vector, small vector のうち unmarked な ノードを解放 cf. RunGenCollect (src/main/memory.c)
  • 15.
    gc 関数の結果の意味 > gc() # 全世代を対象とした GC (full GC) used (Mb) gc trigger (Mb) max used (Mb) Ncells 180515 9.7 407500 21.8 350000 18.7 Vcells 287939 2.2 905753 7.0 877008 6.7 used Mb 使用中のノードの数。 sizeof(SEXPREC) x non-vector, small ノードの数 / 1024 / Ncells vector, large vecotor 全 1024 てを含む。 全 vector ノードのデータ ← を個数ではなく Mb で 部分(ヘッダ部分を除 表した値。 Vcells く)が VECREC 何個分の つまり sizeof(VECREC) サイズに相当するか x 個数 / 1024 / 1024
  • 16.
    メモリ確保の仕組み • small vector 1. データ部分のサイズが VECREC 何個分相当かを算出 2. 1 の結果が1以下であれば gccls = 1、2以下は2、4以下は3、6 以下は4、8以下は5、16以下は6という具合に gccls をセット 3. 対応する gccls の使用されていないノードを割り当てる 4. 使用されていないノードが存在しない場合は一定量のノードを新 しく生成 (GetNewPage) • non-vector ‣ ↑の手順のうち gccls を 0 とする
  • 17.
    メモリ確保の仕組み • large vector 1. データ部分のサイズが VECREC 何個分相当かを算出 2. 1 の結果が16より大きければ gccls を 7 にセット 3. 必要なサイズをその都度 malloc で確保 いずれの場合もメモリが足りない場合は GC を実行したり ヒープサイズを調整する
  • 18.
    tracemem の仕組み // cf.src/main/duplicate.c SEXP duplicate(SEXP s){ SEXP t; duplicate_counter++; t = duplicate1(s); // (s)->sxpinfo.trace フラグが立っていればレポートを出力 if (RTRACE(s) && !(TYPEOF(s) == CLOSXP || TYPEOF(s) == BUILTINSXP || TYPEOF(s) == SPECIALSXP || TYPEOF(s) == PROMSXP || TYPEOF(s) == ENVSXP)){ memtrace_report(s,t); // コピーしたノードにもフラグを立てる SET_RTRACE(t,1); } return t; }
  • 19.
    tracemem の挙動 duplicate が呼ばれないコピーはトレースできない >tracemem(a <- 1:5) [1] "<0x103011a48>" > b <- a > b[1] <- 1 # そのまま代入した場合はトレースされる tracemem[0x103011a48 -> 0x101b41760]: tracemem[0x101b41760 -> 0x103063188]: > c <- a[1:5] > c[1] <- 1 # インデックスを指定した場合はトレースされない > tracemem(a <- list(a = 1:5)) [1] "<0x1064c0958>" > b <- a > a$a <- 1 # そのまま代入した場合はトレースされる tracemem[0x1064c0958 -> 0x1067e9598]: > c <- a$a > c[1] <- 1 # 一部の要素の代入はトレースされない
  • 20.
  • 21.
    参考文献 • R Internals - 1.1 SEXPs http://cran.r-project.org/doc/manuals/R-ints.html#SEXPs • RObjectModel - r-optimization-engine - Optimizing the performance of R - Google Project Hosting http://code.google.com/p/r-optimization-engine/wiki/ RObjectModel • R 2.15.2 のソースコード 主に src/include/Rinternals.h, src/main/include/Defn.h, src/main/memory.c
  • 22.
    変更履歴 • 2012/12/15 世代別 GC の旧世代の世代数が2なのに3になっていたのを訂正