Подтвердить что ты не робот

Такое использование памяти матрицы с разным размером

Меня интересовало использование памяти в матрице в R, когда я заметил что-то странное. В цикле я увеличил количество столбцов матрицы и вычислил для каждого шага размер объекта.

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

Запланированный мой результат

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

enter image description here

Кажется, что матрица с двумя строками и 5,6,7 или 8 столбцами использует одну и ту же память. Как мы можем объяснить это?

4b9b3361

Ответ 1

Чтобы понять, что происходит здесь, вам нужно немного узнать о служебных данных памяти, связанных с объектами в 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")

Memory usage of vectors

Похоже, что использование памяти примерно пропорционально длине вектора, но есть большой разрыв на 168 байт и небольшие разрывы каждые несколько шагов. Большой разрыв заключается в том, что R имеет два пула хранения для векторов: небольшие векторы, управляемые R, и большие векторы, управляемые ОС (это оптимизация производительности, поскольку выделение большого количества небольших объемов памяти является дорогостоящим). Маленькие векторы могут быть только длиной 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 байт? Это потому, что у матрицы есть атрибут dim, содержащий два целых числа, а атрибуты хранятся в 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")

Memory usage of matrices

Ответ 2

Это, по-видимому, происходит только для очень определенного диапазона столбцов на маленьком конце. Если посмотреть на матрицы с 1-100 столбцами, я вижу следующее:

enter image description here

Я не вижу никаких других плато, даже если я увеличиваю количество столбцов, чтобы сказать: 10000:

enter image description here

Заинтригованный, я посмотрел еще немного, поставив ваш код в функции:

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:

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

Указание на то, что это происходит для очень конкретного диапазона для количества ячеек в матрице; 9-16.

Распределение памяти

Как отметил в своем комментарии @Hadley, существует аналогичный поток распределения памяти векторов. Который приходит с формулой: 40 + 8 * floor(n / 2) для numeric векторов размера n.

Для матриц накладные расходы немного отличаются, а отношение шага не выполняется (как видно на моих графиках). Вместо этого я придумал формулу 208 + 8 * n bytes, где n - количество ячеек в матрице (nrow * ncol), за исключением тех случаев, когда n находится между 9 и 16:

Размер матрицы - 208 байт для "double" матриц, 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, мы увидим пошаговое поведение в распределении памяти, описанное в приведенном выше потоке:

Размер матрицы - 208 байт для "integer" матриц 1 строка, 1-20 столбцов:

> 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

Удивительно, что мы не видим такого же поведения с матрицей типа double, так как это просто вектор "numeric" с приложенным атрибутом dim (Спецификация R lang).

Большой шаг, который мы видим в распределении памяти, поступает из R, имеющего два пула памяти: один для малых векторов и один для больших векторов, и именно так происходит скачок. Хэдли Уикхэм подробно объясняет это в своем ответе.

Ответ 3

Посмотрите на числовой вектор размером от 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])

enter image description here