-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c8392dd
commit 04fba9c
Showing
120 changed files
with
15,419 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"hash": "ffae8be9f1a2cb47972f48f9073b92a0", | ||
"result": { | ||
"engine": "knitr", | ||
"markdown": "---\ntitle: \"Optimización del Rendimiento en Shiny\"\nsubtitle: \"Técnicas y Mejores Prácticas\"\nauthor: \"Samuel Calderon\"\nformat: \n revealjs:\n theme: default\n transition: slide\n slide-number: true\n logo: img/Appsilon_logo.svg\n footer: \"LatinR: 2024-11-18\"\nmermaid: \n theme: default\nexecute: \n eval: false\n echo: true\n---\n\n\n\n## Estructura del Taller (3 horas)\n\n- Introducción \n- Ciclo de optimización: Ejercicio 1 - Benchmarking\n- Profiling: Ejercicio 2\n- Optimización - Data: Ejercicio 3\n- Optimización - Shiny: Ejercicio 4\n- Optimización - Async: Ejercicio 5\n- Temas avanzados\n- Preguntas\n\n# Introducción\n\n## Appsilon\n\n![](img/appsilon-web.png)\n\n<https://www.appsilon.com/>\n\n## We are hiring!\n\n- [R shiny developer](https://jobs.lever.co/appsilon/6e6cea0f-4ec3-439a-8456-d5e31e51c05b?lever-origin=ap%5B%E2%80%A6%5Dloper)\n- [R developer with life science](https://jobs.lever.co/appsilon/d5c698a5-9f93-4fb4-a22b-b4abaf77de5d?lever-origin=applied&lever-source%5B%5D=CAREERS)\n- [Project Manager (US time zone)](https://jobs.lever.co/appsilon/e8594bfe-2c9a-4504-b978-ff3242bc9c73?lever-origin=applied&lever-source%5B%5D=careers%20page?utm_medium%3Djob-boards)\n\nPara ver más posiciones: <https://www.appsilon.com/careers>\n\n## Quién soy\n\n- Politólogo y ahora R shiny developer\n- De Lima, Perú\n- Contacto:\n - Web: <https://www.samuelenrique.com>\n - Github: <https://github.com/calderonsamuel>\n - Linkedin: <https://www.linkedin.com/in/samuelcalderon/>\n \n## ¿Quiénes son ustedes?\n\n- Compartir en el chat:\n - Nombre\n - De dónde son y qué hora es en su ciudad\n - Background profesional (brevísimo)\n - 3 paquetes de R favoritos (si son de nicho, mejor)\n\n\n# Ciclo de Optimización\n\n## Primero lo primero\n\n¿Qué es una computadora? La interacción de 3 componentes principales\n\n![](img/computer-core-elements.png)\n\n## ¿Qué es optimizar?\n\n![](img/computer-task-manager.png)\n\n¡Depende de la necesidad! \n\nEn general, pensemos en tiempo (CPU) o espacio (memory/storage). El dinero es un eje secreto.\n\n## El ciclo graficado\n\n![](img/optimizacion-loop.png)\n\n<https://www.appsilon.com/post/optimize-shiny-app-performance>\n\n## A saber en cada etapa\n\n- Benchmarking: ¿Performa como esperamos?\n- Profilling: ¿Dónde están los cuellos de botella?\n- Estimación/Recomendación: ¿Qué puedo hacer?\n- Optimización: Tomar decisión e implementar\n\n## Tipos de benchmarking \n\n- Manual\n- Avanzado ([shinyloadtest](https://rstudio.github.io/shinyloadtest/index.html)):\n\n## Ejercicio 1 - Benchmarking\n\n![](img/benchmark-exercise.png)\n\n---\n\n- Prueba la app y anota cuánto tiempo te toma ver la información para:\n\n- 3 ciudades diferentes\n- 3 edades máximas diferentes\n\nEnlace: <https://01933b4a-2e76-51f9-79f4-629808c48a59.share.connect.posit.cloud/>\n\n# Profiling\n\n## Profiling - Herramientas en R\n\nEl profiling es una técnica utilizada para identificar cuellos de botella en el rendimiento de tu código:\n\n## `{profvis}`\n\nEs una herramienta interactiva que proporciona una visualización detallada del tiempo de ejecución de tu código.\n\n- Instalación:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ninstall.packages(\"profvis\")\n```\n:::\n\n\n\n- Uso básico:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(profvis)\nprofvis({\n# Código a perfilar\n})\n```\n:::\n\n\n\n## shiny.tictoc\n\nUna herramienta que usa Javascript para calcular el tiempo que toman las acciones en la app, desde el punto de vista del navegador.\n\nEs super fácil de añadir a una app.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\ntags$script(\n src = \"https://cdn.jsdelivr.net/gh/Appsilon/[email protected]/shiny-tic-toc.min.js\"\n)\n```\n:::\n\n\n\n- Si no saben añadir JS: [Packaging Javscript code for Shiny](https://shiny.posit.co/r/articles/build/packaging-javascript/)\n\n---\n\nEjecutar cualquiera de estas operaciones en la consola de Javascript. \n\n```js\n// Print out all measurements\nshowAllMeasurements()\n\n// To download all measurements as a CSV file\nexportMeasurements()\n\n// To print out summarised measurements (slowest rendering output, slowest server computation)\nshowSummarisedMeasurements()\n\n// To export an html file that visualizes measurements on a timeline\nawait exportHtmlReport()\n```\n\nMuchos navegadores cuentan con herramientas de desarrollador donde puedes encontrar una mientras tu app está corriendo.\n\n## Usando profvis\n\nUbicar la herramienta en Rstudio\n\n![](img/profiling-01.png)\n\n---\n\n\nLa consola de R mostrará el botón \"Stop profiling\". Esto significa que el profiler está activado.\n\n![](img/profiling-02.png)\n\nCorre tu shiny app e interactúa con ella. Luego, puedes detener la app y el profiler.\n\n---\n\nEl panel de edición de Rstudio te mostrará una nueva vista. \n\n![](img/profiling-03.png)\n\nLa parte superior hace profiling de cada línea de código, la parte inferior muestra un *FlameGraph*, que indica el tiempo requerido por cada operación.\n\n---\n\nTambién puede accederse a la pestaña \"Data\".\n\n![](img/profiling-04.png)\n\nEsta indica cuánto tiempo y memoria se ha requerido por cada operación. Nos da un resumen de la medición.\n\n---\n\nPara una revisión más exhaustiva del uso de `{profvis}`, puedes consultar la documentación oficial:\n\n- Ejemplos: <https://profvis.r-lib.org/articles/rstudio.html>\n- Integración con RStudio: <https://profvis.r-lib.org/articles/rstudio.html>\n\n## Ejercicio 2 - Profiling\n\n- Interpreta los resultados\n - ¿Cuáles son los puntos más críticos?\n \nToma en cuenta que estás probando esto para un solo usuario.\n\n## Optimización - Data\n\n1. Usar opciones más rápidas para cargar datos\n2. Usar formatos de archivo más eficientes\n3. Pre-procesar los cálculos\n4. Usar bases de datos. Puede requerir aprender SQL.\n\n¡Puedes combinar todo!\n\n## Cargar datos más rápido\n\n- data.table::fread()\n- vroom::vroom()\n- readr::read_csv()\n\n## Ejemplo\n\nNO ejecutar durante el workshop porque toma tiempo en correr\n\n```r\nsuppressMessages(\n microbenchmark::microbenchmark(\n read.csv = read.csv(\"data/personal.csv\"),\n read_csv = readr::read_csv(\"data/personal.csv\"),\n vroom = vroom::vroom(\"data/personal.csv\"),\n fread = data.table::fread(\"data/personal.csv\")\n )\n)\n#> Unit: milliseconds\n#> expr min lq mean median uq max neval\n#> read.csv 1891.3824 2007.2517 2113.5217 2082.6016 2232.7825 2442.6901 100\n#> read_csv 721.9287 820.4181 873.4603 866.7321 897.3488 1165.5929 100\n#> vroom 176.7522 189.8111 205.2099 197.9027 206.2619 495.2784 100\n#> fread 291.9581 370.8261 410.3995 398.9489 439.7827 638.0363 100\n```\n\n\n## Formatos de datos eficientes:\n\n- Parquet (via {arrow})\n- Feather (compatibilidad con Python)\n- fst\n- RDS (nativo de R)\n\n## Ejemplo\n\nNO ejecutar durante el workshop porque toma tiempo en correr\n\n```r\nsuppressMessages(\n microbenchmark::microbenchmark(\n read.csv = read.csv(\"data/personal.csv\"),\n fst = fst::read_fst(\"data/personal.fst\"),\n parquet = arrow::read_parquet(\"data/personal.parquet\"),\n rds = readRDS(\"data/personal.rds\")\n )\n)\n#> Unit: milliseconds\n#> expr min lq mean median uq max neval\n#> read.csv 1911.2919 2075.26525 2514.29114 2308.57325 2658.03690 4130.748 100\n#> fst 201.1500 267.85160 339.73881 308.24680 357.19565 834.646 100\n#> parquet 64.5013 67.29655 84.48485 70.70505 87.81995 405.147 100\n#> rds 558.5518 644.32460 782.37898 695.07300 860.85075 1379.519 100\n```\n\n## Pre-procesar cálculos\n\n- Filtrado previo: Menos tamaño\n- Transformación o agregación previa: Menos tiempo\n- Uso de índices: Búsqueda rápida\n\nEs, en esencia, *caching*. Personalmente, mi estrategia favorita. \n\nDifícil de usar si se requiere calcular en vivo, real-time (stock exchange, streaming data), o la data no puede ser guardada en cualquier lugar (seguridad, privacidad). \n\n\n## Sin pre-procesamiento\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# app.R\nsurvey <- read.csv(\"data/survey.csv\")\n\nserver <- function(input, output) {\n output$table <- renderTable({\n survey |> \n filter(region == input$region) |> \n summarise(avg_time = mean(temps_trajet_en_heures))\n })\n}\n```\n:::\n\n\n\n## Con pre-procesamiento\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\n# script.R\nsurvey <- read.csv(\"data/survey.csv\")\nregions <- unique(survey$region)\n\nvalues <- regions |> \n lapply(function(x) {\n survey |> \n dplyr::filter(region == x) |> \n dplyr::summarise(avg_time = mean(temps_trajet_en_heures))\n }) |> \n setNames(regions)\n\nsaveRDS(values, \"data/values.rds\")\n```\n:::\n\n::: {.cell}\n\n```{.r .cell-code}\n# app.R\nvalues <- readRDS(\"data/values.rds\")\n\nserver <- function(input, output) {\n output$table <- renderTable(values[[input$region]])\n}\n```\n:::\n\n\n\n\n\n## Bases de Datos\n\n- **Escalabilidad**: Las bases de datos pueden manejar grandes volúmenes de datos de manera eficiente.\n- **Consultas Rápidas**: Permiten realizar consultas complejas de manera rápida.\n- **Persistencia**: Los datos se almacenan de manera persistente, lo que permite su recuperación en cualquier momento.\n\nAlgunos ejemplos notables son SQLite, MySQL, PostgreSQL, DuckDB.\n \n\n## Ejercicio 3 - Data\n\nImplementa una estrategia de optimización\n\n# Optimización - Shiny\n\n## Cuando una app arranca\n\n![](img/diagrama1.png)\n\n---\n\nDel lado de shiny, optimizar consiste básicamente en hacer que la app (en realidad, el procesador) haga el menor trabajo posible.\n\n## Reducir reactividad\n\n\n\n::: {.cell}\n\n```{.r .cell-code code-line-numbers=\"|3,4,5,9,10,11\"}\nserver <- function(input, output, session) {\n output$table <- renderTable({\n survey |> \n filter(region == input$region) |> \n filter(age <= input$age)\n })\n \n output$histogram <- renderPlot({\n survey |> \n filter(region == input$region) |> \n filter(age <= input$age) |> \n ggplot(aes(temps_trajet_en_heures)) +\n geom_histogram(bins = 20) +\n theme_light()\n })\n}\n```\n:::\n\n\n\n---\n\n`reactive()` al rescate\n\n\n\n::: {.cell}\n\n```{.r .cell-code code-line-numbers=\"|2-6|9,13\"}\nserver <- function(input, output, session) {\n filtered <- reactive({\n survey |> \n filter(region == input$region) |> \n filter(age <= input$age)\n })\n \n output$table <- renderTable({\n filtered()\n })\n \n output$histogram <- renderPlot({\n filtered() |> \n ggplot(aes(temps_trajet_en_heures)) +\n geom_histogram(bins = 20) +\n theme_light()\n })\n}\n```\n:::\n\n\n\n::: aside\nUsamos más de espacio (memoria) para reducir tiempo (CPU)\n:::\n\n## Controlar reactividad\n\nPuedes encadenar `bindEvent()` a un `reactive()` u `observe()`.\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nmy_reactive <- reactive({\n # slow reactive computation\n}) |> \n bindEvent(input$trigger)\n\nobserve({\n # slow reactive side effect\n}) |> \n bindEvent(input$trigger)\n```\n:::\n\n\n\n::: aside\nEn versiones pasadas, esto se hacía con `observeEvent()` o `eventReactive()`.\n:::\n\n---\n\n\n\n::: {.cell}\n\n```{.r .cell-code code-line-numbers=\"|5|16\"}\nui <- page_sidebar(\n sidebar = sidebar(\n selectInput(inputId = \"region\", ...),\n sliderInput(inputId = \"age\", ...),\n actionButton(inputId = \"compute\", label = \"Calcular\")\n ),\n ...\n)\n\nserver <- function(input, output, session) {\n filtered <- reactive({\n survey |> \n filter(region == input$region) |> \n filter(age <= input$age)\n }) |> \n bindEvent(input$compute, ignoreNULL = FALSE)\n}\n```\n:::\n\n\n\nAhora `filtered()` solo se actualizará cuando haya interacción con `input$compute`. \n\n::: aside\n`ignoreNULL = FALSE` permite ejecutar el `reactive()` al iniciar la app.\n:::\n\n## Estrategias de Caché\n\n1. bindCache()\n\n2. Niveles de caché:\n - Nivel aplicación: `cache = \"app\"`\n - Nivel sesión: `cache = \"session\"`\n\n## Comunicación servidor / navegador\n\n- Reducir en tamaño y frecuencia lo que se manda al *cliente*.\n\n\n## Ejercicio 4 - Shiny\n\n\n\n# Optimización - Async\n\n## Programación Asíncrona {.smaller}\n\n- Casos de uso:\n - Operaciones I/O (bases de datos, APIs)\n - Cálculos intensivos\n- Herramientas:\n - Paquetes {promises} y {future}\n - ExtendedTask (Shiny 1.8.1+)\n\n## Ejercicio 5 - Async\n\n\n# Preguntas\n", | ||
"supporting": [ | ||
"index_files" | ||
], | ||
"filters": [ | ||
"rmarkdown/pagebreak.lua" | ||
], | ||
"includes": { | ||
"include-after-body": [ | ||
"\n<script>\n // htmlwidgets need to know to resize themselves when slides are shown/hidden.\n // Fire the \"slideenter\" event (handled by htmlwidgets.js) when the current\n // slide changes (different for each slide format).\n (function () {\n // dispatch for htmlwidgets\n function fireSlideEnter() {\n const event = window.document.createEvent(\"Event\");\n event.initEvent(\"slideenter\", true, true);\n window.document.dispatchEvent(event);\n }\n\n function fireSlideChanged(previousSlide, currentSlide) {\n fireSlideEnter();\n\n // dispatch for shiny\n if (window.jQuery) {\n if (previousSlide) {\n window.jQuery(previousSlide).trigger(\"hidden\");\n }\n if (currentSlide) {\n window.jQuery(currentSlide).trigger(\"shown\");\n }\n }\n }\n\n // hookup for slidy\n if (window.w3c_slidy) {\n window.w3c_slidy.add_observer(function (slide_num) {\n // slide_num starts at position 1\n fireSlideChanged(null, w3c_slidy.slides[slide_num - 1]);\n });\n }\n\n })();\n</script>\n\n" | ||
] | ||
}, | ||
"engineDependencies": {}, | ||
"preserve": {}, | ||
"postProcess": true | ||
} | ||
} |
Oops, something went wrong.