Risultati piccoli e imprevedibili nelle serie di un modello deterministico


10

Ho un modello considerevole (~ 5000 righe) scritto in C. È un programma seriale, senza generazione casuale di numeri da nessuna parte. Fa uso della libreria FFTW per le funzioni che usano FFT - Non conosco i dettagli dell'implementazione di FFTW, ma presumo che anche le funzioni ivi contenute siano deterministiche (correggimi se sbaglio).

Il problema che non riesco a capire è che sto ottenendo piccole differenze nei risultati per esecuzioni identiche sulla stessa macchina (stesso compilatore, stesse librerie).

Uso variabili a doppia precisione e, ad valueesempio, per generare il risultato in variabile , rilascio: fprintf(outFID, "%.15e\n", value);o
fwrite(&value, 1, sizeof(double), outFID);

E
otterrei costantemente differenze come: 2.07843469652206 4 e-16 vs. 2.07843469652206 3 e-16

Ho trascorso molto tempo a cercare di capire perché. Inizialmente pensavo che uno dei miei chip di memoria fosse andato male, e li ho ordinati e sostituiti, senza risultato. Successivamente ho anche provato a eseguire il mio codice sulla macchina Linux di un collega e ho differenze della stessa natura.

Che cosa potrebbe causare questo? Ora è un piccolo problema, ma mi chiedo se sia la "punta dell'iceberg" (di un problema serio).

Ho pensato di pubblicare qui invece di StackOverflow nel caso in cui qualcuno che avesse lavorato con modelli numerici avesse riscontrato questo problema. Se qualcuno potesse far luce su questo, sarei molto grato.

Seguito dato ai commenti:
Christian Clason e Vikram: in primo luogo, grazie per l'attenzione alla mia domanda. Gli articoli che hai collegato suggeriscono che: 1. errori di arrotondamento limitano l'accuratezza e 2. codici diversi (come l'introduzione di istruzioni di stampa apparentemente innocue) possono influire sui risultati fino al epsilon della macchina. Dovrei chiarire che non sto confrontando gli effetti fwritee le fprintffunzioni. Sto usando l'uno o l'altro. In particolare, lo stesso eseguibile viene utilizzato per entrambe le esecuzioni. Sto semplicemente affermando che il problema si verifica se utilizzo fprintfOR fwrite.

Quindi il percorso del codice (ed eseguibile) è lo stesso e l'hardware è lo stesso. Con tutti questi fattori esterni mantenuti costanti, da dove viene la casualità, fondamentalmente? Ho sospettato che il capovolgimento dei bit fosse dovuto al fatto che la memoria difettosa non si manteneva un po 'correttamente, motivo per cui ho sostituito i chip di memoria, ma questo non sembra essere il problema qui, ho verificato e hai indicato. Il mio programma emette migliaia di questi numeri a doppia precisione in una sola corsa e ci sono sempre una manciata casuale che ha lanci di bit casuali.

21016

Seguito n. 2 :
questa è una trama delle serie temporali emesse dal modello, per facilitare la discussione degli spunti nei commenti. inserisci qui la descrizione dell'immagine


21016

Stai chiedendo perché la tua macchina non è più precisa della precisione della macchina. en.wikipedia.org/wiki/Machine_epsilon
Vikram,

1
Vedi inf.ethz.ch/personal/gander/Heisenberg/paper.html per un esempio correlato della sottile influenza dei percorsi del codice sull'aritmetica in virgola mobile. E, naturalmente, ece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/…
Christian Clason,

1
1016

2
1

Risposte:


9

Ci sono aspetti dei moderni sistemi informatici intrinsecamente non deterministici che possono causare questo tipo di differenze. Fintanto che le differenze sono molto ridotte rispetto alla precisione richiesta delle soluzioni, probabilmente non c'è motivo di preoccuparsene.

Un esempio di cosa può andare storto in base alla mia esperienza. Considera il problema di calcolare il prodotto punto di due vettori xey.

d=i=1nxiyi

xiyi

Ad esempio, è possibile calcolare prima il prodotto di due vettori come

d=((x1y1)+(x2y2))+(x3y3)

e poi come

d=(x1y1)+((x2y2)+(x3y3))

Come è potuto succedere? Ecco due possibilità.

  1. Calcoli multithread su core paralleli. I computer moderni in genere hanno 2, 4, 8 o anche più core del processore che possono funzionare in parallelo. Se il tuo codice utilizza thread paralleli per calcolare un prodotto punto su più processori, qualsiasi perturbazione casuale del sistema (ad es. L'utente ha spostato il mouse e uno dei core del processore deve elaborare quel movimento del mouse prima di tornare al prodotto punto) comportare una modifica nell'ordine delle aggiunte.

  2. Allineamento di dati e istruzioni vettoriali. I moderni processori Intel hanno una serie speciale di istruzioni che possono operare (ad esempio) per numeri in virgola mobile alla volta. Queste istruzioni vettoriali funzionano meglio se i dati sono allineati su limiti di 16 byte. In genere, un ciclo del prodotto punto suddivide i dati in sezioni di 16 byte (4 float alla volta). Se si esegue nuovamente il codice una seconda volta, i dati potrebbero essere allineati in modo diverso con i blocchi di memoria da 16 byte in modo che le aggiunte siano eseguito in un ordine diverso, con conseguente risposta diversa.

È possibile indirizzare il punto 1 eseguendo il codice come singolo thread e disabilitando tutta l'elaborazione parallela. Puoi indirizzare il punto 2 richiedendo l'allocazione della memoria per allineare i blocchi di memoria (in genere lo faresti compilando il codice con un interruttore come -align.) Se il tuo codice sta ancora dando risultati che variano, allora ci sono altre possibilità di guardare a.

Questa documentazione di Intel discute problemi che possono portare alla non riproducibilità dei risultati con la libreria Intel Math Kernel. Un altro documento di Intel che parla degli switch del compilatore da utilizzare con i compilatori Intel.


Vedo che pensi che il tuo codice stia eseguendo il thread singolo. Anche se probabilmente conosci bene il tuo codice, non sarei sorpreso se stessi chiamando subroutine (ad es. Routine BLAS) che vengono eseguite in multithread. Dovresti controllare per vedere esattamente quali librerie stai usando. È inoltre possibile utilizzare gli strumenti di monitoraggio del sistema per visualizzare l'utilizzo della CPU.
Brian Borchers,

1
o, come detto, la biblioteca FFTW ...
Christian Clason,

@BrianBorchers, grazie. L'esempio di casualità che arriva dalla natura non associativa dell'aggiunta in virgola mobile è illuminante. Christian Clason ha sollevato un problema secondario sul fatto che l'output del mio modello sia significativo, data l'entità dei numeri - potrebbe essere un grosso problema se ha ragione (e lo sto capendo correttamente), quindi lo sto esaminando ora.
boxofchalk1,

2

La libreria FFTW menzionata potrebbe essere eseguita in modalità non deterministica.

Se si utilizza la modalità FFTW_MEASURE o FFTW_PATIENT, i programmi controllano in fase di esecuzione, i valori dei parametri funzionano più rapidamente e quindi useranno tali parametri nell'intero programma. Poiché il tempo di esecuzione ovviamente fluttuerà leggermente, i parametri saranno diversi e il risultato delle trasformazioni di Fourier sarà non deterministico. Se si desidera FFTW deterministico, utilizzare la modalità FFTW_ESTIMATE.


1

Anche se è vero che le modifiche all'ordine di valutazione dei termini di espressione possono benissimo verificarsi a causa di scenari di elaborazione multi-core / multi-thread, non dimenticare che potrebbe esserci (anche se è un colpo lungo) una sorta di difetto di progettazione hardware al lavoro. Ricorda il problema Pentium FDIV? (Vedi https://en.wikipedia.org/wiki/Pentium_FDIV_bug ). Qualche tempo fa, ho lavorato sul software di simulazione di circuiti analogici basato su PC. Parte della nostra metodologia prevedeva lo sviluppo di suite di test di regressione, che avremmo corso contro build notturne del software. Con molti dei modelli che abbiamo sviluppato, metodi iterativi (ad esempio Newton-Raphson ( https://en.wikipedia.org/wiki/Newton%27s_method) e Runge-Kutta) sono stati ampiamente utilizzati negli algoritmi di simulazione. Con i dispositivi analogici, accade spesso che artefatti interni, come tensioni, correnti, ecc., Presentino valori numerici estremamente piccoli. Questi valori, come parte del processo di simulazione, sono variati in modo incrementale nel tempo (simulato). L'entità di questi cambiamenti può essere estremamente ridotta e ciò che abbiamo spesso osservato è che le successive operazioni FPU su tali valori delta confinavano con la soglia di "rumore" della precisione della FPU (il floating a 64 bit ha una mantissa a 53 bit, IIRC). Ciò, insieme al fatto che spesso dovevamo introdurre il codice di registrazione "PrintF" nei modelli per consentire il debug (ah, i bei vecchi tempi!), Risultati sporadici praticamente garantiti, su base giornaliera! E allora' s significa tutto questo? Devi aspettarti di vedere le differenze in tali circostanze e la cosa migliore da fare è definire e attuare un modo per decidere (grandezza, frequenza, tendenza ecc.) Quando / come ignorarle.


Grazie, Jim per l'intuizione. Qualche idea su quali fenomeni fondamentali causerebbero tali "artefatti interni"? Pensavo che l'interferenza elettromagnetica potesse essere una cosa sola, ma anche i bit significativi avrebbero influito, vero?
boxofchalk1,

1

Mentre l'arrotondamento in virgola mobile da operazioni asincrone può essere il problema, sospetto che sia qualcosa di più banale. L'uso di una variabile non inizializzata che sta aggiungendo casualità al tuo codice altrimenti deterministico. È un problema comune che viene spesso trascurato dagli sviluppatori perché quando si esegue in modalità debug tutte le variabili vengono inizializzate su 0 alla dichiarazione. Quando non è in esecuzione in modalità debug, la memoria assegnata a una variabile ha il valore che la memoria aveva prima dell'assegnazione. La memoria non viene azzerata al momento dell'assegnazione come ottimizzazione. Se ciò accade nel tuo codice, sarà facile da risolvere, tanto meno nel codice delle librerie.

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.