Quali semplici tecniche usi per migliorare le prestazioni?


21

Sto parlando del modo in cui scriviamo semplici routine per migliorare le prestazioni senza rendere più difficile la lettura del codice ... ad esempio, questo è il tipico per cui abbiamo imparato:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

Ma di solito lo faccio quando a foreachnon è applicabile:

for(int i = 0, j = collection.length(); i < j; i++ ){
   // stuff here
}

Penso che questo sia un approccio migliore poiché chiamerà il lengthmetodo solo una volta ... la mia ragazza dice che è criptico. C'è qualche altro trucco che usi nei tuoi sviluppi?


34
+1 solo per avere una ragazza che ti dirà quando il tuo codice non è chiaro.
Kristo,

76
Stai solo postando questo per dirci che hai una ragazza.
Josh K,

11
@Christian: non dimenticare che ci sono ottimizzazioni del compilatore che potrebbero fare questo per te, quindi potresti solo influenzare la leggibilità e non influenzare affatto le prestazioni; l'ottimizzazione prematura è la radice di tutti i mali ... Cerca di evitare più di una dichiarazione o incarico sulla stessa riga, non far leggere due volte alle persone ... Dovresti usare il modo normale (il tuo primo esempio) o inserire il seconda dichiarazione al di fuori del ciclo for (anche se ciò riduce anche la leggibilità in quanto è necessario rileggere per vedere cosa significa j).
Tamara Wijsman,

5
@TomWij: la citazione corretta (e completa): "Dovremmo dimenticare piccole efficienze, diciamo circa il 97% delle volte: l'ottimizzazione prematura è la radice di tutti i mali. Eppure non dovremmo rinunciare alle nostre opportunità in quel 3% critico. "
Robert Harvey,

3
@tomwij: se stai spendendo il tre percento, allora per definizione dovresti farlo in un codice critico in termini di tempo e non perdere tempo sull'altro 97%.
Robert Harvey,

Risposte:


28

inserire una discussione prematura-discussione-è-la-radice-di-tutto-malvagio

Detto questo, ecco alcune abitudini che ho preso per evitare inutili efficienze e, in alcuni casi, per rendere il mio codice più semplice e anche più corretto.

Questa non è una discussione di principi generali, ma di alcune cose da tenere presenti per evitare di introdurre inefficienze inutili nel codice.

Conosci il tuo big-O

Questo dovrebbe probabilmente essere unito alla lunga discussione di cui sopra. È quasi logico che un loop all'interno di un loop, in cui il loop interno ripete un calcolo, sarà più lento. Per esempio:

for (i = 0; i < strlen(str); i++) {
    ...
}

Questo richiederà una quantità orribile di tempo se la stringa è davvero lunga, perché la lunghezza viene ricalcolata su ogni iterazione del ciclo. Si noti che GCC in realtà ottimizza questo caso perché strlen()è contrassegnato come una funzione pura.

Quando si ordina un milione di numeri interi a 32 bit, l' ordinamento a bolle sarebbe il modo sbagliato di procedere . In generale, l'ordinamento può essere eseguito in tempo O (n * log n) (o meglio, nel caso di ordinamento radix), quindi a meno che tu non sappia che i tuoi dati saranno piccoli, cerca un algoritmo che sia almeno O (n * registro n).

Allo stesso modo, quando si ha a che fare con i database, prestare attenzione agli indici. Se tu SELECT * FROM people WHERE age = 20e non hai un indice sulle persone (età), richiederà una scansione sequenziale O (n) piuttosto che una scansione dell'indice O (log n) molto più veloce.

Gerarchia aritmetica intera

Quando si programma in C, tenere presente che alcune operazioni aritmetiche sono più costose di altre. Per i numeri interi, la gerarchia va in questo modo (prima meno costosa):

  • + - ~ & | ^
  • << >>
  • *
  • /

Certo, il compilatore cose di solito ottimizzare come n / 2per n >> 1automaticamente se ci si rivolge un computer tradizionale, ma se ci si rivolge un dispositivo embedded, non si potrebbe ottenere questo lusso.

Inoltre, % 2e & 1hanno una semantica diversa. Divisione e modulo di solito arrotondano verso zero, ma la sua implementazione è definita. Buon vecchio >>e &ruota sempre verso l'infinito negativo, che (secondo me) ha molto più senso. Ad esempio, sul mio computer:

printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1

Quindi, usa ciò che ha senso. Non pensare di essere un bravo ragazzo usando % 2quando stavi per scrivere & 1.

Operazioni in virgola mobile costose

Evita pesanti operazioni in virgola mobile come pow()e log()nel codice che non ne ha davvero bisogno, specialmente quando si tratta di numeri interi. Prendi, ad esempio, la lettura di un numero:

int parseInt(const char *str)
{
    const char *p;
    int         digits;
    int         number;
    int         position;

    // Count the number of digits
    for (p = str; isdigit(*p); p++)
        {}
    digits = p - str;

    // Sum the digits, multiplying them by their respective power of 10.
    number = 0;
    position = digits - 1;
    for (p = str; isdigit(*p); p++, position--)
        number += (*p - '0') * pow(10, position);

    return number;
}

Non solo questo uso di pow()(e le int<-> doubleconversioni necessarie per usarlo) è piuttosto costoso, ma crea un'opportunità per la perdita di precisione (per inciso, il codice sopra non ha problemi di precisione). Ecco perché sussulto quando vedo questo tipo di funzione usata in un contesto non matematico.

Inoltre, nota come l'algoritmo "intelligente" di seguito, che si moltiplica per 10 su ogni iterazione, è in realtà più conciso del codice sopra:

int parseInt(const char *str)
{
    const char *p;
    int         number;

    number = 0;
    for (p = str; isdigit(*p); p++) {
        number *= 10;
        number += *p - '0';
    }

    return number;
}

Risposta molto approfondita.
Paddyslacker,

1
Nota, la discussione sull'ottimizzazione prematura non si applica al codice Garbage. Dovresti sempre usare un'implementazione che funzioni bene in primo luogo.

Si noti che GCC in realtà ottimizza questo caso perché strlen () è contrassegnato come una funzione pura. Penso che intendi dire che è una funzione const, non pura.
Andy Lester,

@Andy Lester: In realtà, intendevo puro. Nella documentazione GCC , afferma che const è leggermente più rigoroso di puro in quanto una funzione const non può leggere la memoria globale. strlen()esamina la stringa indicata dall'argomento puntatore, il che significa che non può essere const. Inoltre, strlen()è davvero contrassegnato come puro in glibcstring.h
Joey Adams il

Hai ragione, errore mio, e avrei dovuto ricontrollare. Ho lavorato sul progetto Parrot annotando le funzioni come o pureo constaddirittura documentandolo nel file di intestazione a causa della sottile differenza tra i due. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.html
Andy Lester

13

Dalla tua domanda e dal thread dei commenti, sembra che tu "pensi" che questo cambiamento di codice migliora le prestazioni, ma non sai davvero se lo faccia o meno.

Sono un fan della filosofia di Kent Beck :

"Fallo funzionare, fallo bene, fallo veloce."

La mia tecnica per migliorare le prestazioni del codice, è prima ottenere il codice superando i test unitari e ben fattorizzato e poi (in particolare per le operazioni di looping) scrivere un test unitario che controlla le prestazioni e quindi refactoring il codice o pensare a un algoritmo diverso se quello I ' ho scelto non funziona come previsto.

Ad esempio, per testare la velocità con il codice .NET, utilizzo l'attributo Timeout di NUnit per scrivere asserzioni che una chiamata a un determinato metodo verrà eseguita entro un determinato periodo di tempo.

Usando qualcosa come l'attributo timeout di NUnit con l'esempio di codice che hai dato (e un gran numero di iterazioni per il ciclo), potresti effettivamente dimostrare se il tuo "miglioramento" al codice ha davvero aiutato con la prestazione di quel ciclo.

Un disclaimer: sebbene sia efficace a livello di "micro", non è certamente l'unico modo per testare le prestazioni e non tiene conto dei problemi che potrebbero sorgere a livello di "macro", ma è un buon inizio.


2
Sebbene io sia un grande sostenitore della profilazione, credo anche che sia intelligente tenere a mente i tipi di suggerimenti che Cristian sta cercando. Sceglierò sempre il più veloce tra due metodi ugualmente leggibili. Essere costretti all'ottimizzazione post-matura non è divertente.
AShelly,

Non sono necessariamente necessari test unitari, ma vale sempre la pena spendere questi 20 minuti per verificare se un mito prestazionale è vero o meno, soprattutto perché la risposta dipende spesso dal compilatore e dallo stato di -O e -g flag (o Debug / Rilascio in caso di VS).
mbq,

+1 Questa risposta integra il mio commento correlato alla domanda stessa.
Tamara Wijsman,

1
@AShelly: se stiamo parlando di semplici riformulazioni della sintassi del loop, cambiarlo dopo il fatto è molto facile da fare. Inoltre, ciò che trovi ugualmente leggibile potrebbe non essere così per altri programmatori. È preferibile utilizzare la sintassi "standard" il più possibile e modificarla solo quando è necessario.
Joeri Sebrechts,

@Shelly sicuramente se riesci a pensare a due metodi ugualmente leggibili e scegli quello meno efficiente che semplicemente non stai facendo il tuo lavoro? Qualcuno lo farebbe davvero?
glenatron,

11

Tieni presente che il tuo compilatore potrebbe girare:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

in:

int j = collection.length();
for(int i = 0; i < j; i++ ){
   // stuff here
}

o qualcosa di simile, se collectioninvariato rispetto al ciclo.

Se questo codice si trova in una sezione critica del tempo della tua applicazione, varrebbe la pena scoprire se questo è il caso o no - o in effetti se è possibile modificare le opzioni del compilatore per farlo.

Ciò manterrà la leggibilità del codice (poiché il primo è ciò che la maggior parte delle persone si aspetta di vedere), ottenendo al contempo quei pochi cicli di macchina extra. Sei quindi libero di concentrarti sulle altre aree in cui il compilatore non può aiutarti.

Nota a margine: se cambi collectionall'interno del ciclo aggiungendo o rimuovendo elementi (sì, lo so che è una cattiva idea, ma succede) quindi il tuo secondo esempio o non scorrerà su tutti gli elementi o proverà ad accedere al passato la fine dell'array.


1
Perché non farlo esplicitamente?

3
In alcune lingue che controllano i limiti, SLOW il tuo codice se lo fai esplicitamente. Con un ciclo su collection.length il compilatore lo sposta per te e omette il controllo dei limiti. Con un ciclo verso una costante da qualsiasi altra parte della tua app avrai un controllo dei limiti su ogni iterazione. Ecco perché è importante misurare: l'intuizione sulle prestazioni non è quasi mai corretta.
Kate Gregory,

1
Ecco perché ho detto "varrebbe la pena scoprirlo".
ChrisF

Come può il compilatore C # sapere che collection.length () non modifica collection, come fa stack.pop ()? Penso che sarebbe meglio controllare l'IL piuttosto che supporre che il compilatore lo ottimizzi. In C ++, puoi contrassegnare un metodo come const ('non cambia l'oggetto'), quindi il compilatore può effettuare questa ottimizzazione in modo sicuro.
JBR Wilkinson,

1
@JBRW Gli ottimizzatori che lo fanno sono anche consapevoli dei metodi ok delle raccolte. Dopotutto puoi solo controllare i limiti se puoi notare che qualcosa è una raccolta e sapere come ottenerne la lunghezza.
Kate Gregory,

9

Questo tipo di ottimizzazione di solito non è raccomandato. Quella parte di ottimizzazione può essere facilmente eseguita dal compilatore, stai lavorando con un linguaggio di programmazione di livello superiore anziché con assembly, quindi pensa allo stesso livello.


1
Dalle

1
+1, poiché la maggior parte delle nostre amiche è probabilmente più interessata a Lady Gaga che alla chiarezza del codice.
Aploid,

Potresti spiegare perché non è raccomandato?
Macneil,

@macneil bene ... quel trucco rende i codici non così comuni e totalmente non funziona, quel pezzo di ottimizzazione dovrebbe essere fatto dal compilatore.
Tothoth,

@macneil se stai lavorando in una lingua di livello superiore, pensa allo stesso livello.
Tothoth,

3

Questo potrebbe non valere tanto per la codifica per scopi generici, ma attualmente mi occupo principalmente di sviluppo embedded. Abbiamo un processore di destinazione specifico (che non sarà più veloce - sembrerà stranamente obsoleto dal momento in cui ritirano il sistema in oltre 20 anni) e scadenze temporali molto restrittive per gran parte del codice. Il processore, come tutti i processori, ha alcune stranezze riguardo a quali operazioni sono veloci o lente.

Abbiamo una tecnica utilizzata per garantire che stiamo generando il codice più efficiente, pur mantenendo la leggibilità per l'intero team. Nei luoghi in cui il costrutto del linguaggio più naturale non genera il codice più efficiente, abbiamo creato una macro che garantisce che venga utilizzato il codice ottimale. Se eseguiamo un progetto di follow-on per un altro processore, possiamo aggiornare le macro per il metodo ottimale su quel processore.

Ad esempio, per il nostro attuale processore, i rami svuotano la pipeline, arrestando il processore per 8 cicli. Il compilatore accetta questo codice:

 bool isReady = (value > TriggerLevel);

e lo trasforma nell'equivalente di assembly di

isReady = 0
if (value > TriggerLevel)
{
  isReady = 1;
}

Questo richiederà 3 cicli o 10 se salta isReady=1;. Ma il processore ha maxun'istruzione a ciclo singolo , quindi è molto meglio scrivere codice per generare questa sequenza che è garantita per prendere sempre 3 cicli:

diff = value-TriggerLevel;
diff = max(diff, 0);
isReady = min(1,diff);

Ovviamente, l'intento qui è meno chiaro dell'originale. Quindi abbiamo creato una macro, che usiamo ogni volta che vogliamo un confronto booleano Greater-Than:

#define BOOL_GT(a,b) min(max((a)-(b),0),1)

//isReady = value > TriggerLevel;
isReady = BOOL_GT(value, TriggerLevel);

Possiamo fare cose simili per altri confronti. Per un estraneo, il codice è un po 'meno leggibile che se usassimo solo il costrutto naturale. Tuttavia, diventa rapidamente chiaro dopo aver trascorso un po 'di tempo a lavorare con il codice, ed è molto meglio che lasciare che ogni programmatore faccia esperimenti con le proprie tecniche di ottimizzazione.


3

Bene, il primo consiglio sarebbe quello di evitare tali ottimizzazioni premature fino a quando non saprai esattamente cosa sta succedendo al codice, in modo da essere sicuro che lo stai effettivamente rendendo più veloce e non più lento.

In C #, ad esempio, il compilatore ottimizzerà il codice se esegui il loop della lunghezza di un array, poiché sa che non è necessario controllare l'intervallo quando accedi all'array. Se si tenta di ottimizzarlo inserendo la lunghezza dell'array in una variabile, si interromperà la connessione tra il loop e l'array e si renderà il codice molto più lento.

Se hai intenzione di microottimizzare, dovresti limitarti a cose che sono note per l'utilizzo di molte risorse. Se c'è solo un leggero miglioramento delle prestazioni, dovresti invece scegliere il codice più leggibile e gestibile. Il modo in cui il lavoro del computer cambia nel tempo, quindi qualcosa che scopri ora è leggermente più veloce, potrebbe non rimanere così.


3

Ho una tecnica molto semplice.

  1. Faccio funzionare il mio codice.
  2. Lo collaudo per la velocità.
  3. Se è veloce, torno al passaggio 1 per alcune altre funzionalità. Se è lento, lo profilo per trovare il collo di bottiglia.
  4. Risolvo il collo di bottiglia. Torna al passaggio 1.

Ci sono molte volte in cui si risparmia tempo per aggirare questo processo, ma in generale saprai se è così. In caso di dubbio, mi attengo a questo per impostazione predefinita.


2

Approfitta del corto circuito:

if(someVar || SomeMethod())

richiede tanto tempo per codificare ed è leggibile quanto:

if(someMethod() || someVar)

ma valuterà più rapidamente nel tempo.


1

Aspetta sei mesi, chiedi al tuo capo di acquistare tutti i nuovi computer. Sul serio. Il tempo del programmatore è molto più costoso dell'hardware nel lungo periodo. I computer ad alte prestazioni consentono ai programmatori di scrivere codice in modo semplice senza preoccuparsi della velocità.


6
Ehm ... E le prestazioni che vedono i tuoi clienti? Sei abbastanza ricco da acquistare anche nuovi computer per loro?
Robert Harvey,

2
E abbiamo quasi colpito il muro delle prestazioni; il calcolo multicore è l'unica via d'uscita, ma l'attesa non lo farà utilizzare dai programmi.
mbq,

+1 Questa risposta integra il mio commento correlato alla domanda stessa.
Tamara Wijsman,

3
Nessun tempo di programmazione non è più costoso dell'hardware quando si hanno migliaia o milioni di utenti. Il tempo del programmatore NON è più importante del tempo dell'utente, passalo alla testa il prima possibile.
HLGEM,

1
Prendi buone abitudini, quindi non ci vuole tempo per i programmatori perché è quello che fai sempre.
Dominique McDonnell,

1

Cerca di non ottimizzare troppo in anticipo, quindi quando ottimizzi ti preoccupi un po 'meno della leggibilità.

Poco odio più della complessità inutile, ma quando si colpisce una situazione complessa è spesso necessaria una soluzione complessa.

Se scrivi il codice nel modo più ovvio, fai un commento spiegando perché è stato modificato quando apporti la modifica complessa.

In particolare per il tuo significato, però, trovo che molte volte fare il contrario booleano dell'approccio predefinito a volte aiuta:

for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}

può diventare

for(int i = collection.length(); i > 0; i-=1 ){
// stuff here
}

In molte lingue, purché si apportino le opportune modifiche alla parte "roba" ed è ancora leggibile. Semplicemente non affronta il problema nel modo in cui la maggior parte delle persone penserebbe di farlo prima perché conta all'indietro.

in c # per esempio:

        string[] collection = {"a","b"};

        string result = "";

        for (int i = 0, j = collection.Count() - 1; i < j; i++)
        {
            result += collection[i] + "~";
        }

potrebbe anche essere scritto come:

        for (int i = collection.Count() - 1; i > 0; i -= 1)
        {
            result = collection[i] + "~" + result;
        }

(e sì, dovresti farlo con un join o uno stringbuilder, ma sto cercando di fare un semplice esempio)

Ci sono molti altri trucchi che si possono usare che non sono difficili da seguire, ma molti di essi non si applicano in tutte le lingue come usare metà sul lato sinistro di un compito nel vecchio vb per evitare la penalità di riassegnazione delle stringhe o leggere file di testo in modalità binaria in .net per superare la penalità di buffering quando il file è troppo grande per un readtoend.

L'unico altro caso veramente generico che mi viene in mente che si applicherebbe ovunque sarebbe applicare algebra booleana a condizionali complessi per provare a trasformare l'equazione in qualcosa che ha maggiori possibilità di sfruttare un condizionale di cortocircuito o trasformare un complesso insieme di istruzioni nidificate if-then o case in un'equazione interamente. Nessuno di questi funziona in tutti i casi, ma può essere un notevole risparmio di tempo.


è una soluzione, ma il compilatore probabilmente emetterà avvisi poiché per la lunghezza delle classi più comuni () restituisce un tipo senza segno
stijn

Ma invertendo l'indice, l'iterazione stessa potrebbe diventare più complessa.
Tamara Wijsman,

@stijn Stavo pensando a c # quando l'ho scritto, ma forse questo suggerimento rientra anche nella categoria specifica della lingua per quel motivo - vedi modifica ... @ToWij certamente, non penso che ci siano molti o nessun suggerimento di questa natura che non corrono il rischio di ciò. Se la tua // roba fosse una sorta di manipolazione dello stack, potrebbe anche non essere possibile invertire la logica correttamente, ma in molti casi è e non è troppo confuso se fatto con attenzione nella maggior parte di questi casi IMHO.
Bill,

hai ragione; in C ++ preferirei comunque il ciclo 'normale' ma con la chiamata length () estratta dall'iterazione (come in const size_t len ​​= collection.length (); per (size_t i = 0; i <len; ++ i) {}) per due motivi: trovo che il ciclo di conteggio in avanti "normale" sia più leggibile / comprensibile (ma probabilmente è solo perché è più comune) e prende la lunghezza invariante del ciclo () dal ciclo.
stijn,

1
  1. Profilo. Abbiamo anche un problema? Dove?
  2. Nel 90% dei casi in cui è in qualche modo correlato all'IO, applica la memorizzazione nella cache (e forse ottieni più memoria)
  3. Se è relativo alla CPU, applica la memorizzazione nella cache
  4. Se le prestazioni sono ancora un problema, abbiamo lasciato il regno delle semplici tecniche: fai i conti.

1

Usa i migliori strumenti che puoi trovare : buon compilatore, buon profiler, buone librerie. Ottieni gli algoritmi giusti, o meglio ancora: usa la libreria giusta per farlo per te. Le banali ottimizzazioni del loop sono piccole patate, inoltre non sei intelligente come il compilatore ottimizzante.


1

Il più semplice per me è usare lo stack quando possibile ogni volta che un modello di utilizzo del caso comune si adatta a un intervallo, diciamo, [0, 64) ma ha casi rari che non hanno un limite superiore piccolo.

Esempio C semplice (prima):

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int* values = calloc(n, sizeof(int));

    // do stuff with values
    ...
    free(values);
}

E dopo:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int values_mem[64] = {0}
    int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int));

    // do stuff with values
    ...
    if (values != values_mem)
        free(values);
}

L'ho generalizzato in questo modo, dato che questo tipo di hotspot cresce molto nella profilazione:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    MemFast values_mem;
    int* values = mf_calloc(&values_mem, n, sizeof(int));

    // do stuff with values
    ...

    mf_free(&values_mem);
}

Quanto sopra utilizza lo stack quando i dati allocati sono abbastanza piccoli in quei casi del 99,9%, e altrimenti utilizza l'heap.

In C ++ l'ho generalizzato con una piccola sequenza conforme agli standard (simile alle SmallVectorimplementazioni là fuori) che ruota attorno allo stesso concetto.

Non è un'ottimizzazione epica (ho ottenuto riduzioni da, diciamo, 3 secondi per completare un'operazione fino a 1,8 secondi), ma richiede un tale sforzo banale da applicare. Quando riesci a ottenere qualcosa da 3 secondi a 1,8 secondi semplicemente introducendo una riga di codice e cambiandone due, è un botto abbastanza buono per un dollaro così piccolo.


0

Bene, ci sono molte modifiche alle prestazioni che puoi apportare quando accedi ai dati che avranno un impatto enorme sulla tua applicazione. Se si scrivono query o si utilizza un ORM per accedere a un database, è necessario leggere alcuni libri di ottimizzazione delle prestazioni per il back-end del database in uso. È probabile che tu stia utilizzando tecniche conosciute poco performanti. Non c'è motivo di farlo se non l'ignoranza. Non si tratta di un'ottimizzazione prematura (maledico il tizio che lo ha detto perché è stato così ampiamente interpretato da non preoccuparsi mai delle prestazioni), questo è un buon design.

Solo un rapido esempio di ottimizzatori delle prestazioni per SQL Server: utilizzare gli indici appropriati, evitare i cursori: utilizzare la logica basata su set, utilizzare sargable in cui clausole, non impilare le viste in cima alle viste, non restituire più dati di quelli necessari o più colonne di cui hai bisogno, non utilizzare sottoquery correlate.


0

Se questo è C ++, dovresti prendere l'abitudine di ++ipiuttosto che i++. ++inon sarà mai peggio, significa esattamente lo stesso di un'istruzione indipendente e in alcuni casi potrebbe essere un miglioramento delle prestazioni.

Non vale la pena cambiare il codice esistente nel caso in cui possa aiutare, ma è una buona abitudine entrare.


0

Ho un approccio leggermente diverso. Basta semplicemente seguire i consigli che ottieni qui non farà molta differenza, perché ci sono alcuni errori che devi fare, che devi quindi correggere, dai quali devi imparare.

L'errore che devi fare è progettare la tua struttura di dati come fanno tutti. Cioè, con dati ridondanti e molti livelli di astrazione, con proprietà e notifiche che si propagano in tutta la struttura cercando di mantenerlo coerente.

Quindi devi eseguire l'ottimizzazione delle prestazioni (profilazione) e mostrarti come, in molti modi, ciò che ti costa una gran quantità di cicli sono i molti strati di astrazione, con proprietà e notifiche che si propagano in tutta la struttura cercando di mantenerlo coerente.

Potresti essere in grado di risolvere questi problemi in qualche modo senza importanti modifiche al codice.

Quindi, se sei fortunato, puoi imparare che è meglio una minore struttura dei dati e che è meglio essere in grado di tollerare un'incoerenza temporanea piuttosto che provare a mantenere molte cose strettamente in accordo con ondate di messaggi.

Il modo in cui scrivi i loop non ha nulla a che fare con questo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.