2013-08-27 9 views
21

Interesowało mnie wykorzystanie pamięci macierzy w R, gdy zaobserwowałem coś dziwnego. W pętli urosłem do liczby kolumn macierzy i obliczyłem dla każdego kroku rozmiar obiektu.To samo użycie pamięci macierzy o różnym rozmiarze

x=10 
size=matrix(1:x,x,2) 

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

wykreślono mój wynik

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

enter image description here

Wydaje się, że matryca z 2 rzędach i kolumnach 5,6,7 lub 8 korzysta z tej samej pamięci. Jak możemy to wyjaśnić?

+2

Th nie jest związane z macierzami (które są wektorami w R). Spójrz na 'rozmiary <- rep (NA_integer_, 100); dla (i w 1: 100) rozmiarach [i] <- object.size (rep (1, i)); działka (rozmiary) '. – Roland

+1

Spójrz na znak 'm <- matrix ('a', 2, i)' i integer 'm <- matrix (1L, 2, i)' i 'm <- matrix (TRUE, 2, i)' - lub co bardziej interesujące, co sugerowała @Roland (dla postaci, liczb całkowitych i wektorów logicznych). – mnel

+0

@ SimonO101 Hadley wyjaśnił to na czacie. Mam nadzieję, że znajdzie czas, aby napisać właściwą odpowiedź. Np. Przeczytaj [tę sekcję w R-exts] (http://cran.r-project.org/doc/manuals/R-exts.html#Profiling-R-code-for-memory-use). – Roland

Odpowiedz

35

Aby zrozumieć, co się tu dzieje, trzeba wiedzieć trochę o napowietrznej pamięci związanej z obiektami w R. każdy obiekt, nawet obiekt bez danych, ma 40 bajtów dane z nim związane:

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

pamięć ta jest używana do przechowywania typ obiektu (jak zwrócony przez typeof()) i inne metadane potrzebne do zarządzania pamięcią.

Po zignorowaniu tego narzutu można się spodziewać, że użycie pamięci wektora jest proporcjonalne do długości wektora. Zobaczmy, że obecnie z kilku działek:

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") 

Memory usage of vectors

Wygląda na to zużycie pamięci jest w przybliżeniu proporcjonalna do długości wektora, ale istnieje duża przerwa na 168 bajtów i małych nieciągłości każdy kilka kroków. Duża nieciągłość polega na tym, że R ma dwie pule pamięci dla wektorów: małe wektory zarządzane przez R i duże wektory zarządzane przez system operacyjny (jest to optymalizacja wydajności, ponieważ przydzielanie dużej ilości niewielkiej ilości pamięci jest kosztowne). Małe wektory mogą być tylko 8, 16, 32, 48, 64 lub 128 bajtów, który raz usuwamy napowietrznej 40 bajtów, jest dokładnie to, co widzimy:

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 

Krok od 64 do 128 powoduje, że duża krok, potem kiedy już przeszedł do wielkiego basenu wektorowe, wektory są przydzielane w kawałki (8 bajtów pamięci jest w jednostkach o określonej wielkości, a R nie można prosić o pół jednostki):

# 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 

Więc w jaki sposób to zachowanie odpowiada temu, co widzisz za pomocą macierzy? Cóż, najpierw musimy spojrzeć na narzutu związanego z matrycy:

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

object.size(xm) 
# 200 bytes 

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

Więc matryca potrzebuje dodatkowe 160 bajtów pamięci w porównaniu do wektora. Dlaczego 160 bajtów?To dlatego, że matryca posiada atrybut dim zawierający dwie liczby całkowite, a cechy są przechowywane w pairlist (starszej wersji list()):

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

Jeśli ponownie wyciągnąć poprzedniego wykresu przy użyciu macierzy, zamiast wektorów i zwiększyć Wszystkie stałe na osi y 160, można zobaczyć nieciągłość odpowiada dokładnie skoku z małym basenem wektora do dużego basenu vector:

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") 

Memory usage of matrices

+5

+1 ciekawa i wspaniała odpowiedź. Dzięki hadley –

+1

Dzięki hadley. To idealna odpowiedź. – Bangyou

7

To zdarza się tylko w przypadku bardzo specyficznego zakresu kolumn na małym końcu. Patrząc na matrycach z 1-100 kolumn widzę następujące:

enter image description here

nie widzę żadnych innych płaskowyże, nawet jeśli mogę zwiększyć liczbę kolumn do powiedzenia, 10000:

enter image description here

Zaintrygowany, szukałem na nieco dalej, stawiając swój kod w funkcji:

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 
} 

ciekawe, ul chory zobaczyć ten płaskowyż i prostą w niewielkiej ilości, jeśli zwiększymy liczbę wierszy, z kurczenia plateau i przesuwając do tyłu, zanim ostatecznie dostosowując się do linii prostej do czasu, kiedy uderzy nrow=8:

Size of matrices with 3-8 rows with 10 columns:

Wskazując że dzieje się to w bardzo specyficznym zakresie liczby komórek w matrycy; 9-16.

alokacji pamięci

Jak @Hadley wskazał w swoim komentarzu, istnieje podobna thread on memory allocation of vectors. Która zawiera następujący wzór: 40 + 8 * floor(n/2) dla numeric wektorów o rozmiarze n.

W przypadku macierzy obciążenie to jest nieco inne, a zależność od kroku nie jest zachowana (jak widać na moich wykresach). Zamiast tego wymyślić o wzorze 208 + 8 * n bajtów gdzie n oznacza liczbę komórek w matrycy (nrow * ncol), z wyjątkiem, gdzie n się pomiędzy 9 i 16:

Matryca wielkość - 208 bajtów "double" matryc, 1 rzędu, 1 -20 kolumn:

> 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 

JEDNAK.W przypadku zmiany rodzaju matrycy do liczby całkowitej lub logiczny, to jest widoczne stopniowe zachowanie podziału pamięci opisane w wątku powyżej:

Matryca wielkość - 208 bajtów "integer" wierszy macierzy 1, 1-20 kolumny:

> 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 

Podobnie dla "logical" matryc:

> 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 

zaskakujące jest to, że nie widzimy to samo zachowanie z matrycą typu double, jak to tylko "numeric" wektor przypisano atrybut dim (R lang specification).

Duży krok, jaki widzimy w alokacji pamięci, pochodzi z R posiadającej dwie pule pamięci, jedną dla małych wektorów i jedną dla dużych wektorów, i która jest tam, gdzie wykonany jest skok. Hadley Wickham wyjaśnia to szczegółowo w swojej odpowiedzi.

5

Spójrz na wektor numeryczny o wielkości od 1 do 20, mam tę cyfrę.

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]) 

enter image description here

+0

Hah, to o wiele sprytniejszy sposób na wkopanie się w to! –