
Il colpevole torna sempre sul luogo del delitto, Sherlock Holmes insegna. E allora eccomi di nuovo a confrontare le performance dei linguaggi, continuazione del post Ancora confronti sui linguaggi.
Walter, dikiyvolk, ha tradotto la versione Java in C (c’è un controllo sbagliato alla riga 54), così:
#include <stdio.h>
#include <math.h>
static double f[14];
static double fsin(double a) {
return a - pow(a, 3.0) / f[3] + pow(a, 5.0) / f[5]
- pow(a, 7.0) / f[7] + pow(a, 9.0) / f[9]
- pow(a, 11.0) / f[11] + pow(a, 13.0) / f[13];
}
static double fcos(double a) {
return 1.0 - pow(a, 2.0) / f[2] + pow(a, 4.0) / f[4]
- pow(a, 6.0) / f[6] + pow(a, 8.0) / f[8]
- pow(a, 10.0) / f[10] + pow(a, 12.0) / f[12];
}
static double myln(double x) {
if (x == 0.0) {
return -1.0e20;
} else {
double r = (x - 1.0) / (x + 1.0);
return 2.0 * (r + pow(r, 3.0) / 3.0
+ pow(r, 5.0) / 5.0
+ pow(r, 7.0) / 7.0
+ pow(r, 9.0) / 9.0);
}
}
static double ln10 = myln(10.0);
static double mylog(double x) {
return x / ln10;
}
int main(int argc, char **argv) {
int i, j;
f[0] = 1.0;
for (i = 1; i < 14; i++)
f[i] = i * f[i - 1];
ln10 = myln(10.0);
int deg = 60 * 60;
int nsteps = 180 * deg;
double step = M_PI / nsteps;
double ssum;
double csum;
double a, s, c, t = 0.0;
double ls, lc;
for (j = 1; j < 11; j++) {
ssum = 0.0;
csum = 0.0;
for (i = 0; i <= nsteps; i++) {
a = i * step;
s = fsin(a);
ls = mylog(myln(s));
ssum += s;
c = fcos(a);
lc = mylog(myln(c));
csum += c;
if ((i % (10 * deg)) == 0) {
if (c != 0.0)
t = s / c;
printf("%3d %11.8f %11.8f %11.8f %15.8e\n",
(i / deg), a, s, c, t);
printf(" %15.8e %15.8e\n", ls, lc);
}
}
}
printf("%15.8e\n", ssum);
printf("%15.8e\n", csum);
}


Come si vede i risultati non sono quelli sperati: incredibilmente lento, 3.55 volte quello del Fortran! C’è qualcosa che non va, sicuro.
G. Lipari ottiene risultati simili. Ma, ricordate i classici: “Quando il gioco si fa duro, i duri cominciano a giocare” (“When the goin’ gets tough the tough get goin’”), Animal House 1978.
E difatti trova il colpevole e propone la soluzione per velocizzare l’esecuzione. La funzione pow() usa double anche per l’esponente, nel caso in esame questo è sempre intero per cui sostituendo tutte le pow() con mypow() definita così definita
inline double mypow(double x, int y) {
double r = 1;
for (int i=0; i<y; i++) r *= x;
return r;
}
Eh sì, semplice adesso che la vedi! xy è nient’altro che x moltiplicato per sé stesso y volte
si ha:
#include <stdio.h>
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
static double f[14];
inline double mypow(double x, int y) {
double r = 1;
for (int i=0; i<y; i++) r *= x;
return r;
}
static double fsin(double a) {
return a - mypow(a, 3) / f[3] + mypow(a, 5) / f[5]
- mypow(a, 7) / f[7] + mypow(a, 9) / f[9]
- mypow(a, 11) / f[11] + mypow(a, 13) / f[13];
}
static double fcos(double a) {
return 1.0 - mypow(a, 2) / f[2] + mypow(a, 4) / f[4]
- mypow(a, 6) / f[6] + mypow(a, 8) / f[8]
- mypow(a, 10) / f[10] + mypow(a, 12) / f[12];
}
static double myln(double x) {
if (x == 0.0) {
return -1.0e20;
} else {
double r = (x - 1.0) / (x + 1.0);
return 2.0 * (r + mypow(r, 3) / 3.0
+ mypow(r, 5) / 5.0
+ mypow(r, 7) / 7.0
+ mypow(r, 9) / 9.0);
}
}
static double ln10 = myln(10.0);
static double mylog(double x) {
return x / ln10;
}
int main(int argc, char **argv) {
int i, j;
f[0] = 1.0;
for (i = 1; i < 14; i++)
f[i] = i * f[i - 1];
ln10 = myln(10.0);
int deg = 60 * 60;
int nsteps = 180 * deg;
double step = M_PI / nsteps;
double ssum;
double csum;
double a, s, c, t = 0.0;
double ls, lc;
for (j = 1; j < 11; j++) {
ssum = 0.0;
csum = 0.0;
for (i = 0; i <= nsteps; i++) {
a = i * step;
s = fsin(a);
ls = mylog(myln(s));
ssum += s;
c = fcos(a);
lc = mylog(myln(c));
csum += c;
if ((i % (10 * deg)) == 0) {
if (c != 0.0)
t = s / c;
printf("%3d %11.8f %11.8f %11.8f %15.8e\n",
(i / deg), a, s, c, t);
printf(" %15.8e %15.8e\n", ls, lc);
}
}
}
printf("%15.8e\n", ssum);
printf("%15.8e\n", csum);
}

OK, adesso ottengo

Nel mio caso la nuova versione è 4.15 volte più veloce (meno del valore annunciato dal dr.prof. non so se sbaglio qualcosa. O forse il mio ‘puter è meno performante: non sembra ci siano variazioni tra il dichiarare inline o no la mypow().
Un’altra cosa ancora: secondo questa pagina dovrebbe essere possibile velocizzare la pow() passandogli un intero come secondo parametro; dalle prove fatte non mi risulta.
Ho provato a ricompilare la versione Fortran con l’opzione -O3 ma non ci sono variazioni.
E con Java? Secondo me dovrebbe funzionare. Non mi sembra esista la direttiva inline ma ne so poco.
import static java.lang.System.out;
class jc10l {
static double f[];
static double ln10 = myln(10.0);
public static double mypow(double x, int y) {
double r = 1;
for (int i = 0; i < y; i++) r *= x;
return r;
}
public static double fsin(double a) {
return a - mypow(a, 3) / f[3] + mypow(a, 5) / f[5]
- mypow(a, 7) / f[7] + mypow(a, 9) / f[9]
- mypow(a, 11) / f[11] + mypow(a, 13) / f[13];
}
public static double fcos(double a) {
return 1.0 - mypow(a, 2) / f[2] + mypow(a, 4) / f[4]
- mypow(a, 6) / f[6] + mypow(a, 8) / f[8]
- mypow(a, 10) / f[10] + mypow(a, 12) / f[12];
}
public static double myln(double x) {
if(x == 0.0) {
return -1.0e20;
} else {
double r = (x - 1.0) / (x + 1.0);
return 2.0 * (r + mypow(r, 3) / 3.0
+ mypow(r, 5) / 5.0
+ mypow(r, 7) / 7.0
+ mypow(r, 9) / 9.0);
}
}
public static double mylog(double x) {
return x / ln10;
}
public static void main(String args[]) {
int i, j;
f = new double[14];
f[0] = 1.0;
for (i = 1; i < 14; i++) {
f[i] = i * f[i - 1];
}
int deg = 60 * 60;
int nsteps = 180 * deg;
double step = Math.PI / nsteps;
double ssum;
double csum;
double a, s, c, t = 0.0;
double ls, lc;
for (j = 1; j < 11; j++) {
ssum = 0.0;
csum = 0.0;
for (i = 0; i <= nsteps; i++) {
a = i * step;
s = fsin(a);
ls = mylog(myln(s));
ssum += s;
c = fcos(a);
lc = mylog(myln(c));
csum += c;
if ((i % (10 * deg)) == 0) {
if (c != 0.0) {
t = s / c;
}
out.printf("%3d %11.8f %11.8f %11.8f %15.8e\n",
(i / deg), a, s, c, t);
out.printf(" %15.8e %15.8e\n",
ls, lc);
}
}
out.println(ssum);
out.println(csum);
}
}
}

Ehi! quasi veloce come il C, e comunque più eloce del Fortran!

Ulteriore prova: sostituire l’operatore **con mypow() per il Fortran. Non credo sia il caso di riportare il codice, ecco perché:

Quindi in conclusione (spero questa volta sia davvero tale) il codice C è il più performante. Subito dopo Java e il Fortran. L’operatore ** del Fortran è molto più efficiente di una funzione che esegue un ciclo di moltiplicazioni; oltretutto si paga il costo della chiamata.
C’è poi la domanda (eterna) che continua a non avere una risposta convincente per me: confrontando i listati non trovo che il codice Fortran sia più facile; OK è più simile al Basic e siamo abituati da tempo (per la versione formatata, quella per le schede) ma sono motivi validi e sufficienti?
Adesso mi cacciano

Commenti
Ispirato da questo post mi sono fatto la domanda, forse non fondamentale per lo sviluppo della Computer Science: “Quale compilatore C gratuito per windows produce il codice più veloce?”. Non potevo non darmi una risposta e fare qualche esperimento a partire dal tuo codice di test e 5 tra compilatori e IDE C gratuiti per Windoz (Cygwin, Mingw, Dev-C++, LCC e PellesC). MingW produce il codice più veloce (Dev-Cpp usa una versione di MingW gcc). Tant’è!
>./speedtest
compilatore Cygwin gcc:
real 0m23.530s
user 0m23.275s
sys 0m0.046s
compilatore Dev-C++:
real 0m12.467s
user 0m0.015s
sys 0m0.015s
compilatore MingW gcc:
real 0m12.554s
user 0m0.000s
sys 0m0.015s
compilatore Lcc:
real 0m14.642s
user 0m0.000s
sys 0m0.000s
compilatore PellesC:
real 0m16.713s
user 0m0.000s
sys 0m0.000s
>
Saluti.
Trackback
[...] ad utilizzarlo sui programmini che Juhan ci ha proposto per fare confronti fra il Fortran e vari altri linguaggi. Cominciamo dalla versione C/C++ di tale [...]