Ответ 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")
Похоже, что использование памяти примерно пропорционально длине вектора, но есть большой разрыв на 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")