Struct vs class en Swift

7

Una de las razones que indican que las estructuras pueden ser más eficaces que las clases es que no se necesita ARC para las estructuras. Pero supongamos que tenemos la siguiente estructura rápida:

struct Point {
   var x:Float
   var y:Float
   mutating func scale(_ a:Float){
      x *= a
      y *= a
   } 
}

var p1 = Point(x:1, y:1)
var p2 = p1 //p1 and p2 point to the same data internally
p1.scale(2) //copy on mutate, p1 and p2 now have distinct copies

Ahora creo que varias copias de una estructura apuntarán a los mismos datos en la pila hasta que una mutación obligue a una copia. Esto significa que debemos hacer un seguimiento de cuántos objetos hacen referencia a una dirección de memoria determinada en la pila para saber si una mutación debe formar una copia, o simplemente formar una copia en cada mutación. Lo primero parece ineficiente y lo segundo parece idéntico a ARC. ¿Qué me estoy perdiendo?

EDITAR: entiendo la diferencia entre la semántica del valor y la semántica de referencia. Pero como un swift de optimización no crea realmente una nueva copia de los datos hasta que se realiza una mutación. Esto evita copias innecesarias. Consulte enlace . Pero debido a esto, no estoy seguro de cómo pueden evitar algún tipo de ARC. Noté que este artículo afirma que la copia en escritura solo se realiza para matrices y diccionarios. No estoy seguro de si eso es cierto, pero si es así, entonces mis preguntas todavía representan matrices.

    
pregunta gloo 09.04.2017 - 18:12

4 respuestas

2

La estructura rápida solo necesita saber si sus datos tienen una referencia única o no, y no necesitan un ARC completo. Por ejemplo, en la línea

var p2 = p1

en teoría, el compilador podría simplemente voltear un indicador de p1 con una referencia única de off a on. Entonces, cuando se hace una escritura, el compilador sabe copiar.

Supongamos que p2 se desasigna antes de que esto suceda. Ahora, la copia es redundante, pero el compilador no sabe, por lo que todavía hace la copia. Es por eso que el enfoque de referencia única no es una aproximación perfecta, en realidad cae entre ARC completo y siempre se copia.

Nota: el compilador swift en realidad no funciona de esta manera, basado en el comentario de @ Alexander y en esta respuesta , el comportamiento de copia en escritura se implementa en código swift, no como una optimización de compilador. En las estructuras definidas por el usuario, simplemente se copia cada vez.

En cuanto a su punto sobre la eficiencia: sí, técnicamente copiar cada vez es menos eficiente. Cuando tienes un montón de comportamientos de copia y mutación, en algún momento se vuelve más eficiente simplemente para cambiar de clase. Es por eso que Swift te ofrece ambas opciones.

    
respondido por el JSquared 06.01.2018 - 01:16
3

El enlace de @ amon es una respuesta completa. Para quote < a>

  

Un tipo de valor es un tipo cuyo valor se copia cuando se asigna a una variable o constante, o cuando se pasa a una función.

Por contraste para las clases

  

A diferencia de los tipos de valor, los tipos de referencia no se copian cuando se asignan a una variable o constante, o cuando se pasan a una función. En lugar de una copia, en su lugar se utiliza una referencia a la misma instancia existente.

La semántica copia por valor de los tipos de estructura tiende a hacer que los usuarios se familiaricen con los idiomas que son solo / principalmente copia por referencia. Para comprender realmente este comportamiento, vale la pena mirar a C (ya que Swift está fuertemente conectado a Objective-C, construido en C). Los conceptos básicos se encuentran en la referencia de C .

  

objeto : región de almacenamiento de datos en el entorno de ejecución, cuyo contenido puede representar valores.

     

Un tipo de estructura describe un conjunto de objetos miembros no vacíos asignados secuencialmente (y, en ciertas circunstancias, una matriz incompleta), cada uno de los cuales tiene un nombre opcionalmente especificado y posiblemente un tipo distinto.

     

En asignación simple (=), el valor del operando derecho se convierte al tipo de la expresión de asignación y reemplaza el valor almacenado en el objeto designado por el operando izquierdo

Poner estas tres definiciones juntas nos dice por qué C struct s no es copia en escritura. Si tenemos en C

struct foo {};

bar() {
    struct foo a = <whatever>;
    struct foo b = <whatever>;
    b = a;
}

En este caso, a y b son valores en regiones de almacenamiento de datos (nota: no se trata de referencias o punteros a esas regiones sino a los valores en esas regiones). La expresión b = a reemplaza el valor almacenado en la región de b con el de la región de a , es decir, el valor de a se "copia" y se asigna a b .

Por lo tanto, el comportamiento de C aquí es un resultado natural de su especificación y modelo de máquina abstracto (que fue diseñado para ser muy susceptible a una implementación eficiente en hardware estándar). El objetivo C heredó este comportamiento y Swift está realizando un seguimiento del objetivo C.

    
respondido por el walpen 09.04.2017 - 20:10
3

Estás empezando con un error. Es una estructura. Las estructuras son tipos de valor. La asignación a p2 crea una copia. p1 y p2 no apuntan a ningún lado.

Esa es exactamente la diferencia entre estructura y clase. Las clases son tipos de referencia. Si tuviera una clase, la asignación a p2 solo crearía una segunda referencia al mismo objeto al que apunta p1 (y contaría la referencia).

Está diciendo: "No hay forma en que los tipos de valor puedan hacer COW sin usar algún direccionamiento indirecto del puntero, lo que anula el propósito de los tipos de valor". El propósito de los tipos de valor es la semántica de los tipos de valor. Los detalles de la implementación no importan.

Pero tenemos que ir más allá. Cuando copia una estructura que contiene dos Flotantes, los dos Flotantes se copian. Cuando copia una estructura que contiene dos referencias a objetos, las referencias de objeto se copian, por lo que ahora tiene dos estructuras, que tienen referencias a los mismos objetos. Si reemplaza una referencia en la copia, esa estructura original no se modifica. Si modifica un objeto utilizando la referencia en la copia, ese es el mismo objeto al que hace referencia el original, por lo que el objeto al que hace referencia la estructura original, que es el mismo objeto, también ha cambiado.

Las matrices son estructuras, pero la estructura contiene una referencia a un objeto que contiene los datos reales (eso es un detalle de implementación de la implementación de matrices de la biblioteca Swift). Esa referencia se copia, por lo que en este punto la matriz copiada se refiere exactamente al mismo objeto de datos. Eso está bien siempre y cuando nadie intente modificar la matriz. Cuando se modifica ese objeto de datos, el elemento de acceso comprueba cuántas referencias al objeto de datos hay, y si hay más de una, entonces se realiza una copia del objeto de datos en ese momento.

El comportamiento observable de las matrices es como si todos los elementos se copiaran cuando se copia la estructura de matriz. Pero la implementación es mucho más rápida porque algunos códigos inteligentes solo en realidad copian los datos cuando es necesario. Si desea la misma optimización para sus propias estructuras, debe escribir el código para hacerlo. No tiene nada que ver con el compilador, todo es parte de la implementación de la biblioteca Swift de una matriz.

    
respondido por el gnasher729 09.04.2017 - 20:36
-1

Tenga en cuenta que COW / copy-on-write es una optimización. Para las estructuras (¿diminutas?), El optimizador del compilador puede determinar que simplemente copiar todos los datos de la estructura es más pequeño y / o más rápido que insertar y / o ejecutar código COW o ARC. Sin embargo, la semántica de los objetos de clase no permite esto.

Respuesta anterior:

Con las estructuras COW, debe copiar una estructura en la memoria justo antes de la primera vez que la modifique después de obtenerla o después de pasarla a otra persona. Eso es un indicador de 1 bit por estructura por usuario y una posible copia de una sola vez por usuario, que se puede optimizar en algunos casos. Después de eso, los datos en la estructura están en su memoria privada y nadie más puede verlos o cambiarlos debajo de usted.

Cualquier otra persona que intente modificar esa estructura debajo de usted debería haber hecho primero su propia copia privada y, en su lugar, haberla modificado, invisible para usted.

Si nadie escribe, no es necesario realizar copias ni rastrear (aparte de un indicador "limpio" de 1 bit por usuario, que puede o no optimizarse). Incluso si la marca de 1 bit por usuario no se puede optimizar, es mucho más simple que el conteo / seguimiento de referencias.

    
respondido por el hotpaw2 15.04.2017 - 22:56

Lea otras preguntas en las etiquetas

Comentarios Recientes

A partir de ahora, puede referirse a las estructuras Swift como decidibles, de hecho, todo su código Swift sin ningún calificador puede tratarse de esta manera. Un const utilizó una función de mapeo para devolver el valor y def consumió el valor de retorno sin verificarlo. Pero siéntase libre de mejorar la definición de las funciones que usará en este capítulo. Reducción de tipos serializados En lo que respecta al escape de tipo, el tipo SerializedBytes no se puede declarar por tanto tiempo: la clase System.Serialization.Serialize... Lee mas