-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathNames-values.qmd
737 lines (491 loc) · 29.6 KB
/
Names-values.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
# Nombres y valores {#sec-names-values}
```{r, include = FALSE}
source("common.R")
id <- function() {
x <- sample(c(0:9, letters[1:6]), 3, replace = TRUE)
paste0("0x", paste(x, collapse = ""))
}
```
## Introducción
En R, es importante comprender la distinción entre un objeto y su nombre. Si lo hace, le ayudará a:
- Predecir con mayor precisión el rendimiento y el uso de memoria de su código.
- Escribir código más rápido evitando copias accidentales, una fuente importante de código lento.
- Comprender mejor las herramientas de programación funcional de R.
El objetivo de este capítulo es ayudarlo a comprender la distinción entre nombres y valores, y cuándo R copiará un objeto.
### Prueba {.unnumbered}
Responda las siguientes preguntas para ver si puede omitir este capítulo con seguridad. Puede encontrar las respuestas al final del capítulo en la @sec-names-values-answers.
1. Dado el siguiente data frame, ¿cómo creo una nueva columna llamada "3" que contenga la suma de `1` y `2`? Solo puede usar `$`, no `[[`. ¿Qué hace que `1`, `2` y `3` sean desafiantes como nombres de variables?
```{r}
df <- data.frame(runif(3), runif(3))
names(df) <- c(1, 2)
```
2. En el siguiente código, ¿cuánta memoria ocupa `y`?
```{r}
x <- runif(1e6)
y <- list(x, x, x)
```
3. ¿En qué línea se copia `a` en el siguiente ejemplo?
```{r}
a <- c(1, 5, 3, 2)
b <- a
b[[1]] <- 10
```
### Estructura {.unnumbered}
- La @sec-binding-basics lo introduce a la distinción entre nombres y valores, y explica cómo `<-` crea un vínculo, o referencia, entre un nombre y un valor.
- La @sec-copy-on-modify describe cuándo R hace una copia: cada vez que modificas un vector, es casi seguro que estás creando un nuevo vector modificado. Aprenderá a usar `tracemem()` para averiguar cuándo se produce realmente una copia. Luego, explorará las implicaciones que se aplican a las llamadas a funciones, listas, data frames y vectores de caracteres.
- La @sec-object-size explora las implicaciones de las dos secciones anteriores sobre cuánta memoria ocupa un objeto. Dado que su intuición puede estar profundamente equivocada y dado que `utils::object.size()` es lamentablemente inexacto, aprenderá a usar `lobstr::obj_size()`.
- La @sec-modify-in-place describe las dos excepciones importantes para copiar al modificar: con entornos y valores con un solo nombre, los objetos se modifican en su lugar.
- La @sec-gc concluye el capítulo con una discusión sobre el recolector de basura, que libera la memoria utilizada por objetos que ya no están referenciados por un nombre.
### Requisitos previos {.unnumbered}
Usaremos el paquete [lobstr](https://github.com/r-lib/lobstr) para profundizar en la representación interna de los objetos R.
```{r setup}
library(lobstr)
```
### Fuentes {.unnumbered}
Los detalles de la gestión de memoria de R no están documentados en un solo lugar. Gran parte de la información de este capítulo se obtuvo de una lectura atenta de la documentación (en particular `?Memory` y `?gc`), la sección [perfilado de memoria](http://cran.r-project.org/doc/manuals/R-exts.html#Profiling-R-code-for-memory-use) de *Escribiendo extensiones R* [@r-exts] y [SEXPs](http://cran.r-project.%20org/doc/manuals/R-ints.html#SEXPs) de *R internals* [@r-ints]. El resto lo descubrí leyendo el código fuente de C, realizando pequeños experimentos y haciendo preguntas sobre R-devel. Cualquier error es enteramente mío.
## Binding basics {#sec-binding-basics}
\index{bindings|seealso {assignment}} \index{assignment} \index{obj\_addr()}
Considere este código:
```{r bind1}
x <- c(1, 2, 3)
```
Es fácil leerlo como: "crear un objeto llamado 'x', que contenga los valores 1, 2 y 3". Desafortunadamente, esa es una simplificación que conducirá a predicciones inexactas sobre lo que R realmente está haciendo detrás de escena. Es más exacto decir que este código está haciendo dos cosas:
- Está creando un objeto, un vector de valores, `c(1, 2, 3)`.
- Y vincula ese objeto a un nombre, `x`.
En otras palabras, el objeto, o valor, no tiene nombre; en realidad es el nombre el que tiene un valor.
Para aclarar aún más esta distinción, dibujaré diagramas como este:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/binding-1.png")
```
El nombre, `x`, se dibuja con un rectángulo redondeado. Tiene una flecha que apunta (o une o hace referencia) al valor, el vector `c(1, 2, 3)`. La flecha apunta en dirección opuesta a la flecha de asignación: `<-` crea un enlace desde el nombre en el lado izquierdo hasta el objeto en el lado derecho.
Por lo tanto, puede pensar en un nombre como una referencia a un valor. Por ejemplo, si ejecuta este código, no obtiene otra copia del valor `c(1, 2, 3)`, obtiene otro enlace al objeto existente:
```{r bind2, dependson = "bind1"}
y <- x
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/binding-2.png")
```
Es posible que hayas notado que el valor `c(1, 2, 3)` tiene una etiqueta: `0x74b`. Si bien el vector no tiene nombre, ocasionalmente necesitaré referirme a un objeto independiente de sus enlaces. Para que eso sea posible, etiquetaré los valores con un identificador único. Estos identificadores tienen una forma especial que se parece a la "dirección" de la memoria del objeto, es decir, la ubicación en la memoria donde se almacena el objeto. Pero debido a que las direcciones de memoria reales cambian cada vez que se ejecuta el código, usamos estos identificadores en su lugar.
Puede acceder al identificador de un objeto con `lobstr::obj_addr()`. Hacerlo te permite ver que tanto `x` como `y` apuntan al mismo identificador:
```{r bind3, dependson = "bind2"}
obj_addr(x)
obj_addr(y)
```
Estos identificadores son largos y cambian cada vez que reinicia R.
Puede tomar algún tiempo comprender la distinción entre nombres y valores, pero comprender esto es realmente útil en la programación funcional, donde las funciones pueden tener diferentes nombres en diferentes contextos.
### Nombres no sintácticos {#sec-non-syntactic}
\index{reserved names} \index{'@\texttt{`}} \index{non-syntactic names}
R tiene reglas estrictas sobre lo que constituye un nombre válido. Un nombre **sintáctico** debe constar de letras[^names-values-1], dígitos, `.` y `_` pero no puede comenzar con `_` o un dígito. Además, no puede usar ninguna de las **palabras reservadas** como `TRUE`, `NULL`, `if` y `function` (vea la lista completa en `?Reserved`). Un nombre que no sigue estas reglas es un nombre **no sintáctico**; si intenta usarlos, obtendrá un error:
[^names-values-1]: Sorprendentemente, precisamente lo que constituye una letra está determinado por su ubicación actual. Eso significa que la sintaxis del código R en realidad puede diferir de una computadora a otra, y que es posible que un archivo que funciona en una computadora ni siquiera se analice en otra. Evite este problema apegado a los caracteres ASCII (es decir, A-Z) tanto como sea posible.
```{r, eval = FALSE}
_abc <- 1
#> Error: unexpected input in "_"
if <- 10
#> Error: unexpected assignment in "if <-"
```
Es posible anular estas reglas y usar cualquier nombre, es decir, cualquier secuencia de caracteres, rodeándolo con acentos graves:
```{r}
`_abc` <- 1
`_abc`
`if` <- 10
`if`
```
Si bien es poco probable que cree deliberadamente nombres tan locos, debe comprender cómo funcionan estos nombres locos porque los encontrará, más comúnmente cuando carga datos que se han creado fuera de R.
::: sidebarinplace
*Puede* también crear enlaces no sintácticos usando comillas simples o dobles (por ejemplo, `"_abc" <- 1`) en lugar de acentos graves, pero no debería, porque tendrá que usar una sintaxis diferente para recuperar los valores. La capacidad de usar cadenas en el lado izquierdo de la flecha de asignación es un artefacto histórico, usado antes de que R admitiera los acentos graves.
:::
### Ejercicios
1. Explique la relación entre `a`, `b`, `c` y `d` en el siguiente código:
```{r}
a <- 1:10
b <- a
c <- b
d <- 1:10
```
2. El siguiente código accede a la función de media de varias maneras. ¿Todos apuntan al mismo objeto de función subyacente? Verifique esto con `lobstr::obj_addr()`.
```{r, eval = FALSE}
mean
base::mean
get("mean")
evalq(mean)
match.fun("mean")
```
3. De forma predeterminada, las funciones de importación de datos base R, como `read.csv()`, convertirán automáticamente los nombres no sintácticos en sintácticos. ¿Por qué podría ser esto problemático? ¿Qué opción le permite suprimir este comportamiento?
4. ¿Qué reglas usa `make.names()` para convertir nombres no sintácticos en sintácticos?
5. Simplifiqué ligeramente las reglas que rigen los nombres sintácticos. ¿Por qué `.123e1` no es un nombre sintáctico? Lea `?make.names` para obtener todos los detalles.
## Copiar al modificar {#sec-copy-on-modify}
\index{copy-on-modify}
Considere el siguiente código. Vincula `x` e `y` al mismo valor subyacente, luego modifica `y`[^names-values-2].
[^names-values-2]: Es posible que se sorprenda al ver que `[[` se usa para crear un subconjunto de un vector numérico. Volveremos a esto en la @sec-subset-single, pero en resumen, creo que siempre debes usar `[[` cuando obtienes o configuras un solo elemento.
```{r}
x <- c(1, 2, 3)
y <- x
y[[3]] <- 4
x
```
Modificar `y` claramente no modificó `x`. Entonces, ¿qué pasó con el enlace compartido? Mientras que el valor asociado con `y` cambió, el objeto original no lo hizo. En su lugar, R creó un nuevo objeto, '0xcd2', una copia de '0x74b' con un valor cambiado, y luego rebotó 'y' a ese objeto.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/binding-3.png")
```
Este comportamiento se llama **copiar al modificar**. Comprenderlo mejorará radicalmente su intuición sobre el rendimiento del código R. Una forma relacionada de describir este comportamiento es decir que los objetos R no se pueden modificar o **inmutables**. Sin embargo, generalmente evitaré ese término porque hay un par de excepciones importantes para copiar al modificar que aprenderá en la @sec-modify-in-place.
Al explorar el comportamiento de copiar al modificar de forma interactiva, tenga en cuenta que obtendrá diferentes resultados dentro de RStudio. Esto se debe a que el panel de entorno debe hacer una referencia a cada objeto para mostrar información sobre él. Esto distorsiona su exploración interactiva pero no afecta el código dentro de las funciones y, por lo tanto, no afecta el rendimiento durante el análisis de datos. Para experimentar, recomiendo ejecutar R directamente desde la terminal o usar Quarto (como este libro).
### `tracemem()`
\index{tracemem()}
Puedes ver cuándo se copia un objeto con la ayuda de `base::tracemem()`. Una vez que llame a esa función con un objeto, obtendrá la dirección actual del objeto:
```{r trace1, eval = FALSE}
x <- c(1, 2, 3)
cat(tracemem(x), "\n")
#> <0x7f80c0e0ffc8>
```
A partir de ese momento, cada vez que se copie ese objeto, `tracemem()` imprimirá un mensaje que le indicará qué objeto se copió, su nueva dirección y la secuencia de llamadas que llevaron a la copia:
```{r trace2, dependson = "trace1", eval = FALSE}
y <- x
y[[3]] <- 4L
#> tracemem[0x7f80c0e0ffc8 -> 0x7f80c4427f40]:
```
Si modifica `y` de nuevo, no se copiará. Esto se debe a que el nuevo objeto ahora solo tiene un único nombre vinculado, por lo que R aplica la optimización de modificación en el lugar. Volveremos a esto en la @sec-modify-in-place.
```{r trace3, dependson = "trace2"}
y[[3]] <- 5L
untracemem(x)
```
`untracemem()` es lo contrario de `tracemem()`; apaga el rastreo.
### Llamadas de función
Las mismas reglas para copiar también se aplican a las llamadas a funciones. Toma este código:
```{r}
f <- function(a) {
a
}
x <- c(1, 2, 3)
cat(tracemem(x), "\n")
z <- f(x)
# there's no copy here!
untracemem(x)
```
Mientras `f()` se está ejecutando, `a` dentro de la función apunta al mismo valor que `x` fuera de la función:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/binding-f1.png")
```
Aprenderá más sobre las convenciones utilizadas en este diagrama en la @sec-execution-environments. En resumen: la función `f()` está representada por el objeto amarillo a la derecha. Tiene un argumento formal, `a`, que se convierte en un enlace (indicado por una línea negra punteada) en el entorno de ejecución (el cuadro gris) cuando se ejecuta la función.
Una vez que `f()` se complete, `x` y `z` apuntarán al mismo objeto. `0x74b` nunca se copia porque nunca se modifica. Si `f()` modificara `x`, R crearía una nueva copia y luego `z` vincularía ese objeto.
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/binding-f2.png")
```
### Listas {#sec-list-references}
\index{ref()}
\index{lists}
No son solo los nombres (es decir, las variables) los que apuntan a los valores; los elementos de las listas también lo hacen. Considere esta lista, que es superficialmente muy similar al vector numérico anterior:
```{r list1}
l1 <- list(1, 2, 3)
```
Esta lista es más compleja porque en lugar de almacenar los valores en sí, almacena referencias a ellos:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/list.png")
```
Esto es particularmente importante cuando modificamos una lista:
```{r list2, dependson = "list1"}
l2 <- l1
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/l-modify-1.png")
```
```{r list3, dependson = "list2"}
l2[[3]] <- 4
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/l-modify-2.png")
```
Al igual que los vectores, las listas utilizan el comportamiento de copiar al modificar; la lista original no se modifica y R crea una copia modificada. Esto, sin embargo, es una copia **superficial**: el objeto de la lista y sus enlaces se copian, pero los valores a los que apuntan los enlaces no. Lo opuesto a una copia superficial es una copia profunda donde se copian los contenidos de cada referencia. Antes de R 3.1.0, las copias siempre eran copias profundas.
Para ver los valores que se comparten en las listas, use `lobstr::ref()`. `ref()` imprime la dirección de memoria de cada objeto, junto con una identificación local para que pueda cruzar fácilmente los componentes compartidos.
```{r list4, dependson = "list3"}
ref(l1, l2)
```
### Data frames {#sec-df-modify}
Los data frames son listas de vectores, por lo que copiar al modificar tiene consecuencias importantes cuando modifica un data frame. Tome este data frame como un ejemplo:
```{r}
d1 <- data.frame(x = c(1, 5, 6), y = c(2, 4, 3))
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/dataframe.png")
```
Si modifica una columna, solo *esa* columna debe modificarse; los otros seguirán apuntando a sus referencias originales:
```{r}
d2 <- d1
d2[, 2] <- d2[, 2] * 2
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/d-modify-c.png")
```
Sin embargo, si modifica una fila, se modifican todas las columnas, lo que significa que se deben copiar todas las columnas:
```{r}
d3 <- d1
d3[1, ] <- d3[1, ] * 3
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/d-modify-r.png")
```
### Vectores de caracteres
\index{string pool}
El último lugar donde R usa referencias es con vectores de caracteres [^names-values-3]. Normalmente dibujo vectores de caracteres como este:
[^names-values-3]: Confusamente, un vector de caracteres es un vector de cadenas, no de caracteres individuales.
```{r}
x <- c("a", "a", "abc", "d")
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/character.png")
```
Pero esto es una ficción educada. R en realidad usa un **grupo de cadenas global** donde cada elemento de un vector de caracteres es un puntero a una cadena única en el grupo:
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/character-2.png")
```
Puede solicitar que `ref()` muestre estas referencias configurando el argumento `character` en `TRUE`:
```{r}
ref(x, character = TRUE)
```
Esto tiene un impacto profundo en la cantidad de memoria que usa un vector de caracteres, pero por lo demás generalmente no es importante, por lo que en otras partes del libro dibujaré vectores de caracteres como si las cadenas vivieran dentro de un vector.
### Ejercicios
1. ¿Por qué `tracemem(1:10)` no es útil?
2. Explique por qué `tracemem()` muestra dos copias cuando ejecuta este código. Sugerencia: mire cuidadosamente la diferencia entre este código y el código que se muestra en la sección anterior.
```{r, results = FALSE}
x <- c(1L, 2L, 3L)
tracemem(x)
x[[3]] <- 4
```
3. Esboza la relación entre los siguientes objetos:
```{r}
a <- 1:10
b <- list(a, a)
c <- list(b, a, 1:10)
```
4. ¿Qué sucede cuando ejecutas este código?
```{r}
x <- list(1:10)
x[[2]] <- x
```
Dibuja una imagen.
## Tamaño del objeto {#sec-object-size}
```{=tex}
\index{object.size}
\index{obj\_size()}
\index{ALTREP}
```
\index{memory usage}
Puedes averiguar cuánta memoria ocupa un objeto con `lobstr::obj_size()`[^names-values-4]:
[^names-values-4]: Tenga cuidado con la función `utils::object.size()`. No tiene en cuenta correctamente las referencias compartidas y devolverá tamaños que son demasiado grandes.
```{r}
obj_size(letters)
obj_size(ggplot2::diamonds)
```
Dado que los elementos de las listas son referencias a valores, el tamaño de una lista puede ser mucho más pequeño de lo esperado:
```{r}
x <- runif(1e6)
obj_size(x)
y <- list(x, x, x)
obj_size(y)
```
`y` es sólo 80 bytes[^names-values-5] mayor que `x`. Ese es el tamaño de una lista vacía con tres elementos:
[^names-values-5]: Si está ejecutando R de 32 bits, verá tamaños ligeramente diferentes.
```{r}
obj_size(list(NULL, NULL, NULL))
```
Del mismo modo, debido a que R usa un grupo de cadenas global, los vectores de caracteres ocupan menos memoria de lo que cabría esperar: repetir una cadena 100 veces no hace que ocupe 100 veces más memoria.
```{r}
banana <- "bananas bananas bananas"
obj_size(banana)
obj_size(rep(banana, 100))
```
Las referencias también dificultan pensar en el tamaño de los objetos individuales. `obj_size(x) + obj_size(y)` solo será igual a `obj_size(x, y)` si no hay valores compartidos. Aquí, el tamaño combinado de `x` e `y` es el mismo que el tamaño de `y`:
```{r}
obj_size(x, y)
```
Finalmente, R 3.5.0 y las versiones posteriores tienen una función que podría generar sorpresas: ALTREP, abreviatura de **representación alternativa**. Esto permite que R represente ciertos tipos de vectores de forma muy compacta. El lugar donde es más probable que vea esto es con `:` porque en lugar de almacenar cada número en la secuencia, R solo almacena el primer y el último número. Esto significa que cada secuencia, sin importar cuán grande sea, tiene el mismo tamaño:
```{r}
obj_size(1:3)
obj_size(1:1e3)
obj_size(1:1e6)
obj_size(1:1e9)
```
### Ejercicios
1. En el siguiente ejemplo, ¿por qué `object.size(y)` y `obj_size(y)` son tan radicalmente diferentes? Consulta la documentación de `object.size()`.
```{r}
y <- rep(list(runif(1e4)), 100)
object.size(y)
obj_size(y)
```
2. Toma la siguiente lista. ¿Por qué su tamaño es algo engañoso?
```{r}
funs <- list(mean, sd, var)
obj_size(funs)
```
3. Prediga la salida del siguiente código:
```{r, results = FALSE}
a <- runif(1e6)
obj_size(a)
b <- list(a, a)
obj_size(b)
obj_size(a, b)
b[[1]][[1]] <- 10
obj_size(b)
obj_size(a, b)
b[[2]][[1]] <- 10
obj_size(b)
obj_size(a, b)
```
## Modificar en el lugar {#sec-modify-in-place}
\index{modify-in-place}
Como hemos visto anteriormente, modificar un objeto R generalmente crea una copia. Hay dos excepciones:
- Los objetos con un solo enlace obtienen una optimización de rendimiento especial.
- Los entornos, un tipo especial de objeto, siempre se modifican en su lugar.
### Objetos con un solo enlace {#sec-single-binding}
\index{loops!avoiding copies in}
Si un objeto tiene un solo nombre vinculado, R lo modificará en su lugar:
```{r}
v <- c(1, 2, 3)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/v-inplace-1.png")
```
```{r}
v[[3]] <- 4
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/v-inplace-2.png")
```
(Tenga en cuenta los ID de objeto aquí: `v` continúa enlazando con el mismo objeto, `0x207`).
Dos complicaciones hacen que predecir exactamente cuándo R aplica esta optimización sea un desafío:
- Cuando se trata de enlaces, R actualmente puede [^names-values-6] solo contar 0, 1 o muchos. Eso significa que si un objeto tiene dos enlaces y uno desaparece, el recuento de referencias no vuelve a 1: uno menos que muchos sigue siendo muchos. A su vez, esto significa que R hará copias cuando a veces no sea necesario.
- Cada vez que llama a la gran mayoría de las funciones, hace una referencia al objeto. La única excepción son las funciones C "primitivas" especialmente escritas. Estos solo pueden ser escritos por R-core y ocurren principalmente en el paquete base.
[^names-values-6]: Para cuando lea esto, es posible que esto haya cambiado, ya que hay planes en marcha para mejorar el conteo de referencias: https://developer.r-project.org/Refcnt.html
Juntas, estas dos complicaciones hacen que sea difícil predecir si se producirá o no una copia. En cambio, es mejor determinarlo empíricamente con `tracemem()`.
\index{loops!performance} \index{for loops|see {loops}} Exploremos las sutilezas con un caso de estudio usando bucles for. Los bucles for tienen la reputación de ser lentos en R, pero a menudo esa lentitud se debe a que cada iteración del bucle crea una copia. Considere el siguiente código. Resta la mediana de cada columna de un data frame grande:
```{r, cache = TRUE}
x <- data.frame(matrix(runif(5 * 1e4), ncol = 5))
medians <- vapply(x, median, numeric(1))
for (i in seq_along(medians)) {
x[[i]] <- x[[i]] - medians[[i]]
}
```
Este ciclo es sorprendentemente lento porque cada iteración del ciclo copia el data frame. Puedes ver esto usando `tracemem()`:
```{r, eval = FALSE}
cat(tracemem(x), "\n")
#> <0x7f80c429e020>
for (i in 1:5) {
x[[i]] <- x[[i]] - medians[[i]]
}
#> tracemem[0x7f80c429e020 -> 0x7f80c0c144d8]:
#> tracemem[0x7f80c0c144d8 -> 0x7f80c0c14540]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c14540 -> 0x7f80c0c145a8]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c145a8 -> 0x7f80c0c14610]:
#> tracemem[0x7f80c0c14610 -> 0x7f80c0c14678]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c14678 -> 0x7f80c0c146e0]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c146e0 -> 0x7f80c0c14748]:
#> tracemem[0x7f80c0c14748 -> 0x7f80c0c147b0]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c147b0 -> 0x7f80c0c14818]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c14818 -> 0x7f80c0c14880]:
#> tracemem[0x7f80c0c14880 -> 0x7f80c0c148e8]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c148e8 -> 0x7f80c0c14950]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c14950 -> 0x7f80c0c149b8]:
#> tracemem[0x7f80c0c149b8 -> 0x7f80c0c14a20]: [[<-.data.frame [[<-
#> tracemem[0x7f80c0c14a20 -> 0x7f80c0c14a88]: [[<-.data.frame [[<-
untracemem(x)
```
De hecho, cada iteración copia el data frame no una, ni dos, ¡sino tres veces! Se hacen dos copias con `[[.data.frame`, y se hace otra copia[^names-values-7] porque `[[.data.frame` es una función normal que incrementa el recuento de referencias de `x`.
[^names-values-7]: Estas copias son superficiales: solo copian la referencia a cada columna individual, no el contenido de las columnas. Esto significa que el rendimiento no es terrible, pero obviamente no es tan bueno como podría ser.
Podemos reducir el número de copias usando una lista en lugar de un data frame. La modificación de una lista utiliza código C interno, por lo que las referencias no se incrementan y no se realiza ninguna copia:
```{r, eval = FALSE}
y <- as.list(x)
cat(tracemem(y), "\n")
#> <0x7f80c5c3de20>
for (i in 1:5) {
y[[i]] <- y[[i]] - medians[[i]]
}
```
Si bien no es difícil determinar cuándo se realiza una copia, es difícil evitarlo. Si se encuentra recurriendo a trucos exóticos para evitar copias, puede ser hora de reescribir su función en C++, como se describe en el @sec-rcpp.
### Entornos {#sec-env-modify}
\index{reference semantics} \index{environments}
Aprenderá más sobre los entornos en el @sec-environments, pero es importante mencionarlos aquí porque su comportamiento es diferente al de otros objetos: los entornos siempre se modifican en su lugar. Esta propiedad a veces se describe como **semántica de referencia** porque cuando modifica un entorno, todos los enlaces existentes a ese entorno continúan teniendo la misma referencia.
Tome este entorno, que vinculamos a `e1` y `e2`:
```{r}
e1 <- rlang::env(a = 1, b = 2, c = 3)
e2 <- e1
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/e-modify-1.png")
```
Si cambiamos un enlace, el entorno se modifica en su lugar:
```{r}
e1$c <- 4
e2$c
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/e-modify-2.png")
```
Esta idea básica se puede utilizar para crear funciones que "recuerden" su estado anterior. Consulte la @sec-stateful-funs para obtener más detalles. Esta propiedad también se usa para implementar el sistema de programación orientado a objetos R6, el tema del @sec-r6.
Una consecuencia de esto es que los entornos pueden contenerse a sí mismos:
```{r}
e <- rlang::env()
e$self <- e
ref(e)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/e-self.png")
```
¡Esta es una propiedad única de los entornos!
### Ejercicios
1. Explique por qué el siguiente código no crea una lista circular.
```{r}
x <- list()
x[[1]] <- x
```
2. Envuelva los dos métodos para restar medianas en dos funciones, luego use el paquete `bench`[@bench] para comparar cuidadosamente sus velocidades. ¿Cómo cambia el rendimiento a medida que aumenta el número de columnas?
3. ¿Qué sucede si intenta usar `tracemem()` en un entorno?
## Desvincular y el recolector de basura. {#sec-gc}
\index{garbage collector} \index{unbinding} \index{rm()}
Considere este código:
```{r}
x <- 1:3
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/unbinding-1.png")
```
```{r}
x <- 2:4
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/unbinding-2.png")
```
```{r}
rm(x)
```
```{r, echo = FALSE, out.width = NULL}
knitr::include_graphics("diagrams/name-value/unbinding-3.png")
```
Creamos dos objetos, pero cuando finaliza el código, ninguno de los objetos está vinculado a un nombre. ¿Cómo se eliminan estos objetos? Ese es el trabajo del **recolector de basura**, o GC para abreviar. El GC libera memoria eliminando objetos R que ya no se usan y solicitando más memoria del sistema operativo si es necesario.
R utiliza un GC de **trazado**. Esto significa que rastrea todos los objetos a los que se puede acceder desde el entorno global[^names-values-8] y todos los objetos a los que, a su vez, se puede acceder desde esos objetos (es decir, las referencias en listas y entornos se buscan de forma recursiva). El recolector de elementos no utilizados no utiliza el recuento de referencias de modificación en el lugar descrito anteriormente. Si bien estas dos ideas están estrechamente relacionadas, las estructuras de datos internas están optimizadas para diferentes casos de uso.
[^names-values-8]: Y todos los entornos de la pila de llamadas actual.
El recolector de basura (GC) se ejecuta automáticamente cada vez que R necesita más memoria para crear un nuevo objeto. Mirando desde el exterior, es básicamente imposible predecir cuándo se ejecutará el GC. De hecho, ni siquiera deberías intentarlo. Si desea saber cuándo se ejecuta GC, llame a `gcinfo(TRUE)` y GC imprimirá un mensaje en la consola cada vez que se ejecute.
\index{garbage collector!gc@\texttt{gc()}} Puedes forzar la recolección de basura llamando a `gc()`. Pero a pesar de lo que hayas leído en otros lugares, nunca hay *necesidad* de llamar a `gc()` tú mismo. Las únicas razones por las que podría *querer* llamar a `gc()` es para pedirle a R que devuelva la memoria a su sistema operativo para que otros programas puedan usarla, o por el efecto secundario que le dice cuánta memoria se está usando actualmente:
```{r}
gc()
```
`lobstr::mem_used()` es un envoltorio alrededor de `gc()` que imprime el número total de bytes utilizados:
```{r}
mem_used()
```
Este número no coincidirá con la cantidad de memoria informada por su sistema operativo. Hay tres razones:
1. Incluye objetos creados por R pero no por el intérprete de R.
2. Tanto R como el sistema operativo son perezosos: no reclamarán memoria hasta que realmente se necesite. R podría estar reteniendo la memoria porque el sistema operativo aún no la ha solicitado.
3. R cuenta la memoria ocupada por objetos, pero puede haber espacios vacíos debido a objetos eliminados. Este problema se conoce como fragmentación de la memoria.
## Respuestas de la prueba {#sec-names-values-answers}
1. Debe citar nombres no sintácticos con acentos graves: `` ` ``: por ejemplo, las variables `1`, `2` y `3`.
```{r}
df <- data.frame(runif(3), runif(3))
names(df) <- c(1, 2)
df$`3` <- df$`1` + df$`2`
```
2. Ocupa unos 8 MB.
```{r}
x <- runif(1e6)
y <- list(x, x, x)
obj_size(y)
```
3. `a` se copia cuando se modifica `b`, `b[[1]] <- 10`.