Af en toe kom ik het idee tegen dat R copy-on-modify semantiekheeft, bijvoorbeeld in Devtools-wiki van Hadley.
De meeste R-objecten hebben een copy-on-modify-semantiek, dus het wijzigen van een functie
argument verandert de oorspronkelijke waarde niet
Ik kan deze term terugvinden in de R-Help mailinglijst. Peter Dalgaard schreef bijvoorbeeld in juli 2003:
R is een functionele taal, met luie evaluatie en zwakke dynamiek
typen (een variabele kan naar believen van type veranderen: a <- 1 ; a <- “a” is
toegestaan). Semantisch gezien is alles copy-on-modify, hoewel sommige
optimalisatietrucs worden gebruikt bij de implementatie om het ergste te voorkomen
inefficiënties.
Evenzo schreef Peter Dalgaard in januari 2004:
R heeft copy-on-modify semantiek (in principe en soms in
oefenen) dus als een deel van een object verandert, moet je misschien naar binnen kijken
nieuwe plaatsen voor alles wat het bevatte, inclusief mogelijk de
object zelf.
Nog verder terug, in Feb 2000Ross Ihaka zei:
We hebben behoorlijk wat werk gestoken om dit mogelijk te maken. ik zou beschrijven
de semantiek als “kopiëren bij wijzigen (indien nodig)”. Kopiëren is klaar
alleen wanneer objecten worden gewijzigd. Het (indien nodig) gedeelte betekent dat als
we kunnen bewijzen dat de wijziging geen niet-lokale
variabelen, dan gaan we gewoon door en passen we het aan zonder te kopiëren.
Het staat niet in de handleiding
Hoe hard ik ook heb gezocht, ik kan geen verwijzing naar “copy-on-modify” vinden in de R-handleidingen, noch in R Taaldefinitienoch in R Internals
Vraag
Mijn vraag bestaat uit twee delen:
- Waar is dit formeel gedocumenteerd?
- Hoe werkt kopiëren-op-wijzigen?
Is het bijvoorbeeld juist om te spreken over “pass-by-reference”, aangezien een beloftewordt doorgegeven aan de functie?
Antwoord 1, autoriteit 100%
Call-by-value
De R-taaldefinitiezegt dit (in sectie 4.3.3 Argumentevaluatie)
De semantiek van het aanroepen van een functie in het R-argument is call-by-value. In het algemeen gedragen argumenten zich alsof het lokale variabelen zijn die zijn geïnitialiseerd met de opgegeven waarde en de naam van het bijbehorende formele argument. Het wijzigen van de waarde van een opgegeven argument binnen een functie heeft geen invloed op de waarde van de variabele in het aanroepende frame. [Nadruk toegevoegd]
Hoewel dit niet het mechanisme beschrijft waarmee copy-on-modifywerkt, vermeldt het wel dat het wijzigen van een object dat aan een functie is doorgegeven, geen invloed heeft op het origineel in het aanroepende frame.
p>
Aanvullende informatie, met name over het aspect copy-on-modify, wordt gegeven in de beschrijving van SEXP
s in het R Internals-handleiding, sectie 1.1.2 Rest van header. Er staat specifiek [Nadruk toegevoegd]
Het veld
named
wordt ingesteld en geopend door deSET_NAMED
ennamed
macro’s, en neem de waarden0
,1
en2
. R heeft een ‘call by value’
illusie, dus een opdracht alsb <- a
lijkt een kopie te maken van
a
en verwijst ernaar alsb
. Echter, als
nocha
nochb
worden vervolgens gewijzigd, het is niet nodig om te kopiëren.
Wat er echt gebeurt, is dat een nieuw symboolb
aan hetzelfde is gebonden
waarde alsa
en het veldnamed
op het waardeobject is ingesteld (in dit
hoofdletters naar2
). Wanneer een object op het punt staat te worden gewijzigd, wordt het veldnamed
wordt geraadpleegd. Een waarde van2
betekent dat het object moet worden gedupliceerd
alvorens te worden gewijzigd. (Merk op dat dit niet zegt dat het zo is)
nodig om te dupliceren, alleen dat het moet worden gedupliceerd of
noodzakelijk of niet.) Een waarde van0
betekent dat het bekend is dat geen andere
SEXP
deelt gegevens met dit object en kan dus veilig worden gewijzigd.
Een waarde van1
wordt gebruikt voor situaties zoalsdim(a) <- c(7, 2)
waar in principe twee exemplaren van a bestaan voor de duur van de
berekening als (in principe)a <- `dim<-`(a, c(7, 2))
maar niet langer, en dus kunnen sommige primitieve functies worden geoptimaliseerd om
vermijd in dit geval een kopie.
Hoewel dit niet de situatie beschrijft waarin objecten worden doorgegeven aan functies als argumenten, zouden we kunnen afleiden dat hetzelfde proces werkt, vooral gezien de informatie uit de eerder geciteerde R Language-definitie.
Beloftes in functie-evaluatie
Ik denk niet dat het helemaal correct is om te zeggen dat een beloftewordt doorgegevenaan de functie. De argumenten worden doorgegeven aan de functie en de daadwerkelijk gebruikte expressies worden opgeslagen als beloften (plus een verwijzing naar de aanroepende omgeving). Alleen wanneer een argument wordt geëvalueerd, wordt de uitdrukking die is opgeslagen in de belofte opgehaald en geëvalueerd binnen de omgeving die wordt aangegeven door de aanwijzer, een proces dat bekend staat als forceren.
Als zodanig geloof ik niet dat het in dit opzicht correct is om over pass-by-referencete praten. R heeft call-by-value-semantiek, maar probeert kopiëren te vermijden, tenzij een waarde die aan een argument is doorgegeven, wordt geëvalueerd en gewijzigd.
Het NAMED-mechanisme is een optimalisatie (zoals opgemerkt door @hadley in de opmerkingen) waarmee R kan volgen of er een kopie moet worden gemaakt bij wijziging. Er zijn enkele subtiliteiten betrokken bij de exacte werking van het NAMED-mechanisme, zoals besproken door Peter Dalgaard (in de R Devel-thread@mnel citeert in hun commentaar op de vraag)
Antwoord 2, autoriteit 55%
Ik heb er wat experimenten mee gedaan en ontdekte dat R altijd het object kopieert onder de eerste wijziging.
Je kunt het resultaat op mijn computer zien in http://rpubs.com/wush978/5916
Laat het me weten als ik een fout heb gemaakt, bedankt.
Te testen of een object is gekopieerd of niet
Ik dump het geheugenadres met de volgende C-code:
#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;
}
Er worden 2 adressen afgedrukt:
- Het adres van het gegevensblok van
SEXP
- Het adres van een doorlopend blok van
integer
Laten we deze C-functie compileren en laden.
Rcpp:::SHLIB("dump_address.c")
dyn.load("dump_address.so")
Sessie-info
Hier is de sessionInfo
van de testomgeving.
sessionInfo()
Kopiëren op schrijven
Eerst test ik de eigenschap van copy on write,
wat betekent dat R het object alleen kopieert als het wordt gewijzigd.
a <- 1L
b <- a
invisible(.Call("dump_address", a))
invisible(.Call("dump_address", b))
b <- b + 1
invisible(.Call("dump_address", b))
Het object b
kopieert van a
bij de wijziging. R implementeert de eigenschap copy on write
.
Wijzig vector/matrix op zijn plaats
Vervolgens test ik of R het object zal kopiëren wanneer we een element van een vector/matrix wijzigen.
Vector met lengte 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))
Het adres verandert elke keer, wat betekent dat R het geheugen niet opnieuw gebruikt.
Lange vector
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))
Voor long vector, R hergebruik het geheugen na de eerste wijziging.
Bovendien laat het bovenstaande voorbeeld ook zien dat “op zijn plaats wijzigen” de prestaties beïnvloedt wanneer het object enorm is.
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))
Het lijkt erop dat R het object alleen bij de eerste wijzigingen kopieert.
Ik weet niet waarom.
Kenmerk wijzigen
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))
Het resultaat is hetzelfde. R kopieert het object alleen bij de eerste wijziging.