r performance lapply

lapply vs for loop-Rendimiento R



performance (2)

A menudo se dice que uno debería preferir lapply for bucles. Hay alguna excepción como, por ejemplo, Hadley Wickham señala en su libro Advance R.

( http://adv-r.had.co.nz/Functionals.html ) (Modificación en el lugar, Recursión, etc.). El siguiente es uno de este caso.

Solo por aprender, intenté reescribir un algoritmo de perceptrón en una forma funcional para comparar el rendimiento relativo. fuente ( https://rpubs.com/FaiHas/197581 ).

Aquí está el código.

# prepare input data(iris) irissubdf <- iris[1:100, c(1, 3, 5)] names(irissubdf) <- c("sepal", "petal", "species") head(irissubdf) irissubdf$y <- 1 irissubdf[irissubdf[, 3] == "setosa", 4] <- -1 x <- irissubdf[, c(1, 2)] y <- irissubdf[, 4] # perceptron function with for perceptron <- function(x, y, eta, niter) { # initialize weight vector weight <- rep(0, dim(x)[2] + 1) errors <- rep(0, niter) # loop over number of epochs niter for (jj in 1:niter) { # loop through training data set for (ii in 1:length(y)) { # Predict binary label using Heaviside activation # function z <- sum(weight[2:length(weight)] * as.numeric(x[ii, ])) + weight[1] if (z < 0) { ypred <- -1 } else { ypred <- 1 } # Change weight - the formula doesn''t do anything # if the predicted value is correct weightdiff <- eta * (y[ii] - ypred) * c(1, as.numeric(x[ii, ])) weight <- weight + weightdiff # Update error function if ((y[ii] - ypred) != 0) { errors[jj] <- errors[jj] + 1 } } } # weight to decide between the two species return(errors) } err <- perceptron(x, y, 1, 10) ### my rewriting in functional form auxiliary ### function faux <- function(x, weight, y, eta) { err <- 0 z <- sum(weight[2:length(weight)] * as.numeric(x)) + weight[1] if (z < 0) { ypred <- -1 } else { ypred <- 1 } # Change weight - the formula doesn''t do anything # if the predicted value is correct weightdiff <- eta * (y - ypred) * c(1, as.numeric(x)) weight <<- weight + weightdiff # Update error function if ((y - ypred) != 0) { err <- 1 } err } weight <- rep(0, 3) weightdiff <- rep(0, 3) f <- function() { t <- replicate(10, sum(unlist(lapply(seq_along(irissubdf$y), function(i) { faux(irissubdf[i, 1:2], weight, irissubdf$y[i], 1) })))) weight <<- rep(0, 3) t }

No esperaba ninguna mejora constante debido a los problemas mencionados anteriormente. Pero, sin embargo, me sorprendió mucho cuando vi el empeoramiento agudo de usar lapply y replicate .

microbenchmark estos resultados usando la función microbenchmark biblioteca microbenchmark

¿Cuáles podrían ser las razones? ¿Podría ser alguna pérdida de memoria?

expr min lq mean median uq f() 48670.878 50600.7200 52767.6871 51746.2530 53541.2440 perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 4184.131 4437.2990 4686.7506 4532.6655 4751.4795 perceptronC(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) 95.793 104.2045 123.7735 116.6065 140.5545 max neval 109715.673 100 6513.684 100 264.858 100

La primera función es la función lapply / replicate

La segunda es la función con bucles for

La tercera es la misma función en C++ usando Rcpp

Aquí, según Roland, el perfil de la función. No estoy seguro de poder interpretarlo de la manera correcta. Me parece que la mayor parte del tiempo se dedica a la subconfiguración de perfiles de funciones


Actualmente,

Probé la diferencia con un problema que resolvió recientemente.

Solo inténtalo tú mismo.

En mi conclusión, no tengo ninguna diferencia, pero for loop to my case fue significativamente más rápido que lapply.

Ps: Trato principalmente de mantener la misma lógica en uso.

ds <- data.frame(matrix(rnorm(1000000), ncol = 8)) n <- c(''a'',''b'',''c'',''d'',''e'',''f'',''g'',''h'') func <- function(ds, target_col, query_col, value){ return (unique(as.vector(ds[ds[query_col] == value, target_col]))) } f1 <- function(x, y){ named_list <- list() for (i in y){ named_list[[i]] <- func(x, ''a'', ''b'', i) } return (named_list) } f2 <- function(x, y){ list2 <- lapply(setNames(nm = y), func, ds = x, target_col = "a", query_col = "b") return(list2) } benchmark(f1(ds2, n )) benchmark(f2(ds2, n ))

Como puede ver, hice una rutina simple para construir una lista nombrada basada en un marco de datos, la función func realiza los valores de columna extraídos, el f1 usa un bucle for para recorrer el marco de datos y el f2 usa una función lapply.

En mi computadora obtengo estos resultados:

test replications elapsed relative user.self sys.self user.child 1 f1(ds2, n) 100 110.24 1 110.112 0 0 sys.child 1 0

&&

test replications elapsed relative user.self sys.self user.child 1 f1(ds2, n) 100 110.24 1 110.112 0 0 sys.child 1 0


En primer lugar, es un mito ya desacreditado que los bucles son más lentos que el lapply . Los bucles for en R se han hecho mucho más lapply y actualmente son al menos tan rápidos como el lapply .

Dicho esto, tienes que repensar tu uso de lapply aquí. Su implementación exige la asignación al entorno global, porque su código requiere que actualice el peso durante el ciclo. Y esa es una razón válida para no considerar lapply .

lapply es una función que debe usar para sus efectos secundarios (o falta de efectos secundarios). La función lapply combina los resultados en una lista automáticamente y no se mete con el entorno en el que trabaja, a diferencia de un bucle for . Lo mismo vale para replicate . Ver también esta pregunta:

¿La familia R aplica más que el azúcar sintáctico?

La razón por la que su solución de lapply es mucho más lenta es porque su forma de usarla crea muchos más gastos generales.

  • replicate no es otra cosa que sapply internamente, por lo que en realidad combina sapply y lapply para implementar su doble bucle. sapply crea una sobrecarga adicional porque tiene que probar si el resultado puede simplificarse o no. Por lo tanto, un bucle for será realmente más rápido que usar replicate .
  • dentro de su función lapply anónima, debe acceder al marco de datos para x e y para cada observación. Esto significa que, en contra de su bucle for, por ejemplo, la función $ debe llamarse cada vez.
  • Debido a que utiliza estas funciones de gama alta, su solución ''lapply'' llama a 49 funciones, en comparación con su solución for que solo llama a 26. Estas funciones adicionales para la solución lapply incluyen llamadas a funciones como match , structure , [[ , names , %in% , sys.call , duplicated , ... Todas las funciones no son necesarias para su bucle for ya que esa no realiza ninguna de estas comprobaciones.

Si desea ver de dónde proviene esta sobrecarga adicional, mire el código interno de replicate , unlist , sapply y simplify2array .

Puede usar el siguiente código para tener una mejor idea de dónde pierde su rendimiento con el lapply . ¡Corre esta línea por línea!

Rprof(interval = 0.0001) f() Rprof(NULL) fprof <- summaryRprof()$by.self Rprof(interval = 0.0001) perceptron(as.matrix(irissubdf[1:2]), irissubdf$y, 1, 10) Rprof(NULL) perprof <- summaryRprof()$by.self fprof$Fun <- rownames(fprof) perprof$Fun <- rownames(perprof) Selftime <- merge(fprof, perprof, all = TRUE, by = ''Fun'', suffixes = c(".lapply",".for")) sum(!is.na(Selftime$self.time.lapply)) sum(!is.na(Selftime$self.time.for)) Selftime[order(Selftime$self.time.lapply, decreasing = TRUE), c("Fun","self.time.lapply","self.time.for")] Selftime[is.na(Selftime$self.time.for),]