22

R の行列のメモリ使用量に興味を持ったとき、奇妙なことに気付きました。ループで、行列の列数を増やし、各ステップでオブジェクトのサイズを次のように計算しました。

x <- 10
size <- matrix(1:x, x, 2)

for (i in 1:x){
  m  <- matrix(1, 2, i)
  size[i,2] <- object.size(m)
}

どちらが与える

plot(size[,1], size[,2], xlab="n columns", ylab="memory")

ここに画像の説明を入力

2 行と 5、6、7、または 8 列の行列は、まったく同じメモリを使用しているようです。それをどのように説明できますか?

4

3 に答える 3

36

ここで何が起こっているのかを理解するには、R のオブジェクトに関連するメモリ オーバーヘッドについて少し知っておく必要があります。データのないオブジェクトであっても、すべてのオブジェクトには 40 バイトのデータが関連付けられています。

x0 <- numeric()
object.size(x0)
# 40 bytes

このメモリは、オブジェクトのタイプ ( によって返されるtypeof()) と、メモリ管理に必要なその他のメタデータを格納するために使用されます。

このオーバーヘッドを無視すると、ベクトルのメモリ使用量はベクトルの長さに比例すると考えるかもしれません。いくつかのプロットでそれを確認しましょう。

sizes <- sapply(0:50, function(n) object.size(seq_len(n)))
plot(c(0, 50), c(0, max(sizes)), xlab = "Length", ylab = "Bytes", 
  type = "n")
abline(h = 40, col = "grey80")
abline(h = 40 + 128, col = "grey80")
abline(a = 40, b = 4, col = "grey90", lwd = 4)
lines(sizes, type = "s")

ベクトルのメモリ使用量

メモリ使用量はベクトルの長さにほぼ比例しているように見えますが、168 バイトで大きな不連続性があり、数ステップごとに小さな不連続性があります。大きな不連続性は、R がベクトル用の 2 つのストレージ プールを持っているためです。R によって管理される小さなベクトルと、OS によって管理される大きなベクトルです (大量の少量のメモリを割り当てるとコストがかかるため、これはパフォーマンスの最適化です)。小さなベクトルの長さは 8、16、32、48、64、または 128 バイトのみです。これは、40 バイトのオーバーヘッドを取り除くと、まさに次のようになります。

sizes - 40
#  [1]   0   8   8  16  16  32  32  32  32  48  48  48  48  64  64  64  64 128 128 128 128
# [22] 128 128 128 128 128 128 128 128 128 128 128 128 136 136 144 144 152 152 160 160 168
# [43] 168 176 176 184 184 192 192 200 200

64 から 128 へのステップにより大きなステップが発生し、大きなベクトル プールに到達すると、ベクトルは 8 バイトのチャンクで割り当てられます (メモリは特定のサイズの単位で取得され、R は半分のメモリを要求できません)。単位):

# diff(sizes)
#  [1]  8  0  8  0 16  0  0  0 16  0  0  0 16  0  0  0 64  0  0  0  0  0  0  0  0  0  0  0
# [29]  0  0  0  0  8  0  8  0  8  0  8  0  8  0  8  0  8  0  8  0  8  0

では、この動作は行列で見られるものとどのように対応しているのでしょうか? まず、行列に関連するオーバーヘッドを確認する必要があります。

xv <- numeric()
xm <- matrix(xv)

object.size(xm)
# 200 bytes

object.size(xm) - object.size(xv)
# 160 bytes

そのため、行列には​​、ベクトルに比べて 160 バイトの余分なストレージが必要です。なぜ160バイト?これは、行列にdim2 つの整数を含む属性があり、属性がpairlist(古いバージョンのlist()) に格納されているためです。

object.size(pairlist(dims = c(1L, 1L)))
# 160 bytes

ベクトルの代わりに行列を使用して前のプロットを再描画し、y 軸のすべての定数を 160 増やすと、不連続性が小さなベクトル プールから大きなベクトル プールへのジャンプに正確に対応することがわかります。

msizes <- sapply(0:50, function(n) object.size(as.matrix(seq_len(n))))
plot(c(0, 50), c(160, max(msizes)), xlab = "Length", ylab = "Bytes", 
  type = "n")
abline(h = 40 + 160, col = "grey80")
abline(h = 40 + 160 + 128, col = "grey80")
abline(a = 40 + 160, b = 4, col = "grey90", lwd = 4)
lines(msizes, type = "s")

行列のメモリ使用量

于 2013-08-27T13:44:34.317 に答える
7

これは、小さな端の非常に特定の範囲の列でのみ発生するようです。1 ~ 100 列の行列を見ると、次のようになります。

ここに画像の説明を入力

列の数を 10000 に増やしても、他の停滞は見られません。

ここに画像の説明を入力

興味をそそられたので、コードを関数に入れて、もう少し調べました。

sizes <- function(nrow, ncol) {
  size=matrix(1:ncol,ncol,2)
  for (i in c(1:ncol)){
    m = matrix(1,nrow, i)
    size[i,2]=object.size(m)
  } 
  plot(size[,1], size[,2])
  size
}

興味深いことに、行の数を増やしても、プラトーが縮小して後方に移動すると、このプラトーと直線が少ない数で表示されます。その後、最終的にnrow=8.

3 ~ 8 行、10 列の行列のサイズ:

これは、マトリックス内のセル数の非常に特定の範囲で発生することを示しています。9-16。

メモリ割り当て

@Hadley がコメントで指摘したように、 vectorのメモリ割り当てに関する同様のスレッドがあります。これは次の式を思いつきます:サイズ40 + 8 * floor(n / 2)numericベクトルの場合n.

行列の場合、オーバーヘッドはわずかに異なり、ステッピング関係は維持されません (私のプロットに見られるように)。代わりに、 は行列内のセルの数 ( ) であるフォーミュラ208 + 8 * nバイトを思いつきました。ただし、は 9 ~ 16 の間です。nnrow * ncoln

行列のサイズ -"double"行列の場合は 208 バイト、1 行、1 ~ 20 列:

> sapply(1:20, function(x) { object.size(matrix(1, 1, x)) })-208
 [1]   0   8  24  24  40  40  56  56 120 120 120 120 120 120 120 120 128 136 144
[20] 152

でも。行列のタイプを Integer または Logical に変更すると、上記のスレッドで説明されているメモリ割り当てで段階的な動作が見られます。

行列のサイズ -"integer"行列 1 行、1 ~ 20 列の場合は 208 バイト:

> sapply(1:20, function(x) { object.size(matrix(1L, 1, x)) })-208
 [1]   0   0   8   8  24  24  24  24  40  40  40  40  56  56  56  56 120 120 120
[20] 120

"logical"行列についても同様:

> sapply(1:20, function(x) { object.size(matrix(1L, 1, x)) })-208
 [1]   0   0   8   8  24  24  24  24  40  40  40  40  56  56  56  56 120 120 120
[20] 120

type の行列で同じ動作が見られないのは驚くべきことです。これは、属性が付加されたdouble単なる"numeric"ベクトルであるためです ( R lang 仕様)。dim

メモリ割り当てで見られる大きな進歩はR、小さなベクトル用と大きなベクトル用の 2 つのメモリ プールを持つことから生まれます。Hadley Wickham は、彼の応答でこれを詳細に説明しています。

于 2013-08-27T12:51:53.563 に答える
5

サイズが 1 から 20 の数値ベクトルを見ると、この図が得られました。

x=20
size=matrix(1:x,x,2)
for (i in c(1:x)){
   m = rep(1, i)
   size[i,2]=object.size(m)
}

plot(size[,1],size[,2])

ここに画像の説明を入力

于 2013-08-27T13:03:38.793 に答える