cat swift/performance-collection-stringhe.md

Lezione 27 50 min

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 Array per ordine e accesso per indice;
  • usa Dictionary per lookup frequenti;
  • usa Set per appartenenza e deduplicazione;
  • evita concatenazione ripetuta di stringhe grandi;
  • usa reserveCapacity quando conosci la dimensione;
  • valuta lazy per pipeline grandi;
  • preferisci chiarezza finché non misuri un problema.

Esercizio

Su una lista di prodotti:

  1. costruisci un dizionario id -> prodotto;
  2. costruisci un set di categorie;
  3. calcola il totale dei soli libri con pipeline;
  4. riscrivi lo stesso calcolo con ciclo;
  5. genera una descrizione testuale con joined(separator:);
  6. spiega quale versione terresti e perché.