Skip to content

Commit

Permalink
add _freeze/
Browse files Browse the repository at this point in the history
  • Loading branch information
calderonsamuel committed Nov 17, 2024
1 parent c8392dd commit 04fba9c
Show file tree
Hide file tree
Showing 120 changed files with 15,419 additions and 0 deletions.
21 changes: 21 additions & 0 deletions _freeze/index/execute-results/html.json
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
}
}
Loading

0 comments on commit 04fba9c

Please sign in to comment.