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

Что такое семантика copy-on-modify в R, а где канонический источник?

Время от времени я сталкиваюсь с понятием, что R имеет семантику copy-on-modify, например, в Hadley devtools вики.

Большинство объектов R имеют семантику copy-on-modify, поэтому изменение функции аргумент не изменяет исходное значение

Я могу проследить этот термин обратно в список рассылки R-Help. Например, Питер Далгаард писал в июль 2003 г.:

R - функциональный язык с ленивой оценкой и слабой динамикой (переменная может изменять тип по желанию: a < - 1; a < - "a" - позволил). Семантически все копируется на модификацию, хотя некоторые оптимизационные трюки используются в реализации, чтобы избежать худшего Неэффективность.

Аналогично, Питер Далгаард писал в Jan 2004:

R имеет семантику copy-on-modify (в принципе, а иногда и в практика), поэтому, когда часть объекта меняется, вам, возможно, придется посмотреть новые места для всего, что содержало это, включая, возможно, самого объекта.

Еще дальше, в февраль 2000 Росс Ихака сказал:

Мы приложили немало усилий, чтобы это произошло. Я бы описал семантика как "копировать при изменении (если необходимо)". Копирование выполнено только когда объекты изменены. Часть (если необходимо) означает, что если мы можем доказать, что модификация не может изменить любые нелокальные переменные, то мы просто продолжаем и модифицируем без копирования.

Это не в руководстве

Независимо от того, насколько сильно я искал, я не могу найти ссылку на "copy-on-modify" в руководствах R, ни в R Определение языка, ни в R Internals

Вопрос

Мой вопрос состоит из двух частей:

  • Где это официально зарегистрировано?
  • Как работает копирование в режиме редактирования?

Например, правильно ли говорить о "pass-by-reference", поскольку обещание передается функции?

4b9b3361

Ответ 1

Вызов по значению

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

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

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

Дополнительная информация, особенно в аспекте copy-on-modify, приведена в описании SEXP в руководстве Internals, раздел 1.1.2 Остальная часть заголовка. В частности, он указывает [Акцент добавлен]

Поле 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, приведенного ранее.

Promises при оценке функции

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

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

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

Ответ 2

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

Вы можете увидеть результат на моей машине в http://rpubs.com/wush978/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 копирует объект только при первых модификациях.

Я не знаю почему.

Изменение атрибута

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 только копирует объект при первой модификации.