Семантика копирования-на-изменении в векторе не добавляется в цикл. Зачем?
Этот вопрос звучит, чтобы быть частично ответным здесь, но это не достаточно конкретно для меня. Я хотел бы лучше понять, когда объект обновляется по ссылке и когда он копируется.
Более простой пример - выращивание векторов. Следующий код невероятно неэффективен в 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 ответа
Я заканчиваю @MikeH. awnser с этим кодом
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 создает дополнительные ссылки, но это действие явно не меняет адрес.
Об этом говорится в книге Хадли "Продвинутый Р.". В нем он говорит (перефразируя здесь), что всякий раз, когда 2 или более переменных указывают на один и тот же объект, R делает копию и затем изменяет эту копию. Прежде чем переходить к примерам, важно отметить, что в книге Хэдли упоминается еще одно важное замечание: RStudio
браузер среды делает ссылку на каждый объект, который вы создаете в командной строке.
Учитывая ваше наблюдаемое поведение, я предполагаю, что вы используете RStudio
который мы увидим, объяснит, почему на самом деле есть две переменные, указывающие на 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
в вашей функции y[i] = x[i] - x[i-1]
:
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.