Вложенные функции и использование памяти в R [duplicate]

Посмотрите на этот пример Plnkr

Переменная this сильно отличается timesCalled с каждым нажатием кнопки увеличивается только на 1. Ответ на мой личный вопрос:

.click( () => { } )

и

.click(function() { })

создают одинаковое количество функции при использовании в цикле, как вы можете видеть из подсчета Guid в Plnkr.

59
задан Andrie 2 April 2013 в 13:43
поделиться

2 ответа

Call-by-value

Определение R языка говорит об этом (в разделе 4.3.3 Оценка аргумента )

Семантика вызова функции в аргументе R - это вызов по значению. В общем случае предоставленные аргументы ведут себя так, как если бы они были локальными переменными, инициализированными поставляемым значением, и именем соответствующего формального аргумента. Изменение значения предоставленного аргумента внутри функции не повлияет на значение переменной в вызывающем кадре. [Выделение добавлено]

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

Дополнительная информация, особенно в аспекте копирования на модификацию, приведена в описании SEXP s в руководстве R Internals , раздел 1.1 .2 Остальная часть заголовка . В частности, он указывает [Emphasis added]

Поле named установлено и доступно макросами SET_NAMED и NAMED и принимает значения 0, 1 и 2. R имеет иллюзию 'по значению' , поэтому присваивание типа

b <- a

появляется, чтобы сделать копию a и ссылаться на нее как b . Однако , если ни впоследствии a, ни b не были изменены, нет необходимости копировать. Что действительно происходит, так это то, что новый символ b привязан к тому же значению, что и a и поле named объекта значения задано (в данном случае - 2). Когда объект будет изменен, поле named будет проконсультироваться. Значение 2 означает, что объект должен быть дублирован перед изменением. (Обратите внимание, что это не означает, что необходимо дублировать, только чтобы он дублировался независимо от того, нужно ли это или нет.) Значение 0 означает, что известно, что никакие другие SEXP не делят данные с этим объектом, а поэтому его можно безопасно изменить. Значение 1 используется для ситуаций, подобных

dim(a) <- c(7, 2)

, где в принципе две копии a существуют в течение времени вычисления как (в принципе)

a <- `dim<-`(a, c(7, 2))

но уже не так, и поэтому некоторые примитивные функции могут быть оптимизированы, чтобы избежать копирования в этом случае.

Пока это не описывает ситуацию, когда объекты передаются в функции как аргументы, мы может вывести, что тот же самый процесс работает, особенно с учетом информации из определения языка R, приведенного ранее.

Обещания в оценке функций

Не думаю, что вполне правильно сказать, что обещание передано функции . Аргументы передаются функции, а используемые фактические выражения хранятся как обещания (плюс указатель на вызывающую среду). Только когда оценивается аргумент, это выражение, хранящееся в обещании, полученном и оцененном в среде, указанной указателем, процесс, известный как , принудительный .

Таким образом, t верю, что правильно говорить о pass-by-reference в этом отношении. R имеет семантику вызова по значению , но пытается избежать копирования, если значение, переданное аргументу, оценивается и изменяется.

Механизм NAMED является оптимизацией (как отмечено @ hadley в комментариях), который позволяет R отслеживать, нужно ли копировать при модификации. Есть некоторые тонкости, связанные с тем, как работает механизм NAMED, о котором говорил Питер Дальгаард (в потоке R Devel @mnel цитирует в своем комментарии к вопросу)

36
ответ дан Gavin Simpson 20 August 2018 в 07:28
поделиться
  • 1
    Важным битом (который следует подчеркнуть) является то, что R является значением по умолчанию – hadley 2 April 2013 в 14:12
  • 2
    @hadley, но не концепция NAMED, также используемая с вызовами функций, с дополнительной проблемой обещаний? – Gavin Simpson 2 April 2013 в 14:17
  • 3
    @hadley добавил новый акцент. – Gavin Simpson 2 April 2013 в 14:18
  • 4
    NAMED - это просто оптимизация. R будет вести себя одинаково без него. – hadley 3 April 2013 в 14:14
  • 5
    Совершенно верно @ JoshO'Brien +1. Я пара-фразировал слишком много и изменил намерение Питера. Соответственно отредактирует. – Gavin Simpson 3 April 2013 в 15:31

Я сделал несколько экспериментов над ним и обнаружил, что R всегда копирует объект под первой модификацией.

Вы можете увидеть результат на моей машине в http://rpubs.com/ wash978 / 5916

Пожалуйста, дайте мне знать, если я сделал какую-либо ошибку, спасибо.


Чтобы проверить, скопирован ли объект или нет

Я выгружаю адрес памяти со следующим кодом C:

#define USE_RINTERNALS
#include <R.h>
#include <Rdefines.h>

SEXP dump_address(SEXP src) {
  Rprintf("%16p %16p %d\n", &(src->u), INTEGER(src), INTEGER(src) - (int*)&(src->u));
  return R_NilValue;
}

Он выведет 2 адреса:

  • Адрес блока данных SEXP
  • Адрес непрерывного блока integer

Давайте скомпилируем и загрузим эту функцию C.

Rcpp:::SHLIB("dump_address.c")
dyn.load("dump_address.so")

Информация о сеансе

Здесь sessionInfo тестовой среды.

sessionInfo()

Копировать при записи

Сначала я проверяю свойство копии при записи , что R только копирует объект только при его изменении.

a <- 1L
b <- a
invisible(.Call("dump_address", a))
invisible(.Call("dump_address", b))
b <- b + 1
invisible(.Call("dump_address", b))

Объект b копирует с a при модификации. R реализует свойство copy on write.

Измените вектор / матрицу на месте

Затем я проверю, будет ли R копировать объект, когда мы модифицируем элемент вектора / матрицы.

Вектор с длиной 1

a <- 1L
invisible(.Call("dump_address", a))
a <- 1L
invisible(.Call("dump_address", a))
a[1] <- 1L
invisible(.Call("dump_address", a))
a <- 2L 
invisible(.Call("dump_address", a))

Адрес меняется каждый раз, что означает, что R не использует повторно память.

Длинный вектор

system.time(a <- rep(1L, 10^7))
invisible(.Call("dump_address", a))
system.time(a[1] <- 1L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 1L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 2L)
invisible(.Call("dump_address", a))

Для длинного вектора R повторно использовать память после первой модификации.

Кроме того, приведенный выше пример также показывает, что «изменение на месте» влияет на производительность, когда объект огромен.

Матрица

system.time(a <- matrix(0L, 3162, 3162))
invisible(.Call("dump_address", a))
system.time(a[1,1] <- 0L)
invisible(.Call("dump_address", a))
system.time(a[1,1] <- 1L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 2L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 2L)
invisible(.Call("dump_address", a))

Кажется, что R копирует объект только при первых модификациях.

Не знаю, почему.

Изменение attribute

system.time(a <- vector("integer", 10^2))
invisible(.Call("dump_address", a))
system.time(names(a) <- paste(1:(10^2)))
invisible(.Call("dump_address", a))
system.time(names(a) <- paste(1:(10^2)))
invisible(.Call("dump_address", a))
system.time(names(a) <- paste(1:(10^2) + 1))
invisible(.Call("dump_address", a))

Результат тот же. R только копирует объект при первой модификации.

21
ответ дан wush978 20 August 2018 в 07:28
поделиться
  • 1
    +1. Очень интересно. Я думаю, вы могли бы задать вопрос о том, почему R копирует объекты в первую настройку изменения / атрибута. – Ferdinand.kraft 5 May 2013 в 14:17
Другие вопросы по тегам:

Похожие вопросы: