... относительно времени выполнения и / или памяти.
Если это не так, докажите это с помощью фрагмента кода. Обратите внимание, что ускорение векторизацией не считается. Убыстрение должны исходить из apply
( tapply
, sapply
, ...) сама по себе.
Эти apply
функции в R не обеспечивают повышенную производительность по сравнению с другими сквозными функциями (например for
). Единственным исключением является то, lapply
что он может быть немного быстрее, потому что он выполняет больше работы в C-коде, чем в R (см. Этот вопрос для примера ).
Но в целом правило состоит в том, что вы должны использовать функцию применения для ясности, а не для производительности .
Я хотел бы добавить к этому, что применяемые функции не имеют побочных эффектов , что является важным отличием, когда речь идет о функциональном программировании на R. Это можно переопределить с помощью assign
или <<-
, но это может быть очень опасно. Побочные эффекты также затрудняют понимание программы, поскольку состояние переменной зависит от истории.
Редактировать:
Просто чтобы подчеркнуть это на тривиальном примере, который рекурсивно вычисляет последовательность Фибоначчи; это может быть выполнено несколько раз, чтобы получить точную меру, но дело в том, что ни один из методов не имеет существенно отличающихся характеристик:
> fibo <- function(n) {
+ if ( n < 2 ) n
+ else fibo(n-1) + fibo(n-2)
+ }
> system.time(for(i in 0:26) fibo(i))
user system elapsed
7.48 0.00 7.52
> system.time(sapply(0:26, fibo))
user system elapsed
7.50 0.00 7.54
> system.time(lapply(0:26, fibo))
user system elapsed
7.48 0.04 7.54
> library(plyr)
> system.time(ldply(0:26, fibo))
user system elapsed
7.52 0.00 7.58
Изменить 2:
Что касается использования параллельных пакетов для R (например, rpvm, rmpi, snow), они обычно предоставляют apply
семейные функции (даже foreach
пакет, по сути, эквивалентен, несмотря на название). Вот простой пример sapply
функции в snow
:
library(snow)
cl <- makeSOCKcluster(c("localhost","localhost"))
parSapply(cl, 1:20, get("+"), 3)
В этом примере используется кластер сокетов, для которого не требуется устанавливать дополнительное программное обеспечение; в противном случае вам понадобится что-то вроде PVM или MPI (см . страницу кластеризации Tierney ). snow
имеет следующие прикладные функции:
parLapply(cl, x, fun, ...)
parSapply(cl, X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)
parApply(cl, X, MARGIN, FUN, ...)
parRapply(cl, x, fun, ...)
parCapply(cl, x, fun, ...)
Имеет смысл использовать apply
функции для параллельного выполнения, поскольку они не имеют побочных эффектов . Когда вы изменяете значение переменной в for
цикле, оно устанавливается глобально. С другой стороны, все apply
функции можно безопасно использовать параллельно, потому что изменения являются локальными для вызова функции (если вы не пытаетесь использовать assign
или <<-
, в этом случае вы можете ввести побочные эффекты). Само собой разумеется, важно быть осторожным с локальными и глобальными переменными, особенно когда имеешь дело с параллельным выполнением.
Редактировать:
Вот простой пример , чтобы продемонстрировать разницу между for
и до *apply
сих пор , как побочные эффекты обеспокоены:
> df <- 1:10
> # *apply example
> lapply(2:3, function(i) df <- df * i)
> df
[1] 1 2 3 4 5 6 7 8 9 10
> # for loop example
> for(i in 2:3) df <- df * i
> df
[1] 6 12 18 24 30 36 42 48 54 60
Обратите внимание, как df
в родительской среде изменяется, for
но не изменяется *apply
.
apply
семейство функций. Следовательно, структурирование программ по их применению позволяет их распараллеливать с очень небольшими предельными издержками.snowfall
упаковку и попробовать примеры в их виньетке.snowfall
строится поверхsnow
пакета и абстрагирует детали распараллеливания, что еще больше упрощает выполнение распараллеленныхapply
функций.foreach
тех пор он стал доступен и, похоже, очень интересуется SO.lapply
«немного быстрее», чемfor
цикл. Тем не менее, я не вижу ничего, что подсказывало бы это. Вы только упоминаете, чтоlapply
это быстрее, чемsapply
, что является общеизвестным фактом по другим причинам (sapply
пытается упростить вывод и, следовательно, должен сделать много проверки размера данных и потенциальных преобразований). Ничего не связано сfor
. Я что-то упускаю?Иногда ускорение может быть значительным, например, когда вам приходится вкладывать циклы для получения среднего значения, основанного на группировке из более чем одного фактора. Здесь у вас есть два подхода, которые дают вам одинаковый результат:
Оба дают одинаковый результат, будучи матрицей 5 x 10 со средними значениями и именованными строками и столбцами. Но :
Вот и ты. Что я выиграл? ;-)
источник
*apply
быстрее. Но я думаю, что более важным моментом являются побочные эффекты (обновил мой ответ на примере).data.table
это еще быстрее, и я думаю, что "проще".library(data.table)
dt<-data.table(X,Y,Z,key=c("Y,Z"))
system.time(dt[,list(X_mean=mean(X)),by=c("Y,Z")])
tapply
является специализированной функцией для конкретной задачи, именно поэтому он быстрее , чем цикл. Он не может делать то, что может делать цикл for (в то время как обычныйapply
может). Ты сравниваешь яблоки с апельсинами.... и как я только что написал в другом месте, vapply твой друг! ... это как sapply, но вы также указываете тип возвращаемого значения, что делает его намного быстрее.
1 января 2020 г., обновление:
источник
for
циклы быстрее на моем Windows 10, 2-ядерный компьютер. Я сделал это с5e6
элементами - цикл составил 2,9 секунды против 3,1 секунды дляvapply
.В другом месте я писал, что пример, подобный Шейну, на самом деле не подчеркивает разницу в производительности между различными типами циклического синтаксиса, потому что все время затрачивается внутри функции, а не на нагрузку на цикл. Кроме того, код несправедливо сравнивает цикл for без памяти с функциями семейства apply, которые возвращают значение. Вот немного другой пример, который подчеркивает суть.
Если вы планируете сохранить результат, то применять семейные функции можно гораздо больше, чем синтаксический сахар.
(простой unlist для z равен всего 0,2 с, поэтому задержка выполняется намного быстрее. Инициализация z в цикле for довольно быстрая, потому что я даю среднее значение за последние 5 из 6 запусков, поэтому перемещение за пределы system.time вряд ли повлияет на вещи)
Еще одна вещь, которую следует отметить, - это то, что есть еще одна причина использовать семейные функции независимо от их производительности, ясности или отсутствия побочных эффектов.
for
Цикл , как правило , способствует положить в максимально возможной степени в пределах цикла. Это связано с тем, что каждый цикл требует настройки переменных для хранения информации (среди прочих возможных операций). Применить заявления, как правило, смещены в другую сторону. Часто вы хотите выполнить несколько операций с вашими данными, некоторые из которых могут быть векторизованы, но некоторые могут быть не в состоянии. В R, в отличие от других языков, лучше отделить те операции и запустить те, которые не векторизованы в операторе применения (или векторизованной версии функции), и те, которые векторизованы как истинные векторные операции. Это часто значительно повышает производительность.На примере Joris Meys, где он заменяет традиционный цикл for на удобную R-функцию, мы можем использовать ее, чтобы показать эффективность написания кода более R-дружественным образом для аналогичного ускорения без специализированной функции.
Это оказывается намного быстрее, чем
for
петля, и немного медленнее, чем встроенная оптимизированнаяtapply
функция. Это не потому, чтоvapply
он намного быстрее,for
а потому, что он выполняет только одну операцию в каждой итерации цикла. В этом коде все остальное векторизовано. В традиционномfor
цикле Джориса Мейса в каждой итерации выполняется много (7?) Операций, и для его выполнения достаточно много настроек. Также обратите внимание, насколько компактнее этаfor
версия.источник
2.798 0.003 2.803; 4.908 0.020 4.934; 1.498 0.025 1.528
, а vapply еще лучше:1.19 0.00 1.19
sapply
50% медленнее , чемfor
и вlapply
два раза быстрее.y
к1:1e6
, а неnumeric(1e6)
(вектор нулей). Попытка выделитьfoo(0)
наz[0]
снова и не хорошо иллюстрирует типичное сfor
использованием цикла. В противном случае сообщение на месте.При применении функций к подмножествам вектора, это
tapply
может быть довольно быстро, чем цикл for. Пример:apply
Однако в большинстве случаев увеличение скорости не обеспечивается, а в некоторых случаях может быть даже намного медленнее:Но для этих ситуаций у нас есть
colSums
иrowSums
:источник
microbenchmark
это гораздо точнее, чемsystem.time
. Если вы попытаетесь сравнить,system.time(f3(mat))
иsystem.time(f4(mat))
вы получите разные результаты почти каждый раз. Иногда только надлежащий тест может показать самую быструю функцию.