Ulteriori confronti, anche con C++


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 😦

Posta un commento o usa questo indirizzo per il trackback.

Commenti

  • Luciano  Il 1 ottobre 2012 alle 14:44

    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

  • Ottimizzare il codice « Ok, panico su 25 luglio 2012 alle 17:33

    […] 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 […]

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo di WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google photo

Stai commentando usando il tuo account Google. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.

%d blogger hanno fatto clic su Mi Piace per questo: