Gestión avanzada de memoria en R: tracemem (II)

He leído estos días el capítulo 14 de The Art of R Programming que trata problemas y trucos para mejorar el rendimiento de R en términos de velocidad y memoria. Menciona la función tracemem de la que nos ocupamos el otro día.

Menciona el capítulo cómo uno de los estranguladores del rendimiento de R es su política de copiar al cambiar (copy-on-change). Generalmente, cuando modificamos un objeto, R realiza una copia íntegra de él (¿y qué pasa si realizamos pequeñas modificaciones en un objeto muy grande?):

1
2
3
4
5
m <- 1:10
tracemem(m)
# [1] "<0x16952c0>"
m[1] <- 8
# tracemem[0x16952c0 -> 0x10cd228]:

Sin embargo el libro menciona cómo, a pesar de la política copiar al cambiar, hay casos en los que R es lo suficientemente inteligente como para modificar sólo la parte afectada por el cambio:

1
2
3
4
5
6
z <- runif(10)
tracemem(z)
# [1] "<0x1044ff0>"
z[1] <- 8
tracemem(z)
# [1] "<0x1044ff0>"

En este caso, no se copia el objeto: sólo se modifica una de las entradas del mismo.

Pero, ¿por qué en este segundo ejemplo no hay copia y el en primero sí? El motivo es el tipo de almacenamiento interno de R:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
m <- 1:10
typeof(m)
# [1] "integer"
tracemem(m)
# [1] "<0x14e83f8>"
m[1] <- 8
# tracemem[0x14e83f8 -> 0x14e8450]:
# tracemem[0x14e8450 -> 0x1045140]:
typeof(m)
# [1] "double"

Efectivamente, hay una copia, pero precisamente porque la asignación implica un cambio (implícito) de manera de almacenar datos. Aunque se podría hacer también

1
2
3
4
m <- 1:10
tracemem(m)
# [1] "<0x14e8558>"
m[1] <- 8L

para evitar la copia.