Le istruzioni condizionali non banali devono essere spostate nella sezione di inizializzazione dei cicli?

21

Ho avuto questa idea da questa domanda su stackoverflow.com

Il seguente schema è comune:

final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
  //...do something
}

Il punto che sto cercando di fare è che l'istruzione condizionale è qualcosa di complicato e non cambia.

È meglio dichiararlo nella sezione di inizializzazione del ciclo, come tale?

final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
  //...do something
}

È più chiaro?

Che cosa succede se l'espressione condizionale è semplice come

final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
  //...do something
}
    
posta Celeritas 29.12.2016 - 18:25
fonte

5 risposte

62

Quello che farei è qualcosa di simile a questo:

void doSomeThings() {
    final x = 10;//whatever constant value
    final limit = Math.floor(Math.sqrt(x)) + 1;
    for(int i = 0; i < limit; i++) {
         //...do something
    }
}

Sinceramente, l'unica buona ragione per ingannare l'inizializzazione di j (ora limit ) nell'intestazione del ciclo è di mantenerlo correttamente in ambito. Tutto ciò che serve per fare in modo che un problema non sia un bel campo di applicazione.

Posso apprezzare il desiderio di essere veloce ma non sacrificare la leggibilità senza una vera buona ragione.

Certo, il compilatore può ottimizzare, inizializzare più vars può essere legale, ma i loop sono abbastanza difficili da debugare così com'è. Per favore sii gentile con gli umani. Se questo risulta davvero rallentare, è bello capirlo abbastanza per risolverlo.

    
risposta data 29.12.2016 - 18:55
fonte
38

Un buon compilatore genererà lo stesso codice in entrambi i modi, quindi se stai andando per le prestazioni, apporta solo una modifica se è in un ciclo critico e in realtà lo hai profilato e trovato che lo rende una differenza. Anche se il compilatore non può ottimizzarlo, come la gente ha sottolineato nei commenti sul caso con le chiamate di funzione, nella stragrande maggioranza delle situazioni, la differenza di prestazioni sarà troppo piccola per meritare la considerazione di un programmatore.

Tuttavia ...

Non dobbiamo dimenticare che il codice è principalmente un mezzo di comunicazione tra gli umani, e entrambe le opzioni non comunicano molto bene agli altri umani. Il primo dà l'impressione che l'espressione debba essere calcolata ad ogni iterazione, e il secondo nella sezione di inizializzazione implica che sarà aggiornato da qualche parte all'interno del ciclo, dove è veramente costante per tutto.

In realtà preferirei che fosse estratto sopra il ciclo e reso final per renderlo immediatamente e abbondantemente chiaro a chiunque leggesse il codice. Questo non è l'ideale perché aumenta lo scope della variabile, ma la tua funzione di inclusione non dovrebbe comunque contenere molto più di quel ciclo.

    
risposta data 29.12.2016 - 18:58
fonte
9

Come ha detto @Karl Bielefeldt nella sua risposta, questo di solito è un non-problema.

Tuttavia, era una volta un problema comune in C e C ++, e un trucco è arrivato a risolvere il problema senza ridurre la leggibilità del codice- iterare all'indietro, fino a 0 .

final x = 10;//whatever constant value
for(int i = Math.floor(Math.sqrt(x)); i >= 0; i--) {
  //...do something
}

Ora il condizionale in ogni iterazione è solo >= 0 che ogni compilatore compilerà in 1 o 2 istruzioni di assemblaggio. Ogni CPU prodotta negli ultimi decenni dovrebbe avere controlli di base come questi; facendo un rapido controllo sulla mia macchina x64 vedo che questo si trasforma in cmpl $0x0, -0x14(%rbp) (valore long-int-compare 0 vs register rbp offset -14) e jl 0x100000f59 (salta all'istruzione che segue il ciclo se il confronto precedente era true per "2nd-arg < 1st-arg") .

Notare che ho rimosso + 1 da Math.floor(Math.sqrt(x)) + 1 ; affinché il calcolo matematico funzioni, il valore iniziale dovrebbe essere int i = «iterationCount» - 1 . Vale anche la pena notare che il tuo iteratore deve essere firmato; unsigned int non funzionerà e probabilmente compiler-warn.

Dopo aver programmato in linguaggi basati su C per ~ 20 anni, ora scrivo solo loop iterating al contrario, a meno che non vi sia un motivo specifico per forward-index-iterate. Oltre ai controlli più semplici nei condizionali, l'iterazione inversa spesso comporta anche passaggi laterali che altrimenti sarebbero fastidiose matrici di array, mentre iterating.

    
risposta data 30.12.2016 - 05:20
fonte
3

Diventa interessante una volta che Math.sqrt (x) viene sostituito da Mymodule.SomeNonPureMethodWithSideEffects (x).

Fondamentalmente il mio modus operandi è: se ci si aspetta che qualcosa doni sempre lo stesso valore, allora valutalo solo una volta. Ad esempio List.Count, se l'elenco non dovrebbe cambiare durante il funzionamento del ciclo, quindi ottenere il conteggio al di fuori del ciclo in un'altra variabile.

Alcuni di questi "conteggi" possono essere sorprendentemente costosi, specialmente quando si hanno a che fare con i database. Anche se stai lavorando su un set di dati che non dovrebbe cambiare durante l'iterazione dell'elenco.

    
risposta data 30.12.2016 - 15:34
fonte
0

Secondo me è molto specifico per la lingua. Ad esempio, se si utilizza C ++ 11, sospetto che se il controllo delle condizioni fosse una funzione constexpr , il compilatore sarebbe molto probabile che ottimizzasse le esecuzioni multiple poiché sa che produrrà lo stesso valore ogni volta.

Tuttavia se la funzione call è una funzione di libreria che non è constexpr , il compilatore quasi certamente la eseguirà ad ogni iterazione in quanto non può dedurlo (a meno che non sia in linea e possa quindi essere dedotta come pura).

So meno di Java, ma dato che è compilato JIT direi che il compilatore ha abbastanza informazioni in fase di esecuzione per probabilmente in linea e ottimizzare la condizione. Ma ciò si baserebbe su una buona progettazione del compilatore e il compilatore che decideva questo ciclo era una priorità di ottimizzazione che possiamo solo immaginare.

Personalmente ritengo che sia leggermente più elegante inserire la condizione all'interno del ciclo for se possibile, ma se è complessa la scriverò in una funzione constexpr o inline , o l'equivalente in lingue per suggerire che la funzione è pura e ottimizzabile. Ciò rende ovvio l'intento e mantiene lo stile del loop idiomatico senza creare un'enorme linea illeggibile. Assegna inoltre al controllo di condizione un nome se è una sua funzione in modo che i lettori possano vedere immediatamente a livello logico a cosa serve il controllo senza leggerlo se complesso.

    
risposta data 30.12.2016 - 21:19
fonte

Leggi altre domande sui tag