• Programmazione
  • Benchmark performance C, Python, MATLAB - GNU Octave, JavaScript, PHP

Dopo aver parlato di un confronto performance fra Julia, R, Python, MATLAB, vediamo ora un altro benchmark interessante: risoluzione numerica dell'integrale definito di una funzione gaussiana, confronto di prestazioni fra i seguenti linguaggi:

  • C, codice standard
  • C, codice ottimizzato
  • Python 3.9.2
  • MATLAB (GNU Octave 6.2.0)
  • JavaScript (script in HTML su Chromium 104.0.5112.101)
  • PHP 7.4.30

È stato fatto uso di una funzione per misurare il tempo di esecuzione del codice in secondi (end-start), 10 campionamenti per ogni caso, poi fatta la media e riportati i valori graficamente, normalizzandoli con C_opt (l'algoritmo scritto in C "ottimizzato"); quindi C_opt ha valore 1, gli altri più sono elevati e più sono inefficienti per risolvere questo algoritmo computazionale, richiedono quindi tempo maggiore. Infine l'immagine mostra tutti i risultati e il grafico di benchmark. Sistema operativo Linux Parrot OS 5.1, 64bit (Kernel Linux 5.18.0-14parrot1-amd64 x86_64).

Riportiamo di seguito il codice nei vari casi:

  • C, codice standard:
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <math.h>
    float f(float x);
    int main(){
    clock_t start = clock();
    float ris=0;
    int N=1000000;
    float a=0;
    float b=20;
    float h=(b-a)/N;
    int i;
    for(i=0;i<N;i++){
    ris+=h*f(a+i*h);
    }
    clock_t end = clock();
    printf("%f\n",(float)(end-start)/CLOCKS_PER_SEC);
    return 0;
    }
    float f(float x){
    return pow(1/(2*M_PI),0.5)*pow(M_E,(-0.5*x*x));
    }
  • C, codice ottimizzato (C_opt):
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <math.h>
    int main(){
    clock_t start = clock();
    register int N=1000000;
    register float ris=0;
    register float a=0;
    register float b=20;
    register float h=(b-a)/N;
    register int i;
    for(i=0;i<N;i++){
    ris+=pow(1/(2*M_PI),0.5)*pow(M_E,(-0.5*(a+i*h)*(a+i*h)));
    }
    clock_t end = clock();
    printf("%f\n",(float)(end-start)/CLOCKS_PER_SEC);
    return 0;
    }
  • Python 3.9.2:
    import time
    import numpy as np
    start=time.time()
    ris=0
    N=10**6
    a=0
    b=20
    h=(b-a)/N
    def f(x):
        return 1/(2*np.pi)**0.5*np.exp(-x**2/2)
    for i in range(0,N):
        ris+=h*f(a+i*h)
    print("ris=",ris)
    end=time.time()
    print(end-start,"s")
  • MATLAB (GNU Octave 6.2.0):
    clear all
    close all
    clc
    tic
    ris=0;
    N=1000000;
    a=0;
    b=20;
    h=(b-a)/N;
    for i=0:N
      ris=ris+1/(2*3.141592653589793)^0.5*2.718281828459045^(-0.5*(a+i*h)*(a+i*h));
      end
    toc
  • JavaScript (script all'interno di una pagina HTML):
    <html>
    <script>
    var start=performance.now();
    var ris=0;
    var N=10**6;
    var a=0;
    var b=20;
    var h=(b-a)/N;
    function f(x){
    return 1/(2*3.141592653589793)**0.5*2.718281828459045**(-0.5*x**2)
    }
    for(i=0;i<N;i++){
    ris+=h*f(a+i*h);
    }
    var end=performance.now();
    document.write("ris="+ris+"<br>")
    document.write(end-start+"ms")
    </script>
    </html>
  • PHP 7.4.30:
    <?php
    $start=microtime(TRUE);
    $ris=0;
    $N=10**6;
    $a=0;
    $b=20;
    $h=($b-$a)/$N;
    function f($x){
    return 1/(2*3.141592653589793)**0.5*2.718281828459045**(-0.5*$x**2);
    }
    for($i=0;$i<$N;$i++){
    $ris+=$h*f($a+$i*$h);
    }
    $end=microtime(TRUE);
    echo "ris=".$ris."\n";
    echo $end-$start."s";
    ?>

I risultati sono mostrati nella seguente immagine: mostra chiaramente che le performance migliori sono ottenute da C (sia "ottimizzato" che "standard"), PHP e JavaScript risultano accettabili, Python e MATLAB (GNU Octave) risultano estremamente inefficienti (67 e 92 volte più lenti del codice C ottimizzato).
Interessante anche la deviazione standard dei campionamenti, il tempo di esecuzione di C, così come PHP, ha bassa variabilità, valori sempre molto simili; JavaScript e MATLAB hanno più variabilità, Python ancora molta più variabilità.

Risultati (normalizzando rispetto a C_opt):

  • C_opt = 1
  • C, standard = 1,2367
  • Python = 67,16
  • MATLAB = 92,463
  • JavaScript = 3,84
  • PHP = 2,6289

benchmark-performance-C-Python-MATLAB-Java-Script-PHP

un anno dopo

Oltre a quanto già detto, vediamo un'ulteriore ottimizzazione per quanto riguarda il codice C (utile concettualmente, poiché estendibile ad altri progetti):

  • sostituire la funzione pow() con exp() e sqrt() rispettivamente, quando richieste (pow() va bene usarlo come elevamento a potenza generico, se abbiamo funzioni più precise per casi specifici, usiamo quelle)
  • minimizzare il numero di operazioni in particolare all'interno del ciclo, quindi ris+=exp(-0.5*(a+i*h)*(a+i*h)); mentre ris*=sqrt(1/(2*M_PI)); essendo di fatto una costante moltiplicativa (non dipende dalla variabile) lo si mette una tantum, al termine del ciclo
  • un eventuale uso delle macro del preprocessore (tema importante e poco noto, a cui dedicheremo sicuramente un approfondimento a parte!) non ha portato benefici in termini di performance, essendo di fatto un programma strutturalmente semplice e quindi di fatto sono già disponibili funzioni standard della libreria <math.h>
  • analogamente alle macro, anche definire Pi greco e il numero di Nepero come costanti (tramite direttiva #define) o semplici variabili, non cambia le performance rispetto ad usare M_PI e M_E rispettivamente, della libreria <math.h>
  • Nota: nel caso si volesse considerare gli estremi a e b come numeri interi (short / unsigned short, char / unsigned char) e non float, avremmo un ulteriore (notevole!) miglioramento, in questo caso però perde di validità generale (non potremmo fare uso di numeri decimali come estremi dell'intervallo), quindi dipende dal caso specifico, in questo caso li lasciamo come variabili di tipo float

Con i miglioramenti elencati, l'efficienza computazionale migliora moltissimo (praticamente raddoppia!). Passiamo infatti da 0,043-0,043 secondi a 0,021-0,022 secondi! La metà del tempo di esecuzione del codice, un miglioramento molto interessante 😀 in questo caso risparmiamo il confronto con gli altri linguaggi (anche la vecchia versione del codice C era già 67 volte più veloce di Python, 92 volte più veloce di MATLAB/Octave, per non "voler male" agli altri linguaggi, evitiamo dunque di agitare il coltello nella piaga).

2 mesi dopo

Nell'ultimo commento avevo anche fatto un accenno alle MACRO. Ne ho parlato più in dettaglio nella seguente discussione: Macro function in C: verso nuovi orizzonti di efficienza, caso di studio.

Vediamo un'applicazione delle MACRO anche in questo caso, quindi come cambia il tempo di esecuzione del codice, scrivnedo l'algoritmo in modo differente:

  • struttura originale (scrivere direttamente l'espressione, comunque con le varie ottimizzazioni discusse prima): i punti salienti sono questi:
    register unsigned int N=100000000;
    
    ris+=exp(-0.5*(a+i*h)*(a+i*h));
    tempo totale di esecuzione del codice: 0,563 secondi
  • fare uso delle MACRO: i punti salienti sono questi:
    #include <stdlib.h> //libreria necessaria se vogliamo usare le MACRO
    #define ADD(x,y) x+=y
    #define MULT(x,y) x*=y
    
    register unsigned int N=100000000;
    
    ADD(ris,h*exp(MULT(-0.5*(a+i*h),(a+i*h))));
    tempo totale di esecuzione del codice: 0,505 secondi

Vale a dire che con l'uso delle MACRO abbiamo ottenuto un guadagno delle performance del 10% circa. Ribadisco quindi che è una soluzione molto interessante quando si tratta di implementare funzioni abbastanza semplici per operazioni ripetute molte volte.

Powered by: FreeFlarum.
(remove this footer)