cat swift/performance-collection-stringhe.md
Performance di collection e stringhe
Array, Dictionary, Set e String sono potenti ma non gratuiti: imparare i costi comuni evita ottimizzazioni sbagliate.
La performance in Swift non si impara memorizzando micro-regole. Si impara riconoscendo forme di codice che possono diventare costose e misurando quando serve.
In questa lezione guardiamo collection e stringhe, perché sono ovunque.
Array
Array è una collection ordinata.
Accedere per indice è veloce:
let primo = prodotti[0]
Aggiungere in fondo e generalmente efficiente:
var prodotti: [Prodotto] = []
prodotti.append(nuovo)
Inserire all’inizio può essere costoso, perché gli elementi devono essere spostati:
prodotti.insert(nuovo, at: 0)
Se fai molte inserzioni in testa, forse Array non è la struttura ideale.
reserveCapacity
Quando sai quanti elementi aggiungerai:
var risultati: [Risultato] = []
risultati.reserveCapacity(input.count)
for elemento in input {
risultati.append(trasforma(elemento))
}
Questo può evitare riallocazioni.
Non serve ovunque. Serve in punti caldi o su collection grandi.
Dictionary
Dictionary è adatto a lookup per chiave.
let prodottiPerId = Dictionary(uniqueKeysWithValues: prodotti.map { prodotto in
(prodotto.id, prodotto)
})
let prodotto = prodottiPerId["p-1"]
Se cerchi spesso per id, un dizionario è meglio di filtrare ogni volta un array.
Meno efficiente:
let prodotto = prodotti.first { $0.id == "p-1" }
Va bene una volta. Non va bene ripetuto migliaia di volte.
Set
Set è utile per deduplicare e verificare appartenenza.
let categorie = Set(carrello.righe.map { $0.prodotto.categoria })
if categorie.contains(.libri) {
...
}
contains su Set e generalmente più efficiente di contains su array, soprattutto se gli elementi sono molti.
map/filter/reduce e allocazioni intermedie
Questa catena è leggibile:
let totale = righe
.filter { $0.prodotto.categoria == .libri }
.map { $0.subtotaleInCent }
.reduce(0, +)
Ma crea collection intermedie.
Se il punto è critico, puoi usare un ciclo:
var totale = 0
for riga in righe where riga.prodotto.categoria == .libri {
totale += riga.subtotaleInCent
}
Non scegliere sempre il ciclo per paura. Sceglilo quando hai misurato o quando la catena diventa chiaramente costosa.
lazy
Puoi evitare alcune collection intermedie con lazy:
let totale = righe.lazy
.filter { $0.prodotto.categoria == .libri }
.map { $0.subtotaleInCent }
.reduce(0, +)
lazy può aiutare, ma non è una bacchetta magica. Rende la valutazione pigra e può migliorare alcune pipeline, ma può anche rendere il debugging meno immediato.
Usalo quando la pipeline è chiara e il volume giustifica attenzione.
String non è un array semplice
Le stringhe Swift gestiscono Unicode. Questo significa che contare caratteri non è sempre banale come contare byte.
let testo = "caffe"
print(testo.count)
Con emoji, accenti composti e alfabeti diversi, la complessità cresce.
Non trattare String come un array di byte se stai lavorando con testo umano.
Concatenazione ripetuta
Questo può essere costoso:
var descrizione = ""
for prodotto in prodotti {
descrizione += "\(prodotto.nome)\n"
}
Meglio:
let descrizione = prodotti
.map(\.nome)
.joined(separator: "\n")
Oppure, se devi costruire progressivamente:
var righe: [String] = []
righe.reserveCapacity(prodotti.count)
for prodotto in prodotti {
righe.append(prodotto.nome)
}
let descrizione = righe.joined(separator: "\n")
Misurare senza ossessione
Prima regola: scrivi codice chiaro.
Seconda regola: se una parte è lenta, misura.
Puoi misurare in modo grezzo:
let start = ContinuousClock.now
// codice da misurare
let end = ContinuousClock.now
print(end - start)
Per lavoro serio usa Instruments, test di performance o strumenti dedicati.
Checklist
Ricorda:
- usa
Arrayper ordine e accesso per indice; - usa
Dictionaryper lookup frequenti; - usa
Setper appartenenza e deduplicazione; - evita concatenazione ripetuta di stringhe grandi;
- usa
reserveCapacityquando conosci la dimensione; - valuta
lazyper pipeline grandi; - preferisci chiarezza finché non misuri un problema.
Esercizio
Su una lista di prodotti:
- costruisci un dizionario
id -> prodotto; - costruisci un set di categorie;
- calcola il totale dei soli libri con pipeline;
- riscrivi lo stesso calcolo con ciclo;
- genera una descrizione testuale con
joined(separator:); - spiega quale versione terresti e perché.