Je sais que Swift optimisera la copie lors de l'écriture pour les tableaux, mais le fera-t-il pour toutes les structures? Par exemple:
struct Point {
var x:Float = 0
}
var p1 = Point()
var p2 = p1 //p1 and p2 share the same data under the hood
p2.x += 1 //p2 now has its own copy of the data
Array
est implémenté avec un comportement de copie sur écriture - vous l'obtiendrez indépendamment de toutes les optimisations du compilateur (bien sûr, optimisations peut réduire le nombre de cas où une copie doit se produire).
À un niveau de base, Array
est juste une structure qui contient une référence à un tampon alloué en tas contenant les éléments - par conséquent, plusieurs instances Array
peuvent référencer les même tampon. Lorsque vous venez de muter une instance de tableau donnée, l'implémentation vérifie si le tampon est référencé de manière unique, et si c'est le cas, mute directement. Sinon, le tableau effectuera une copie du tampon sous-jacent afin de préserver la sémantique des valeurs.
Cependant, avec votre structure Point
- vous n'implémentez pas la copie sur écriture au niveau de la langue. Bien sûr, comme @ Alexander dit , cela n'empêche pas le compilateur d'effectuer toutes sortes d'optimisations pour minimiser le coût de copie de structures entières. Ces optimisations n'ont pas besoin de suivre le comportement exact de la copie sur écriture - le compilateur est tout simplement libre de faire ce qu'il souhaite , tant que le le programme s'exécute selon la spécification du langage.
Dans votre exemple spécifique, p1
Et p2
Sont globaux, par conséquent le compilateur doit en faire des instances distinctes, car d'autres fichiers .Swift du même module y ont accès (bien que cela puisse potentiellement être optimisé avec l'optimisation du module entier). Cependant, le compilateur n'a toujours pas besoin de copier les instances - il peut simplement évaluer l'addition à virgule flottante au moment de la compilation et initialiser l'un des globaux avec 0.0
, Et le autre avec 1.0
.
Et s'il s'agissait de variables locales dans une fonction, par exemple:
struct Point {
var x: Float = 0
}
func foo() {
var p1 = Point()
var p2 = p1
p2.x += 1
print(p2.x)
}
foo()
Le compilateur n'a même pas besoin de créer deux instances de Point
pour commencer - il peut simplement créer une seule variable locale à virgule flottante initialisée à 1.0
, Et l'imprimer.
En ce qui concerne le passage de types de valeur comme arguments de fonction, pour les types assez grands et (dans le cas des structures) les fonctions qui utilisent suffisamment de leurs propriétés, le compilateur peut les transmettre par référence plutôt que de copier. L'appelé ne peut ensuite en faire une copie qu'en cas de besoin, par exemple lorsqu'il doit travailler avec une copie modifiable.
Dans d'autres cas où les structures sont passées par valeur, il est également possible pour le compilateur de fonctions spécialisées afin de copier uniquement sur les propriétés dont la fonction a besoin.
Pour le code suivant:
struct Point {
var x: Float = 0
var y: Float = 1
}
func foo(p: Point) {
print(p.x)
}
var p1 = Point()
foo(p: p1)
En supposant que foo(p:)
n'est pas inséré par le compilateur (il le fera dans cet exemple, mais une fois que son implémentation atteindra une certaine taille, le compilateur ne pensera pas que cela en vaut la peine) - le compilateur peut spécialiser la fonction comme:
func foo(px: Float) {
print(px)
}
foo(px: 0)
Il transmet uniquement la valeur de la propriété Point
de x
à la fonction, ce qui permet d'économiser le coût de la copie de la propriété y
.
Le compilateur fera donc tout ce qu'il peut pour réduire la copie des types de valeurs. Mais avec autant d'optimisations diverses dans des circonstances différentes, vous ne pouvez pas simplement réduire le comportement optimisé des types de valeurs arbitraires à une simple copie sur écriture.