Was genau ist die copy-on-modify-Semantik in R, und wo ist die kanonische Quelle?

Hin und wieder stoße ich auf die Vorstellung, dass R eine copy-on-modify-Semantik hat , zum Beispiel in Hadleys Devtools-Wiki .

Die meisten R-Objekte haben eine Semantik zum Kopieren und Ändern, so dass das Ändern eines functionsarguments den ursprünglichen Wert nicht ändert

Ich kann diesen Begriff auf die R-Help-Mailingliste zurückverfolgen. Zum Beispiel schrieb Peter Dalgaard im Juli 2003 :

R ist eine funktionale Sprache mit Lazy-Evaluierung und schwacher dynamischer Typisierung (eine Variable kann den Typ beliebig ändern: a <- 1; a <- "a" ist erlaubt). Semantisch ist alles copy-on-modify, obwohl einige Optimierungstricks in der Implementierung verwendet werden, um die schlimmsten Ineffizienzen zu vermeiden.

Ähnlich schrieb Peter Dalgaard im Januar 2004 :

R hat eine Semantik zum Kopieren und Verändern (im Prinzip und manchmal in der Praxis). Wenn sich also ein Teil eines Objekts ändert, müssen Sie möglicherweise nach neuen Stellen suchen, einschließlich des Objekts selbst.

Noch weiter zurück, im Februar 2000 sagte Ross Ihaka:

Wir haben einiges an Arbeit investiert, um dies möglich zu machen. Ich würde die Semantik als “Kopie bei Änderung (falls erforderlich)” beschreiben. Das Kopieren wird nur durchgeführt, wenn Objekte geändert werden. Der Teil (falls erforderlich) bedeutet, dass wir, wenn wir beweisen können, dass die Änderung keine nicht lokalen Variablen ändern kann, einfach weitermachen und ohne Kopieren modifizieren.

Es ist nicht im Handbuch

Egal wie sehr ich gesucht habe, ich kann keinen Verweis auf “copy-on-modify” in den R-Handbüchern finden , weder in R Language Definition noch in R Internals

Frage

Meine Frage besteht aus zwei Teilen:

  1. Wo ist das formell dokumentiert?
  2. Wie funktioniert das Kopieren bei Änderung?

Ist es zum Beispiel richtig, von “pass-by-reference” zu sprechen, da ein Versprechen an die function weitergegeben wird?

Call-by-Value

Die R Sprachdefinition sagt dies (in Abschnitt 4.3.3 Argument Evaluation )

Die Semantik zum Aufrufen einer function in R Argument ist call-by-value . Im Allgemeinen verhalten sich die übergebenen Argumente so, als wären sie lokale Variablen, die mit dem angegebenen Wert und dem Namen des entsprechenden formalen Arguments initialisiert werden. Wenn Sie den Wert eines übergebenen Arguments in einer function ändern, wirkt sich dies nicht auf den Wert der Variablen im aufrufenden Rahmen aus . [Betonung hinzugefügt]

Obwohl dies nicht den Mechanismus beschreibt, mit dem copy-on-modify funktioniert, wird erwähnt, dass das Ändern eines an eine function übergebenen Objekts das Original im aufrufenden Frame nicht beeinflusst.

Weitere Informationen, insbesondere zum Aspekt ” Copy-on-Modify”, finden Sie in der Beschreibung von SEXP s im Handbuch R Internals , Abschnitt 1.1.2 Rest of Header . Insbesondere heißt es [Hervorhebung hinzugefügt]

Das named Feld wird von den Makros SET_NAMED und NAMED SET_NAMED und NAMED und nimmt die Werte 0 , 1 und 2 . R hat eine “call by value” Illusion, also eine Aufgabe wie

 b < - a 

scheint eine Kopie von a zu machen und es als b . Wenn jedoch weder a noch b nachträglich geändert werden, muss nicht kopiert werden. Was wirklich passiert, ist, dass ein neues Symbol b an denselben Wert wie a gebunden wird und das named Feld auf dem Wertobjekt gesetzt wird (in diesem Fall auf 2 ). Wenn ein Objekt geändert werden soll, wird das named Feld konsultiert. Ein Wert von 2 bedeutet, dass das Objekt vor der Änderung dupliziert werden muss. (Beachten Sie, dass dies nicht besagt, dass es notwendig ist, zu duplizieren, nur dass es dupliziert werden sollte, falls notwendig oder nicht.) Ein Wert von 0 bedeutet, dass kein anderer SEXP Daten mit diesem Objekt teilt, und so kann es sicher sein Verändert werden. Ein Wert von 1 wird für Situationen wie verwendet

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

wo prinzipiell zwei Kopien eines für die Dauer der Berechnung als (im Prinzip) existieren

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

aber nicht mehr, und so können einige primitive functionen optimiert werden, um in diesem Fall eine Kopie zu vermeiden.

Obwohl dies nicht die Situation beschreibt, in der Objekte als Argumente an functionen übergeben werden, können wir daraus schließen, dass derselbe process funktioniert, insbesondere angesichts der Informationen aus der zuvor zitierten R-Sprachdefinition.

Versprechen bei der functionsauswertung

Ich glaube nicht, dass es richtig ist zu sagen, dass ein Versprechen an die function weitergegeben wird. Die Argumente werden an die function übergeben und die tatsächlich verwendeten Ausdrücke werden als Versprechen gespeichert (plus einen pointers auf die aufrufende Umgebung). Nur wenn ein Argument ausgewertet wird, wird der Ausdruck, der in der Zusage gespeichert ist, in der durch den pointers angegebenen Umgebung abgerufen und ausgewertet, ein Vorgang, der als Forcen bekannt ist .

Daher glaube ich nicht, dass es richtig ist, in diesem Zusammenhang über Pass-by-Reference zu sprechen. R hat eine call-by-value- Semantik, versucht jedoch, das Kopieren zu vermeiden, es sei denn, ein an ein Argument übergebener Wert wird ausgewertet und modifiziert.

Der NAMED-Mechanismus ist eine Optimierung (wie von @hadley in den Kommentaren angemerkt), die es R ermöglicht zu verfolgen, ob eine Kopie bei der Modifikation erstellt werden muss. Es gibt einige Feinheiten, die genau damit verbunden sind, wie der NAMED-Mechanismus funktioniert, wie von Peter Dalgaard diskutiert (im R Devel-Thread @ mnel zitiert in ihrem Kommentar zu der Frage).

Ich habe einige Experimente daran gemacht und festgestellt, dass R das Objekt immer unter der ersten Modifikation kopiert.

Sie können das Ergebnis auf meinem Computer in http://rpubs.com/wush978/5916 sehen

Bitte lassen Sie mich wissen, wenn ich einen Fehler gemacht habe, danke.


Um zu testen, ob ein Objekt kopiert wurde oder nicht

Ich speichere die Speicheradresse mit dem folgenden C-Code:

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

Es wird 2 Adressen gedruckt:

  • Die Adresse des Datenblocks von SEXP
  • Die Adresse des fortlaufenden integer

Lassen Sie uns diese C-function kompilieren und laden.

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

Sitzungsinfo

Hier ist die sessionInfo der Testumgebung.

 sessionInfo() 

Kopieren beim Schreiben

Zuerst teste ich die Eigenschaft der Kopie beim Schreiben , was bedeutet, dass R das Objekt nur dann kopiert, wenn es geändert wird.

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

Das Objekt b kopiert von a bei der Modifikation. R implementiert die Eigenschaft copy on write .

Ändern Sie Vektor / Matrix an Ort und Stelle

Dann teste ich, ob R das Objekt kopiert, wenn wir ein Element eines Vektors / einer Matrix modifizieren.

Vektor mit der Länge 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)) 

Die Adresse ändert sich jedes Mal, was bedeutet, dass R den Speicher nicht wiederverwenden kann.

Langer Vektor

 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)) 

Bei langem Vektor verwendet R den Speicher nach der ersten Modifikation.

Darüber hinaus zeigt das obige Beispiel auch, dass "vor Ort modifizieren" die performance beeinträchtigt, wenn das Objekt sehr groß ist.

Matrix

 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)) 

Es scheint, dass R das Objekt nur bei den ersten Modifikationen kopiert.

Ich weiß nicht warum.

Attribut ändern

 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)) 

Das Ergebnis ist das gleiche. R kopiert das Objekt nur bei der ersten Änderung.