Семантика Copy-on-modify на векторе не добавляется в цикле. Почему?
На этот вопрос, кажется, можно частично ответить здесь, но это недостаточно конкретно для меня. Я хотел бы лучше понять, когда объект обновляется по ссылке и когда он копируется.
Более простой пример-векторное выращивание. Следующий код крайне неэффективен в R, потому что память не выделяется перед циклом, и копия создается на каждой итерации.
x = runif(10)
y = c()
for(i in 2:length(x))
y = c(y, x[i] - x[i-1])
Выделение памяти позволяет зарезервировать некоторую память, не перераспределяя ее при каждая итерация. Таким образом, этот код значительно быстрее, особенно с длинными векторами.
x = runif(10)
y = numeric(length(x))
for(i in 2:length(x))
y[i] = x[i] - x[i-1]
И вот приходит мой вопрос. На самом деле, когда вектор обновляется, он перемещается. Существует копия, которая сделана, как показано ниже.
a = 1:10
pryr::tracemem(a)
[1] "<0xf34a268>"
a[1] <- 0L
tracemem[0xf34a268 -> 0x4ab0c3f8]:
a[3] <-0L
tracemem[0x4ab0c3f8 -> 0xf2b0a48]:
Но в цикле эта копия не происходит
y = numeric(length(x))
for(i in 2:length(x))
{
y[i] = x[i] - x[i-1]
print(address(y))
}
Дает
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
[1] "0xe849dc0"
Я понимаю, почему код является медленным или быстрым в зависимости от распределения памяти, но я не понимаю логику R. Почему и как, для того же утверждения, в случае обновление производится по ссылке, а в другом случае обновление производится по копии. В общем случае, как мы можем знать, что произойдет?2 ответа:
Это описано в книге Хэдли "Advanced R book". В нем он говорит (перефразируя здесь), что всякий раз, когда 2 или более переменных указывают на один и тот же объект, R сделает копию, а затем изменит эту копию. Прежде чем перейти к примерам, одно важное замечание, которое также упоминается в книге Хэдли, заключается в том, что при использовании
RStudio
Обозреватель среды делает ссылку на каждый объект, созданный в командной строке.
Учитывая ваше наблюдаемое поведение, я предполагаю, что вы использование
RStudio
, которое мы увидим, объяснит, почему на самом деле есть 2 переменные, указывающие наa
вместо 1, Как вы могли бы ожидать.Функция, которую мы будем использовать, чтобы проверить, сколько переменных указывают на объект, является
refs()
. В первом опубликованном примере вы можете увидеть:library(pryr) a = 1:10 refs(x) #[1] 2
Это предполагает (и это то, что вы нашли), что 2 переменные указывают на
a
и, таким образом, любая модификацияa
приведет к тому, что R скопирует его, а затем изменит эту копию.Проверка
for loop
мы можем видеть, чтоy
всегда имеет один и тот же адрес и чтоrefs(y) = 1
в цикле for.y
не копируется, поскольку в вашей функцииy[i] = x[i] - x[i-1]
нет других ссылок, указывающих наy
:for(i in 2:length(x)) { y[i] = x[i] - x[i-1] print(c(address(y), refs(y))) } #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1" #[1] "0x19c3a230" "1"
С другой стороны, если ввести непримитивную функцию
y
в вашемfor loop
, вы увидите, что адресy
меняется каждый раз, что больше соответствует тому, что мы ожидаем:Обратите внимание на акцент нанепримитивном . Если ваша функцияis.primitive(lag) #[1] FALSE for(i in 2:length(x)) { y[i] = lag(y)[i] print(c(address(y), refs(y))) } #[1] "0x19b31600" "1" #[1] "0x19b31948" "1" #[1] "0x19b2f4a8" "1" #[1] "0x19b2d2f8" "1" #[1] "0x19b299d0" "1" #[1] "0x19b1bf58" "1" #[1] "0x19ae2370" "1" #[1] "0x19a649e8" "1" #[1] "0x198cccf0" "1"
y
является примитивным, таким как-
как:y[i] = y[i] - y[i-1]
R может оптимизировать это, чтобы избежать копирования.Спасибо @duckmayr за помощь в объяснении поведения внутри цикла for.
Я завершаю @MikeH. ошибки с этим кодом
library(pryr) x = runif(10) y = numeric(length(x)) print(c(address(y), refs(y))) for(i in 2:length(x)) { y[i] = x[i] - x[i-1] print(c(address(y), refs(y))) } print(c(address(y), refs(y)))
Вывод ясно показывает, что произошло
[1] "0x7872180" "2" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "1" [1] "0x765b860" "2"
Существует копия на первой итерации. Действительно, из-за Rstudio есть 2 ссылки. Но после этого первая копия
y
принадлежит циклам и недоступна в глобальной среде. Затем Rstudio не создает никаких дополнительных ссылок и, таким образом, никакая копия не производится во время следующих обновлений.y
обновляется по ссылке. На выходе из циклаy
становятся доступными в глобальном окружающая среда. Rstudio создает дополнительные ссылки, но это действие явно не изменяет адрес.