8

この回答について: R のコピー オン モディファイ セマンティクスとは正確には何ですか? 正規のソースはどこにありますか?

ベクトルが で最初に変更されると'[<-'、R は単一のエントリのみが変更される場合でもベクトル全体をコピーすることがわかります。ただし、2 回目では、ベクトルは「その場で」変更されます。これは、大きなベクトルを作成および変更する時間を測定すると、オブジェクトのアドレスを検査しなくても顕著です。

> system.time(a <- rep(1L, 10^8))
   user  system elapsed 
   0.15    0.17    0.31 
> system.time(a[222L] <- 111L)
   user  system elapsed 
   0.26    0.08    0.34 
> system.time(a[333L] <- 111L)
   user  system elapsed 
      0       0       0

type/storage.mode の変更はないことに注意してください。

問題は、最初の括弧の割り当ても最適化できないのはなぜですか? この種の動作 (最初の変更で完全なコピー) が実際に必要になるのはどのような状況ですか?

編集:(ネタバレ!)以下の受け入れられた回答で説明されているように、これはsystem.time関数呼び出しで最初の割り当てを囲むことの成果物に他なりません。これにより、R はバインドさaれたメモリ空間を複数のシンボルを参照する可能性があるとマークするため、変更時に複製が必要になります。囲んでいる呼び出しを削除すると、ベクトルは最初のブラケット割り当てからその場で変更されます。

詳細な解決策を提供してくれた Martin に感謝します。

4

2 に答える 2

9

の「NAM()」部分を比較します

> a <- rep(1L, 10)
> .Internal(inspect(a))
@457b840 13 INTSXP g0c4 [NAM(1)] (len=10, tl=0) 1,1,1,1,1,...

> system.time(a <- rep(1L, 10))
[...]
> .Internal(inspect(a))
@4626f88 13 INTSXP g0c4 [NAM(2)] (len=10, tl=0) 1,1,1,1,1,...

最初の例の「1」は、R が への参照が 1 つあると考えていることを意味するためa、その場で更新できます。"2" は、R が への参照が少なくとも 2 つあると考えていることを意味するaため、変更した場合は複製が必要です。rep()大まかに言えば、これをinsideの戻り値とsystem.timeoutsideの値の表現として合理化しますsystem.timef = function() { x <- rep(1L, 10); x }; a = f()よりもむしろの道徳的同等物g = function() rep(1L, 10); a = g()

実際のコードa <- rep(1L, 10^8); a[123L] <- 231Lにはコピーは含まれません。NAMED カウントを人為的にインクリメントすることなく、割り当てのタイミングを計ることができます。

> a <- rep(1L, 10^8)
> .Internal(inspect(a))
@7f972b571010 13 INTSXP g0c7 [NAM(1)] (len=100000000, tl=0) 1,1,1,1,1,...
> system.time(a[123L] <- a[321L])
   user  system elapsed 
      0       0       0 
于 2013-05-07T17:10:55.943 に答える
3

Joshua のコメントに従って編集します。以下に示す動作は R-studio に限定されます!!

OPの質問に答えるために、コピーの根本的な理由(@MartinMorganが説明しているように)は、のNAM(2)SEXPオブジェクトによるものですa。最初のコマンドに が含まれていなかった場合system.time(.)、型がa <- rep(1, 10^8)返さNAM(1)れ、両方の代入でコピーが作成されません。

Rスタジオでの観察:

ただし、別の興味深い観察/相違点を指摘すると、R-studio で実行する場合、(R64/R32 セッションの動作とは) 気付いていない追加の動作の相違点があります。

(R スタジオでの) 違いは、コードの実行方法に起因するようです。つまり、一度にすべてをコピーして貼り付ける場合 (以下に示すように、出力を含む):

system.time(a <- rep(1L, 10^8))
#    user  system elapsed 
#   0.256   0.263   0.526 
.Internal(inspect(a))
# @10745d000 13 INTSXP g0c7 [NAM(2)] (len=100000000, tl=0) 1,1,1,1,1,...
system.time(a[222L] <- 111L)
#    user  system elapsed 
#   0.299   0.199   0.498 
.Internal(inspect(a))
# @11f1d6000 13 INTSXP g0c7 [NAM(1)] (len=100000000, tl=0) 1,1,1,1,1,...
system.time(a[333L] <- 111L)
#    user  system elapsed 
#       0       0       0 
.Internal(inspect(a))
# @11f1d6000 13 INTSXP g1c7 [MARK,NAM(1)] (len=100000000, tl=0) 1,1,1,1,1,...

2 番目の割り当てにはメモリ コピーが含まれておらず、所要時間は 0 秒であることがわかります。ここで、同じコマンド セットをコピー/貼り付け/実行しますが、1 つずつ実行します (次の行を入力する前に、すべての行の後で Enter キーを押します)。結果は次のとおりです。

system.time(a <- rep(1L, 10^8))
#    user  system elapsed 
#   0.256   0.265   0.588 
> 
.Internal(inspect(a))
# @10745d000 13 INTSXP g0c7 [NAM(2)] (len=100000000, tl=0) 1,1,1,1,1,...

system.time(a[222L] <- 111L)
#    user  system elapsed 
#   0.302   0.204   0.559 

.Internal(inspect(a))
# @11f1d6000 13 INTSXP g0c7 [NAM(2)] (len=100000000, tl=0) 1,1,1,1,1,...

system.time(a[333L] <- 111L)
#    user  system elapsed 
#   0.296   0.208   0.504 
> 
.Internal(inspect(a))
# @10745d000 13 INTSXP g0c7 [NAM(2)] (len=100000000, tl=0) 1,1,1,1,1,...

同じ構文のコピーが作成され、実行時間は 0.5 秒です。

違いを説明するために(@MartinMorganが彼の回答で説明したように):

最初のケースでは、NAM(2) SEXP オブジェクトであるため、割り当て中に複製されます。ただし、これは、すべての行を一度に実行する最初のケースで 1 回だけ発生します。また、2 番目の代入には MARK (unsigned int) があり、これは「オブジェクトを使用中としてマークする」( R-internalsから) ことを示します。

2 番目のケースでは、R-studio ですべての行に対して Enter キーを押すと、これらの割り当てのそれぞれが NAM(2) SEXP オブジェクトを返します。そのため、毎回コピーが作成されています。

于 2013-05-07T17:21:15.110 に答える