#r #memory-management
#r #управление памятью
Вопрос:
Я знаком с tracemem()
отображением шестнадцатеричного адреса памяти назначенной переменной, например
x <- 2
tracemem(x)
#> [1] "<0x876df68>"
но что это подразумевает (под капотом), когда значение буквально является просто неназначенным значением? например
tracemem(4)
#> [1] "<0x9bd93b8>"
Тот же вопрос относится к простому вычислению выражения без присвоения
4
#> [1] 4
Кажется, что если я оцениваю это несколько раз в консоли, я получаю постоянно увеличивающиеся шестнадцатеричные адреса
tracemem(4)
#> [1] "<0x8779968>"
tracemem(4)
#> [1] "<0x87799c8>"
tracemem(4)
#> [1] "<0x8779a28>"
но если я либо явно выполняю эту операцию в цикле
for ( i in 1:3 ) { print(tracemem(4)) }
#> [1] "<0x28bda48>"
#> [1] "<0x28bda48>"
#> [1] "<0x28bda48>"
или с sapply
помощью replicate
replicate(3, tracemem(4))
#> [1] "<0xba88208>" "<0xba88208>" "<0xba88208>"
Я получаю повторения одного и того же адреса, даже если я явно задерживаю печать между итерациями
for ( i in 1:3 ) { print(tracemem(4)); Sys.sleep(1) }
#> [1] "<0xa3c4058>"
#> [1] "<0xa3c4058>"
#> [1] "<0xa3c4058>"
Мое лучшее предположение заключается в том, что вызов ссылается на уже временно присвоенное значение в parent.frame
данном eval.parent(substitute(
в replicate
, но я недостаточно разбираюсь в базовом .Primitive
коде for
, чтобы знать, делает ли он там то же самое.
У меня есть некоторая уверенность, что R
создает временные переменные, учитывая, что я могу сделать
list(x = 1)
#> $x
#> [1] 1
таким образом, R
должно быть, обрабатываются данные, даже если они никогда ничему не присваиваются. Я осведомлен о строгой формальности, изложенной в твите @hadleywickham:
но я не уверен, как это работает здесь. Просто временное имя не сохраняется? Всегда ли for
цикл использует это имя / объект? При вычислении большого количества кода, независимо от того, назначен он или нет, все еще расходуется память? (до тех пор, пока не будет вызван gc()
, когда бы это ни было??)
tl; dr — как R
«сохранять» неназначенные значения для печати?
Ответ №1:
Хорошо, я сделаю здесь все, что смогу.
Во-первых, tracemem — это примитив. Это означает, что это не замыкание, подобное подавляющему большинству функций уровня R, которые вы можете вызвать из R-кода. Более конкретно, это встроенный примитив XP:
> .Internal(inspect(tracemem))
@62f548 08 BUILTINSXP g0c0 [MARK,NAM(1)]
Это означает, что при его вызове НЕ применяется замыкание (поскольку это примитив), а вычисляется его аргумент, поскольку это ВСТРОЕННЫЙ XP (см. Этот раздел руководства по R internals).
Приложение закрытия — это когда объекты R, переданные в качестве аргументов при вызовах функций, присваиваются соответствующим переменным во фрейме вызова. Таким образом, этого не происходит для tracemem. Вместо этого его аргументы вычисляются на уровне C в SEXP, который никогда не привязывается к какому-либо символу в любой среде, а вместо этого передается непосредственно в функцию do_tracemem уровня C. Смотрите эту строку в функции оценки уровня C
Это означает, что когда числовая константа передается в tracemem (допустимый вызов, хотя обычно для этого нет никаких оснований), вы получаете фактический SEXP для константы, а не тот, который представляет переменную уровня R со значением 4, передаваемую в do_tracemem.
Насколько я могу судить, в любом фрейме оценки (возможно, я не совсем точно использую этот термин, но фреймы вызова и шаги в цикле for соответствуют требованиям, как и отдельные выражения верхнего уровня), каждая оценка, например, «4L» получает совершенно новый SEXP (в частности, INTSXP) с ИМЕНЕМ, сразу равным 4. Похоже, что между этими фреймами они могут быть общими, хотя я довольно сильно подозреваю, что это может быть артефактом повторного использования памяти, а не фактически разделяемыми секспами.
Приведенный ниже вывод, по-видимому, подтверждает теорию повторного использования памяти, но на данный момент у меня нет свободных циклов, чтобы подтвердить это сверх этого.
> for(i in 1:3) {print(tracemem(4L)); print(tracemem(4L))}
[1] "<0x1c3f3b8>"
[1] "<0x1c3f328>"
[1] "<0x1c3f3b8>"
[1] "<0x1c3f328>"
[1] "<0x1c3f3b8>"
[1] "<0x1c3f328>"
Надеюсь, это поможет.
Комментарии:
1. Спасибо за содержательный и подробный ответ. Кроме того, замена непостоянной структуры в
tracemem
, похоже, делает то, что я ожидал, например,print(tracemem(c(5L)))
в цикле для каждого вызова выдается другой адрес, предположительно потому, что (если я правильно понимаю ваш ответ) теперь он вычисляется с копией каждой итерации из-за того, что является оценкой закрытия (и, таким образом, ИМЯ увеличивается вdo_for
).