Struct vs class in Swift

7

Uno dei motivi per cui le strutture possono essere più performanti delle classi è che non è necessario ARC per le strutture. Ma supponiamo di avere la seguente struttura in swift:

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

Ora credo che varie copie di una struttura puntino effettivamente agli stessi dati nello stack finché una mutazione non forza una copia. Ciò significa che dobbiamo tenere traccia del numero di oggetti che fanno riferimento a un dato indirizzo di memoria nello stack in modo da sapere se una mutazione deve formare una copia, o semplicemente formiamo una copia su ogni mutazione. Il primo sembra inefficiente e il secondo sembra identico a ARC. Cosa mi manca?

EDIT: Capisco la differenza tra semantica del valore e semantica di riferimento. Ma come una rapida ottimizzazione non crea una nuova copia dei dati fino a quando non viene effettuata una mutazione. Questo evita copie non necessarie. Vedi link . Ma per questo non sono sicuro di come possano evitare una qualche forma di ARC. Ho notato che questo articolo afferma che la copia su scrittura è fatta solo per array e dizionari. Non sono sicuro che sia vero, ma se così fosse, le mie domande rimarranno gli array.

    
posta gloo 09.04.2017 - 18:12
fonte

4 risposte

2

La struttura rapida ha solo bisogno di sapere se i dati sono referenziati in modo univoco o meno e non necessitano di un ARC completo. Ad esempio, sulla linea

var p2 = p1

il compilatore teoricamente potrebbe semplicemente capovolgere un flag di p1 in modo univoco da off a on. Quindi, quando viene effettuata una scrittura, il compilatore sa di copiare.

Supponiamo che p2 sia deallocato prima che ciò accada. Ora, la copia è ridondante, ma il compilatore non lo sa, quindi fa ancora la copia. Ecco perché l'approccio con riferimenti unici non è un'approssimazione perfetta, in realtà cade tra ARC completo e copia sempre.

Nota: il compilatore rapido non funziona in questo modo - basato sul commento di @ Alexander e questa risposta , il comportamento copy on write è implementato nel codice swift, non come ottimizzazione del compilatore. Sulle strutture definite dall'utente, copia semplicemente ogni volta.

Riguardo al tuo punto sull'efficienza: sì, tecnicamente la copia ogni volta è meno efficiente. Quando si ha un sacco di comportamenti di copia e di mutazione, a un certo punto diventa più efficiente solo passare alle classi. Ecco perché swift ti offre entrambe le opzioni.

    
risposta data 06.01.2018 - 01:16
fonte
3

Il link di @ amon è una risposta completa. Per citazione

A value type is a type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function.

Per contrasto per le classi

Unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead.

La semantica del copy-by-value dei tipi di struct tende a far viaggiare gli utenti che hanno familiarità con le lingue che sono esclusivamente / principalmente copia-per-riferimento. Per capire veramente questo comportamento vale la pena guardare C (dato che Swift è strongmente connesso a Objective-C, a sua volta basato su C). Le informazioni di base sono disponibili nel riferimento C .

object: region of data storage in the execution environment, the contents of which can represent values.

A structure type describes a sequentially allocated nonempty set of member objects (and, in certain circumstances, an incomplete array), each of which has an optionally specified name and possibly distinct type.

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand

Mettere insieme queste tre definizioni ci dice perché C struct s non è copy-on-write. Se abbiamo in C

struct foo {};

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

In questo caso, a e b sono valori nelle regioni di archiviazione dati (nota: non riferimenti o puntatori a quelle regioni ma i valori in quelle regioni). L'espressione b = a sostituisce il valore memorizzato nella regione di b con quello nella regione a , ovvero il valore di a viene "copiato" e assegnato in b .

Quindi il comportamento di C qui è un risultato naturale delle sue specifiche e del modello astratto della macchina (che è stato progettato per essere molto suscettibile di un'implementazione efficiente su hardware standard). L'Objective C ha ereditato questo comportamento e Swift sta seguendo l'Obiettivo C.

    
risposta data 09.04.2017 - 20:10
fonte
3

Stai iniziando con un equivoco. È una struttura. le strutture sono tipi di valore. L'assegnazione a p2 crea una copia. p1 e p2 non puntano da nessuna parte.

Questa è esattamente la differenza tra struct e class. Le classi sono tipi di riferimento. Se avessi una classe, l'assegnazione a p2 creerebbe solo un secondo riferimento allo stesso oggetto a cui p1 punta (e conta il riferimento).

Stai dicendo: "Non esiste un modo in cui i tipi di valore possono fare COW senza usare un puntatore indiretto, che sconfigge lo scopo dei tipi di valore". Lo scopo dei tipi di valore è la semantica dei tipi di valore. I dettagli di implementazione non contano.

Ma dobbiamo andare oltre. Quando copi una struttura contenente due Float, i due Float vengono copiati. Quando copi una struttura contenente due riferimenti a oggetti, i riferimenti all'oggetto vengono copiati, quindi ora hai due strutture, con riferimenti agli stessi oggetti. Se si sostituisce un riferimento nella copia, quella struttura originale non viene modificata. Se si modifica un oggetto usando il riferimento nella copia, si tratta dello stesso oggetto a cui fa riferimento l'originale, quindi anche l'oggetto a cui fa riferimento la struttura originale, essendo lo stesso oggetto, è stato modificato.

Le matrici sono strutture, ma la struttura contiene un riferimento a un oggetto contenente i dati effettivi (che è un dettaglio di implementazione dell'implementazione di matrici della libreria Swift). Quel riferimento viene copiato, quindi a questo punto l'array copiato fa riferimento allo stesso identico oggetto dati. Va bene fino a quando nessuno cerca di modificare l'array. Quando l'oggetto dati viene modificato, l'accessor controlla quanti riferimenti all'oggetto dati ci sono e, se ce ne sono più di uno, viene creata una copia dell'oggetto dati in quel momento.

Il comportamento osservabile degli array è come se tutti gli elementi fossero copiati quando la struttura dell'array viene copiata. Ma l'implementazione è molto più veloce perché un codice intelligente copia solo i dati quando necessario. Se vuoi la stessa ottimizzazione per le tue strutture, devi scrivere il codice per farlo. Non ha nulla a che fare con il compilatore, fa tutto parte dell'implementazione della libreria Swift di un array.

    
risposta data 09.04.2017 - 20:36
fonte
-1

Si noti che COW / copy-on-write è un'ottimizzazione. Per le strutture (minuscole?), L'ottimizzatore del compilatore può determinare che la semplice copia di tutti i dati della struttura è più piccola e / o più veloce rispetto all'inserimento e / o all'esecuzione di codice COW o ARC. Tuttavia, la semantica degli oggetti di classe non lo consente.

Risposta precedente:

Con le strutture COW, devi copiare una struttura in memoria appena prima che la si modifica per la prima volta dopo averla ricevuta o dopo averla passata a qualcun altro. Questo è un flag a 1 bit per struttura per utente e una potenziale copia singola per utente, che può essere ottimizzata in alcuni casi. Dopodiché, i dati nella struct sono nella tua memoria privata e nessun altro può vederlo o cambiarlo sotto di te.

Chiunque tenti di modificare quella struttura sotto di te dovrebbe aver prima creato la propria copia privata, e averla modificata per te, invisibile.

Se nessuno scrive, non è necessario eseguire alcuna copia o tracciamento (a parte un flag "pulito" a 1 bit per utente, che può essere o meno ottimizzato). Anche se il flag a 1 bit per utente non può essere ottimizzato, è molto più semplice del conteggio / tracciamento di riferimento.

    
risposta data 15.04.2017 - 22:56
fonte

Leggi altre domande sui tag