2012-02-14 12 views
24

Abbiamo un risolutore CFD e durante l'esecuzione di una simulazione, è stato rilevato che si eseguiva straordinariamente lento su alcune macchine ma non su altre. Utilizzando Intel VTune, è stato trovato il seguente linea era il problema (in Fortran):Sostituzione della funzione Pow() straordinariamente lenta

RHOV= RHO_INF*((1.0_wp - COEFF*EXP(F0)))**(1.0_wp/(GAMM - 1.0_wp)) 

perforazione con VTune, il problema è stato rintracciato alla linea call pow montaggio e quando si traccia la pila, mostrava stava usando __slowpow(). Dopo alcune ricerche, il numero this page si è lamentato della stessa cosa.

Sulla macchina con libc versione 2.12, la simulazione ha impiegato 18 secondi. Sulla macchina con versione di libc 2.14, la simulazione ha richiesto 0 secondi.

In base alle informazioni sulla pagina di cui sopra, il problema sorge quando la base a pow() è vicina a 1.0. Quindi abbiamo fatto un altro semplice test in cui abbiamo ridimensionato la base con un numero arbitrario prima del pow() e poi diviso per il numero generato dall'esponente dopo la chiamata pow(). Questo ha fatto decadere il tempo di esecuzione da 18 secondi a 0 secondi con la libc 2.12.

Tuttavia, non è pratico inserire tutto questo nel codice in cui facciamo a**b. Come si può sostituire la funzione pow() in libc? Per esempio, vorrei che la linea di assemblaggio call pow generata dal compilatore Fortran richiamasse una funzione personalizzata pow() che scriviamo che esegue il ridimensionamento, chiama la libc pow() e quindi divide per il ridimensionamento. Come si crea uno strato intermedio trasparente al compilatore?

Modifica

per chiarire, stiamo cercando qualcosa di simile (pseudo-codice):

double pow(a,b) { 
    a *= 5.0 
    tmp = pow_from_libc(a,b) 
    return tmp/pow_from_libc(5.0, b) 
} 

E 'possibile caricare il pow da libc e rinominarlo nella nostra funzione personalizzata evitare i conflitti di denominazione? Se il file customPow.o può rinominare pow da libc, cosa succede se libc è ancora necessario per altre cose? Ciò causerebbe un conflitto di denominazione tra pow in customPow.o e pow in libc?

+0

Buon vecchio Fortran! Interessante domanda però +1 –

risposta

7

Basta scrivere la propria funzione pow, inserire il file .o in un archivio di librerie statiche libmypow.a da qualche parte nel percorso della libreria del linker e passare -lmypow durante il collegamento.

+1

Would che permettono l'usanza funzione 'pow' chiamare 'pow' in libc però? Questo 'pow' personalizzato ridimensiona la base se necessario, quindi richiama libc' pow' e poi svita se necessario. Sembra che ci sarebbero alcuni conflitti di denominazione. – tpg2114

+9

Se usi il collegamento dinamico, ci sono gli hack 'dlsym' che puoi usare per ottenere il comportamento desiderato, ma è fragile. Un approccio migliore, se si ha solo bisogno di lavorare su sistemi con il linker GNU, è l'opzione '--wrap' su' ld' (che 'gcc' può passare a' ld' tramite '-Wl, - wrap, pow'). Quindi metti '__wrap_pow' in' libmypow.a', e fallo chiamare '__real_pow' dove deve usare libc pow, e tutto dovrebbe andare bene. –

3

pow(a,b) è lo stesso di exp(b*ln(a)), forse la sostituzione funzionerà per voi.

+1

Questo probabilmente aggirerebbe la lentezza della chiamata, ma stiamo cercando un modo per sostituire essenzialmente la chiamata di funzione generata dall'operatore '**' in Fortran senza dover cambiare la base di codice effettiva che abbiamo, se possibile. – tpg2114

+2

Collega quindi la tua versione di pow() che utilizza questa identità. –

+2

questo dà un risultato diverso per 1,0000000000000020^1.5: 1,0000000000000031 con 'chiamata pow', 1,0000000000000029 con' -ffast-math' e 1,5000000000000013 con 'exp (b * ln (a))' – steabert

1

L'ho provato personalmente e, in effetti, se compilo il programma di test dalla pagina a cui ci si collega utilizza call pow nel codice assembly. Tuttavia, la compilazione con ottimizzazione -ffast-math non prevede alcuna chiamata a pow, ma il risultato è leggermente diverso.

22

Bene, aspetta. La biblioteca non chiama lo __slowpow() solo per giocare con te; chiama __slowpow() perché crede che la precisione extra sia necessaria per dare un risultato accurato per i valori che gli stai dando (in questo caso, base molto vicino a 1, esponente di ordine 1). Se ti interessa l'accuratezza di questo calcolo, dovresti capire perché è così e se è importante prima di provare a risolverlo. Potrebbe essere il caso che per (dire) grande F0 negativo questa intera cosa possa essere arrotondata in sicurezza a 1; o potrebbe non farlo, a seconda di cosa è stato fatto con questo valore in seguito.Se hai mai bisogno di 1.d0 meno questo risultato, vorrai quella precisione in più.

+0

Questo è certamente vero. Ma, almeno nella nostra istanza, il nostro codice è dimensionale, quindi le uniche volte che abbiamo una base vicina è quando calcoliamo le cose per la visualizzazione o l'elaborazione post di qualche tipo, quindi entrare in 1e-15 della risposta giusta non è terribilmente importante . Ho eseguito un confronto per vedere quanto perdiamo e l'errore è ~ 1e-13, che per il nostro codice accurato del secondo ordine è comunque inferiore al nostro errore di discretizzazione, quindi è sicuro per noi sostituire tutto il pow() con uno leggermente meno accurato . – tpg2114