72 votes

En quoi consiste exactement la sémantique de copie sur modification dans R et où se trouve la source canonique?

Une fois de temps en temps, je tombe sur la notion que la R a copie sur modifier la sémantique, par exemple dans Hadley du devtools wiki.

La plupart des objets R de la fonction de copier-sur-modifier la sémantique, de sorte que la modification d'une fonction l'argument ne change pas la valeur d'origine

Je peux tracer ce terme de retour à la R-Aider à la liste de diffusion. Par exemple, Peter Dalgaard a écrit en juillet 2003:

R est un langage fonctionnel, avec lazy de l'évaluation et de la faiblesse de la dynamique de frappe (une variable peut changer de type à volonté: a <- 1 ; a <- "a" est autorisé). Sémantiquement, tout est copie sur modifier bien que certains l'optimisation des trucs sont utilisés dans la mise en œuvre pour éviter le pire les inefficacités.

De même, Peter Dalgaard a écrit dans Jan 2004:

R a copie sur modifier la sémantique (en principe, et parfois dans la pratique) donc, une fois que la partie d'un objet change, vous pourriez avoir à regarder dans de nouveaux endroits pour tout ce qu'il contenait, y compris, éventuellement, la objet lui-même.

Encore plus en arrière, en Février 2000 Ross Ihaka dit:

Nous avons mis un peu de travail dans la réalisation de cet objectif. Je décrirais la sémantique comme "copie sur modifier (si nécessaire)". La copie est fait seulement, lorsque les objets sont modifiés. L' (si nécessaire) la partie qui signifie que si nous pouvons prouver que la modification ne peut pas changer les non-local variables, alors nous avons simplement aller de l'avant et de le modifier sans avoir à les copier.

Il n'est pas dans le manuel

N'importe comment dur j'ai cherché, je ne peux pas trouver une référence à "copier-sur-modifier" dans la R manuels, ni dans la R de Définition de Langage , ni dans R Internals

Question

Ma question est en deux parties:

  1. Où est-ce que formellement documentés?
  2. Comment copier-sur-modifier le travail?

Par exemple, est-il correct de parler de "par référence", car une promesse est transmis à la fonction?

43voto

Gavin Simpson Points 72349

Appel-par-valeur

Le R de Définition de Langage dit ceci (à la section 4.3.3 de l'Argument de l'Évaluation)

La sémantique de l'appel d'une fonction dans R sont en appel par valeur. En général, les arguments fournis se comportent comme s'ils sont des variables locales initialisé avec la valeur fournie et le nom de l'argument formel correspondant. La modification de la valeur d'un argument fourni à l'intérieur d'une fonction n'affecte pas la valeur de la variable dans le cadre de l'appel. [Italiques ajoutés]

Alors que ce n'est pas de décrire le mécanisme par lequel la copie sur modifier œuvres, il ne mentionne que la modification d'un objet passé à une fonction n'affecte pas l'original dans le cadre de l'appel.

Des renseignements supplémentaires, notamment sur la copie sur modifier l'aspect sont donnés dans la description de l' SEXPs dans la R Internes manuel, section 1.1.2 Reste de l'en-Tête. Spécifiquement, les états [Emphase ajoutée]

L' named champ est configuré et accédé par l' SET_NAMED et NAMED les macros, et de prendre des valeurs 0, 1 et 2. R a un "appel par valeur" illusion, si une assignation comme

b <- a

s'ouvre pour faire une copie de a et en parler comme d' b. Toutefois, si ni a ni b sont par la suite modifié il n'y a pas besoin de la copier. Ce qui se passe réellement est que un nouveau symbole b est lié à la même valeur a et de la named sur le terrain sur la valeur de l'objet est défini (dans ce cas 2). Lorsqu'un objet est sur le point d'être modifiée, l' namedchamp est consulté. Une valeur de 2 signifie que l'objet doit être dupliqué avant d'être changé. (Notez que cela ne veut pas dire que c'est nécessaire de dupliquer, mais seulement qu'il doit être doublé si nécessaire ou pas.) Une valeur de 0 signifie qu'il est connu qu'aucun autre SEXP des actions de données avec cet objet, et donc, il peut en toute sécurité être modifié. Une valeur de 1 est utilisé pour les situations comme

dim(a) <- c(7, 2)

lorsque, en principe, deux copies d'un existent pour la durée de la calcul (en principe)

a <- `dim<-`(a, c(7, 2))

mais pas plus, et certaines fonctions primitives peuvent être optimisés pour éviter une copie dans ce cas.

Bien que cela ne décrivent pas la situation dans laquelle les objets sont passés à des fonctions comme arguments, nous pourrions en déduire que le même processus fonctionne, surtout compte tenu de l'information de la R la Langue de la définition citée plus haut.

Des promesses en l'évaluation de la fonction

Je ne pense pas que c'est tout à fait correct de dire qu'une promesse est passé à la fonction. Les arguments sont passés à la fonction et les expressions réelles utilisées sont stockées comme des promesses (ainsi qu'un pointeur vers la vocation de l'environnement). Seulement quand un argument obtient évalué est l'expression stockées dans la promesse récupéré et évalué au sein de l'environnement a indiqué par le pointeur, un processus connu sous le nom de forcer.

En tant que tel, je ne crois pas qu'il est correct de parler par référence à cet égard. R a appel-par-valeur sémantique, mais essaie d'éviter la copie, à moins qu'une valeur passée à un argument est évalué et modifié.

Le NOMMÉ mécanisme est une optimisation (comme indiqué par @hadley dans les commentaires) qui permet de R de savoir si une copie doit être faite lors de la modification. Il y a quelques subtilités impliqué avec exactement comment le NOMMÉ mécanisme fonctionne, comme expliqué par Pierre Dalgaard (dans le R Devel fil @mnel cite dans son commentaire à la question)

26voto

wush978 Points 824

J'ai fait des expériences sur elle, et a découvert que R toujours des copies de l'objet en vertu de la première modification.

Vous pouvez voir le résultat sur ma machine en http://rpubs.com/wush978/5916

S'il vous plaît laissez-moi savoir si j'ai fait une erreur, merci.


Pour tester si un objet est copié ou pas

J'ai un dump de la mémoire d'adresse avec les éléments suivants du code C:

#define USE_RINTERNALS
#include <R.h>
#include <Rdefines.h>

SEXP dump_address(SEXP src) {
  Rprintf("%16p %16p %d\n", &(src->u), INTEGER(src), INTEGER(src) - (int*)&(src->u));
  return R_NilValue;
}

Il permet d'imprimer 2 adresse:

  • L'adresse de bloc de données d' SEXP
  • L'adresse de bloc continu de l' integer

Nous allons compiler et charger ce C de la fonction.

Rcpp:::SHLIB("dump_address.c")
dyn.load("dump_address.so")

Session D'Info

Voici l' sessionInfo de l'environnement de test.

sessionInfo()

La copie sur Écriture

J'ai d'abord tester la propriété de la copie sur écriture, ce qui signifie que R seule copie de l'objet uniquement lorsqu'il est modifié.

a <- 1L
b <- a
invisible(.Call("dump_address", a))
invisible(.Call("dump_address", b))
b <- b + 1
invisible(.Call("dump_address", b))

L'objet b des copies à partir d' a à la modification. R ne mettre en œuvre l' copy on write de la propriété.

Modifier vecteur/matrice en place

Puis-je tester si R copie de l'objet lorsque l'on modifie un élément d'un vecteur/matrice.

D'un vecteur de longueur 1

a <- 1L
invisible(.Call("dump_address", a))
a <- 1L
invisible(.Call("dump_address", a))
a[1] <- 1L
invisible(.Call("dump_address", a))
a <- 2L 
invisible(.Call("dump_address", a))

Les changements d'adresse à chaque fois ce qui signifie que R ne pas réutiliser la mémoire.

Long de vecteur

system.time(a <- rep(1L, 10^7))
invisible(.Call("dump_address", a))
system.time(a[1] <- 1L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 1L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 2L)
invisible(.Call("dump_address", a))

Pendant de longues vecteur, R la réutilisation de la mémoire après la première modification.

En outre, l'exemple ci-dessus montre également que "modifier en place" ne nuire à la performance lorsque l'objet est énorme.

La matrice

system.time(a <- matrix(0L, 3162, 3162))
invisible(.Call("dump_address", a))
system.time(a[1,1] <- 0L)
invisible(.Call("dump_address", a))
system.time(a[1,1] <- 1L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 2L)
invisible(.Call("dump_address", a))
system.time(a[1] <- 2L)
invisible(.Call("dump_address", a))

Il semble que la R des copies de l'objet lors de la première des modifications seulement.

Je ne sais pas pourquoi.

Modification d'attribut

system.time(a <- vector("integer", 10^2))
invisible(.Call("dump_address", a))
system.time(names(a) <- paste(1:(10^2)))
invisible(.Call("dump_address", a))
system.time(names(a) <- paste(1:(10^2)))
invisible(.Call("dump_address", a))
system.time(names(a) <- paste(1:(10^2) + 1))
invisible(.Call("dump_address", a))

Le résultat est le même. R seules les copies de l'objet lors de la première modification.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X