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

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

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

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

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

Точно так же Питер Далгаард написал в январе 2004 года :

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

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

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

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

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

Вопрос

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

  1. Где это официально зарегистрировано?
  2. Как работает копирование в редакторе?

Например, правильно ли говорить о «передаче по ссылке», поскольку promise передается функции?

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

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

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

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

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

named поле устанавливается и получает доступ к SET_NAMED и SET_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, приведенного ранее.

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

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

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

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

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

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

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


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

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

 #define USE_RINTERNALS #include  #include  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 свойства 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 повторно используйте память после первой модификации.

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

matrix

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

  • Почему data.table определен: = а не перегрузка <-?
  • Различные разрывы на грань гистограммы ggplot2
  • Карта Choropleth в ggplot с полигонами с отверстиями
  • Вставка значений переменных фактора в R или Python на основе даты - создание перерывов в школе
  • Определение использования памяти объектами?
  • Форсировать векторное кодирование символов от «неизвестного» до «UTF-8» в R
  • Как выровнять несколько графиков ggplot2 и добавить тени поверх них
  • Извлеките регулярное выражение
  • Как увеличить пространство между полосками на штриховом участке в ggplot2?
  • Как преобразовать имя переменной (object) в String
  • Фильтрация строк данных.файла по логическому условию
  • Давайте будем гением компьютера.