Почему производительность следующей функции не снижается из-за роста дополнительного объекта?

Ниже у меня есть три функции, которые выполняют одну и ту же операцию - дублируют данный data.frame и rbind его себе (т.е. плохая практика выращивания объекта).

Первая функция, f1, копирует входной объект в новый объект, x, затем увеличивает этот объект и, наконец, заменяет входной объект.

Вторая функция, f2, копирует входной объект в новый объект, x, а затем увеличивает входной объект.

Третья функция, f3, только увеличивает входной объект.

Я ожидал, что f1 будет самым медленным, учитывая, что он по существу требует изменения распределения памяти как для df, так и для x. Напротив, все 3 функции кажутся примерно равными по времени вычислений. Как я могу понять такое поведение? Мой пример ошибочен?

## Functions
# copy df, grow copy, replace df with copy
f1 <- function(df){
  x <- df
  x <- rbind(x, x)
  df <- x
  return(df)
}

# copy df, grow df
f2 <- function(df){
  x <- df
  df <- rbind(df, x)
  return(df)
}

# grow df
f3 <- function(df){
  df <- rbind(df, df)
  return(df)
}


## Benchmark
df <- rbind(iris)
res <- microbenchmark(f1(df), f2(df), f3(df), times=5000L)


## Print results:
print(res)

# Unit: microseconds
#   expr    min      lq     mean  median      uq       max neval
# f1(df) 255.66 263.591 292.6851 270.123 292.516  2693.291  5000
# f2(df) 255.66 263.591 302.5159 270.590 292.516 15460.876  5000
# f3(df) 255.66 263.591 299.6157 270.122 292.516  3613.758  5000


## Plot results:
boxplot(res)

Почему производительность следующей функции не снижается из-за роста дополнительного объекта?

Учитывая, что функции R оцениваются во временной среде, выделение памяти было бы новым для всех трех функций, не так ли?

LAP 03.07.2018 10:57

@LAP - Да, я думал об этом, но даже в этом случае должен быть вопрос о выращивании объектов.

Marc in the box 03.07.2018 10:58

@ chinsoon12 - Я не вижу разницы даже при использовании df <- matrix(runif (1e6), 1e3, 1e3) (times=100L).

Marc in the box 03.07.2018 11:04

Думаю, комментарий @LAP актуален. Вы вызываете функцию не рекурсивно, а каждую функцию N раз.

chinsoon12 03.07.2018 11:09

Но вы выращиваете объект во всех трех функциях? Я не вижу разницы в работе. R не волнует, какой объект вы выращиваете.

LAP 03.07.2018 11:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
20
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Функции R используют копирование при изменении. Итак, когда вы передаете df в качестве аргумента, если вы не изменяете его, он будет указывать на тот же объект, который вы передали (тот же адрес).

То же самое происходит, если вы назначаете один и тот же объект. Например, используя address <- function(x) cat(data.table::address(x), "\n"):

> x <- 1
> address(x)
0x58164b8 
> y <- x
> address(y)
0x58164b8 

Итак, теперь напечатайте несколько адресов в ваших функциях

## Functions
# copy df, grow copy, replace df with copy
f1 <- function(df){
  address(x <- df)
  address(x <- rbind(x, x))
  address(df <- x)
  return(df)
}

# copy df, grow df
f2 <- function(df){
  address(x <- df)
  address(df <- rbind(df, x))
  return(df)
}

Результат:

> df <- rbind(iris)
> address(df)
0x543e378 
> res1 <- f1(df)
0x543e378 
0x5d3bd08 
0x5d3bd08 
> res2 <- f2(df)
0x543e378 
0x5c89e40 

Итак, каждая из ваших функций создает только один новый объект, поэтому они имеют одинаковый размер.

Другие вопросы по теме