Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions vignettes/es/datatable-benchmarking.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
title: "Benchmarking con data.table"
date: "`{r} Sys.Date()`"
output:
litedown::html_format:
options:
toc: true
number_sections: true
vignette: >
%\VignetteIndexEntry{Benchmarking data.table}
%\VignetteEngine{litedown::vignette}
\usepackage[utf8]{inputenc}
---

```{r, echo = FALSE, message = FALSE}
library(data.table)
litedown::reactor(comment = "# ")
```

<style>
h2 {
font-size: 20px;
}

#TOC {
border: 1px solid #ccc;
border-radius: 5px;
padding-left: 1em;
background: #f6f6f6;
}
</style>

```{r, echo=FALSE, file='../_translation_links.R'}
```

`{r} .write.translation.links("Las traducciones de este documento están disponibles en: %s")`

Este documento sirve como guía para medir el rendimiento de `data.table`. Es un lugar único para documentar las mejores prácticas y los errores que se deben evitar.

# fread: borrar cachés

Idealmente, cada llamada a «fread» debería ejecutarse en una sesión nueva con los siguientes comandos antes de la ejecución de R. Esto borra el archivo de caché del sistema operativo en la RAM y la caché del disco duro

```sh
free -g
sudo sh -c 'echo 3 >/proc/sys/vm/drop_caches'
sudo lshw -class disk
sudo hdparm -t /dev/sda
```

Al comparar `fread` con soluciones que no son de R, tenga en cuenta que R requiere que los valores de las columnas de caracteres se agreguen a la caché de cadenas global de R. Esto requiere tiempo al leer datos, pero las operaciones posteriores se benefician, ya que las cadenas de caracteres ya se han almacenado en caché. Por lo tanto, además de cronometrar tareas aisladas (como `fread` por sí sola), conviene comparar el tiempo total de un flujo de trabajo integral de tareas, como la lectura de datos, su manipulación y la generación del resultado final.

# subset: umbral para la optimización del índice en consultas compuestas

La optimización de índice para consultas de filtros compuestos no se utilizará cuando el producto cruzado de los elementos proporcionados para filtrar exceda 1e4 elementos.

```r
DT = data.table(V1=1:10, V2=1:10, V3=1:10, V4=1:10)
setindex(DT)
v = c(1L, rep(11L, 9))
length(v)^4 # cross product of elements in filter
#[1] 10000 # <= 10000
DT[V1 %in% v & V2 %in% v & V3 %in% v & V4 %in% v, verbose=TRUE]
#Optimized subsetting with index 'V1__V2__V3__V4'
#on= matches existing index, using index
#Starting bmerge ...done in 0.000sec
#...
v = c(1L, rep(11L, 10))
length(v)^4 # cross product of elements in filter
#[1] 14641 # > 10000
DT[V1 %in% v & V2 %in% v & V3 %in% v & V4 %in% v, verbose=TRUE]
#Subsetting optimization disabled because the cross-product of RHS values exceeds 1e4, causing memory problems.
#...
```

# subset: evaluación comparativa basada en índices

Para mayor comodidad, `data.table` crea automáticamente un índice en los campos que se utilizan para filtra datos. Esto añadirá algo de sobrecarga al primer filtrado en campos específicos, pero reduce considerablemente el tiempo de consulta de esas columnas en ejecuciones posteriores. Para medir la velocidad, la mejor manera es medir la creación y la consulta de índices por separado. Con estos tiempos, es fácil decidir cuál es la estrategia óptima para su caso de uso. Para controlar el uso del índice, utilice las siguientes opciones:

```r
options(datatable.auto.index=TRUE)
options(datatable.use.index=TRUE)
```

- `use.index=FALSE` forzará la consulta a no usar índices incluso si existen, pero las claves existentes aún se usan para la optimización.
- `auto.index=FALSE` deshabilita la creación automática de índices al filtrar datos no indexados, pero si los índices se crearon antes de que se estableciera esta opción, o explícitamente al llamar a `setindex`, aún se usarán para la optimización.

Otras dos opciones controlan la optimización a nivel global, incluido el uso de índices:

```r
options(datatable.optimize=2L)
options(datatable.optimize=3L)
```

`options(datatable.optimize=2L)` desactivará por completo la optimización de filtros, mientras que `options(datatable.optimize=3L)` la reactivará. Estas opciones afectan a muchas más optimizaciones y, por lo tanto, no deben usarse cuando solo se necesita controlar los índices. Más información en `?datatable.optimize`.

# Operaciones *por referencia*

Al comparar funciones `set*`, solo tiene sentido medir la primera ejecución. Estas funciones actualizan su entrada por referencia, por lo que las ejecuciones posteriores utilizarán la `data.table` ya procesada, lo que sesgará los resultados

Para proteger su `data.table` de la actualización por referencia, puede usar las funciones `copy` o `data.table:::shallow`. Tenga en cuenta que `copy` puede ser muy costoso, ya que requiere duplicar el objeto completo. Es poco probable que queramos incluir el tiempo de duplicación en la tarea que estamos evaluando.

# Intentar comparar los procesos atómicos

Si su punto de referencia está destinado a ser publicado, será mucho más esclarecedor si lo divide para medir el tiempo de los procesos atómicos. De esta manera, sus lectores pueden ver cuánto tiempo se dedicó a leer los datos de la fuente, limpiarlos, transformarlos realmente y exportar los resultados. Por supuesto, si su punto de referencia está destinado a presentar un *flujo de trabajo de extremo a extremo*, entonces tiene todo el sentido presentar el tiempo general. Sin embargo, separar el tiempo de los pasos individuales es útil para comprender qué pasos son los principales cuellos de botella de un flujo de trabajo. Hay otros casos en los que el punto de referencia atómico podría no ser deseable, por ejemplo, al *leer un csv*, seguido de *agrupar*. R requiere llenar *la caché de cadena global de R*, lo que agrega sobrecarga adicional al importar datos de caracteres a una sesión de R. Por otro lado, la *caché de cadena global* podría acelerar procesos como *agrupar*. En tales casos, al comparar R con otros lenguajes, podría ser útil incluir el tiempo total.

# Evite la coerción de clase

A menos que esto sea lo que realmente quiera medir, debe preparar objetos de entrada de la clase esperada para cada herramienta que esté evaluando

# evitar `microbenchmark(..., times=100)`

Repetir un benchmark muchas veces no suele ofrecer la imagen más clara para las herramientas de procesamiento de datos. Por supuesto, tiene mucho sentido para cálculos más atómicos, pero esta no es una buena representación de la forma más común en que se utilizarán realmente estas herramientas, es decir, para las tareas de procesamiento de datos, que consisten en lotes de transformaciones proporcionadas secuencialmente, cada una ejecutada una vez. Matt dijo una vez:

> Soy muy cauteloso con los puntos de referencia medidos en tiempos inferiores a 1 segundo. Prefiero 10 segundos o más para una sola ejecución, lo que se logra aumentando el tamaño de los datos. Un recuento de repeticiones de 500 es alarmante. De 3 a 5 ejecuciones deberían ser suficientes para convencer con datos más grandes. La sobrecarga de llamadas y el tiempo de recolección de basura afectan las inferencias a esta escala tan pequeña.

Esto es muy válido. Cuanto menor sea la medición de tiempo, mayor será el ruido relativo. El ruido se genera por el envío de métodos, la inicialización de paquetes/clases, etc. El punto de referencia debe centrarse principalmente en casos de uso reales.

# procesamiento multiproceso

Uno de los principales factores que probablemente afecte a los tiempos es el número de subprocesos disponibles para su sesión de R. En versiones recientes de `data.table`, algunas funciones están paralelizadas. Puede controlar el número de subprocesos que desea utilizar con `setDTthreads`

```r
setDTthreads(0) # use all available cores (default)
getDTthreads() # check how many cores are currently used
```

# Dentro de un bucle, prefiera `set` en lugar de `:=`

A menos que esté utilizando el índice al realizar una *subasignación por referencia*, debería preferir la función `set` que no impone la sobrecarga de la llamada al método `[.data.table`.

```r
DT = data.table(a=3:1, b=letters[1:3])
setindex(DT, a)

# for (...) { # imagine loop here

DT[a==2L, b := "z"] # sub-assign by reference, uses index
DT[, d := "z"] # not sub-assign by reference, not uses index and adds overhead of `[.data.table`
set(DT, j="d", value="z") # no `[.data.table` overhead, but no index yet, till #1196

# }
```

# Dentro de un bucle, prefiera `setDT` en lugar de `data.table()`

A partir de ahora, `data.table()` tiene una sobrecarga, por lo tanto, dentro de los bucles se prefiere utilizar `as.data.table()` o `setDT()` en una lista válida.
Loading
Loading