Что такое семантика копирования-на-изменении в R, и где находится канонический источник?
Время от времени я сталкиваюсь с мнением, что R имеет семантику копирования-на-изменении, например, в вики devtools Хэдли.
Большинство объектов R имеют семантику копирования при изменении, поэтому изменение аргумента функции не меняет исходное значение
Я могу проследить этот термин до списка рассылки R-Help. Например, Питер Далгаард написал в июле 2003 года:
R - функциональный язык с ленивой оценкой и слабой динамической типизацией (переменная может менять тип по желанию: a<- 1; a <- "a" разрешено). Семантически все копируется при модификации, хотя в реализации используются некоторые приемы оптимизации, чтобы избежать наихудшей неэффективности.
Точно так же Питер Далгаард написал в январе 2004 года:
R имеет семантику копирования при модификации (в принципе, а иногда и на практике), поэтому, как только часть объекта изменяется, вам, возможно, придется искать в новых местах все, что его содержит, включая, возможно, сам объект.
Еще дальше, в феврале 2000 года Росс Ихака сказал:
Мы приложили немало усилий, чтобы это произошло. Я бы описал семантику как "копировать при модификации (при необходимости)". Копирование выполняется только при изменении объектов. Часть (если необходимо) означает, что если мы можем доказать, что модификация не может изменить какие-либо нелокальные переменные, то мы просто продолжаем и модифицируем без копирования.
Это не в руководстве
Независимо от того, как сильно я искал, я не могу найти ссылку на "copy-on-modify" в руководствах R, ни в R Language Definition, ни в R Internals
Вопрос
Мой вопрос состоит из двух частей:
- Где это официально задокументировано?
- Как работает копирование на изменение?
Например, уместно ли говорить о "передаче по ссылке", поскольку обещание передается функции?
2 ответа
Вызов по значению
Определение языка R говорит об этом (в разделе 4.3.3 Оценка аргумента)
Семантика вызова функции в аргументе R определяется по значению. Как правило, предоставленные аргументы ведут себя так, как будто они являются локальными переменными, инициализированными предоставленным значением и именем соответствующего формального аргумента. Изменение значения предоставленного аргумента в функции не повлияет на значение переменной в вызывающем фрейме. [Акцент добавлен]
Хотя это не описывает механизм, с помощью которого работает метод копирования-на-изменении, в нем упоминается, что изменение объекта, переданного функции, не влияет на оригинал в вызывающем фрейме.
Дополнительная информация, в частности, об аспекте копирования при изменении, приведена в описании SEXP
s в руководстве по внутренним компонентам R, раздел 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 <- `dim<-`(a, c(7, 2))
но уже не так, и поэтому некоторые примитивные функции могут быть оптимизированы, чтобы избежать копирования в этом случае.
Хотя это и не описывает ситуацию, когда объекты передаются функциям в качестве аргументов, мы можем сделать вывод, что работает тот же процесс, особенно учитывая информацию из определения языка R, которое цитировалось ранее.
Обещания в оценке функций
Я не думаю, что будет правильно сказать, что обещание передано функции. Аргументы передаются в функцию, а используемые выражения сохраняются как обещания (плюс указатель на вызывающую среду). Только когда аргумент оценен, выражение, сохраненное в обещании, извлекается и оценивается в среде, указанной указателем, процесс, известный как форсирование.
В связи с этим я не считаю правильным говорить об этом по ссылке. R имеет семантику вызова по значению, но пытается избежать копирования, если только значение, переданное аргументу, не будет оценено и изменено.
Механизм NAMED - это оптимизация (как отмечено @hadley в комментариях), которая позволяет R отслеживать, нужно ли делать копию после модификации. Есть некоторые тонкости, связанные с тем, как именно работает механизм NAMED, как обсуждалось Питером Далгаардом (в ветке R Devel @mnel цитирует в своем комментарии к вопросу)
Я провел несколько экспериментов и обнаружил, что 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()
Копирование при записи
Сначала я проверяю свойство copy при записи, что означает, что 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 только копирует объект при первой модификации.