#r #function
#r #передача по ссылке #передача по значению
Вопрос:
Время от времени я сталкиваюсь с понятием, что R имеет семантику копирования при изменении, например, в devtools wiki Хэдли.
Большинство объектов R имеют семантику копирования при изменении, поэтому изменение аргумента функции не изменяет исходное значение
Я могу проследить этот термин до списка рассылки R-Help. Например, Питер Далгаард написал в июле 2003 года:
R — функциональный язык с отложенной оценкой и слабой динамической типизацией (переменная может изменять тип по желанию: a <- 1 ; допускается a <- «a»). Семантически все копируется при изменении, хотя в реализации используются некоторые приемы оптимизации, чтобы избежать наихудшей неэффективности.
Аналогично, Питер Далгаард написал в январе 2004 года:
R имеет семантику копирования при изменении (в принципе, а иногда и на практике), поэтому, как только часть объекта изменяется, вам, возможно, придется искать в новых местах все, что его содержало, включая, возможно, сам объект.
Еще дальше, в феврале 2000 года Росс Ихака сказал:
Мы приложили немало усилий, чтобы это произошло. Я бы описал семантику как «копировать при изменении (при необходимости)». Копирование выполняется только при изменении объектов. Часть (при необходимости) означает, что если мы можем доказать, что модификация не может изменить какие-либо нелокальные переменные, тогда мы просто идем дальше и модифицируем без копирования.
Этого нет в руководстве
Как бы я ни искал, я не могу найти ссылку на «копирование при изменении» в руководствах по R, ни в определении языка R, ни во внутренних компонентах R
Вопрос
Мой вопрос состоит из двух частей:
- Где это официально задокументировано?
- Как работает копирование при изменении?
Например, правильно ли говорить о «передаче по ссылке», поскольку обещание передается функции?
Комментарии:
1. В некоторых случаях внутренние компоненты могут не быть задокументированы, чтобы дать разработчикам свободу действий для изменения того, как это работает. В этом случае не предполагается писать код, который зависит от внутренней операции, поскольку он может сломаться в будущем.
Ответ №1:
Вызов по значению
Определение языка R говорит об этом (в разделе 4.3.3 Оценка аргументов)
Семантика вызова функции в аргументе R является вызовом по значению. В общем случае предоставленные аргументы ведут себя так, как если бы они были локальными переменными, инициализированными указанным значением и именем соответствующего формального аргумента. Изменение значения предоставленного аргумента внутри функции не повлияет на значение переменной в вызывающем фрейме. [Курсив добавлен]
Хотя это не описывает механизм, с помощью которого работает копирование при изменении, в нем упоминается, что изменение объекта, переданного функции, не влияет на оригинал в вызывающем фрейме.
Дополнительная информация, в частности, об аспекте копирования при изменении, приведена в описании SEXP
s в руководстве по внутренним функциям R, раздел 1.1.2 Rest заголовка. В частности, в нем говорится [Курсив добавлен]
named
Поле устанавливается и к нему обращаютсяSET_NAMED
NAMED
макросы and , и принимает значения0
,1
и2
. R имеет иллюзию «вызова по значению», поэтому присваивание типаb <- a
кажется, что он создает копию
a
и ссылается на нее какb
. Однако, если ниa
b
один из них не был впоследствии изменен, копировать не нужно.
На самом деле происходит то, что новый символb
привязывается к тому же
значению,a
что иnamed
поле объекта value (в данном
случае к2
). Когда объект собирается быть изменен, кnamed
полю
обращаются. Значение2
означает, что объект должен быть продублирован
перед изменением. (Обратите внимание, что это не говорит о том, что
необходимо дублировать, только о том, что его следует дублировать, независимо
от того, необходимо это или нет.) Значение0
означает, что известно, что никакие другие
SEXP
данные не используются совместно с этим объектом, и поэтому его можно безопасно изменять.
Значение1
используется для таких ситуаций, как
dim(a) <- c(7, 2)
где в принципе две копии a существуют на время
вычисления как (в принципе)
a <- `dim<-`(a, c(7, 2))
но не дольше, и поэтому некоторые примитивные функции могут быть оптимизированы, чтобы
избежать копирования в этом случае.
Хотя это не описывает ситуацию, когда объекты передаются функциям в качестве аргументов, мы можем сделать вывод, что работает тот же процесс, особенно учитывая информацию из определения языка R, приведенного ранее.
Обещания при оценке функции
Я не думаю, что правильно говорить, что обещание передается функции. Аргументы передаются функции, а фактически используемые выражения сохраняются как обещания (плюс указатель на вызывающую среду). Только когда вычисляется аргумент, выражение, сохраненное в обещании, извлекается и вычисляется в среде, указанной указателем, процесс, известный как принудительное.
Таким образом, я не считаю правильным говорить о передаче по ссылке в этом отношении. R имеет семантику вызова по значению, но пытается избежать копирования, если значение, переданное аргументу, не вычисляется и не изменяется.
Именованный механизм — это оптимизация (как отметил @hadley в комментариях), которая позволяет R отслеживать, нужно ли делать копию при модификации. Есть некоторые тонкости, связанные с тем, как именно работает ИМЕНОВАННЫЙ механизм, о чем говорил Питер Далгаард (в ветке разработки R, которую @mnel цитирует в своем комментарии к вопросу)
Комментарии:
1. Важным моментом (который следует подчеркнуть) является то, что R является вызовом по значению
2. @hadley но разве эта
NAMED
концепция не используется также при вызовах функций с дополнительным выпуском обещаний?3. @hadley добавил новый акцент.
4. NAMED — это просто оптимизация. R вел бы себя одинаково без него.
5. Совершенно верно @Josho’Brien 1. Я слишком много перефразировал там и изменил намерение того, что написал Питер. Отредактирует соответствующим образом.
Ответ №2:
Я провел над ним несколько экспериментов и обнаружил, что R всегда копирует объект при первой модификации.
Вы можете увидеть результат на моей машине в http://rpubs.com/wush978/5916
Пожалуйста, дайте мне знать, если я допустил какую-либо ошибку, спасибо.
Чтобы проверить, скопирован ли объект или нет
Я сбрасываю адрес памяти следующим кодом на C:
#define USE_RINTERNALS
#include <R.h>
#include <Rdefines.h>
SEXP dump_address(SEXP src) {
Rprintf("p p %dn", amp;(src->u), INTEGER(src), INTEGER(src) - (int*)amp;(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 копирует объект только при первом изменении.
Комментарии:
1. 1. Очень интересно. Я думаю, вы могли бы задать вопрос о том, почему R копирует объекты при первой настройке модификации / атрибута.