Compilati VS interpretati passo 2 (The compiler strikes back)

Ciao!!!

Spero che i fans di guerre stellari non me ne vogliano per la citazione del titolo 🙂 Dopo settimane di ritardo e alcune discussioni con Ari (come chi è? … non avete letto i commenti al primo articolo?) sono tornato con ulteriori test e risultati da proporvi.
In realtà questa seconda parte doveva essere diversa, ma viste le giuste osservazioni fatte da Ari ho deciso di cambiarlo per dare ascolto a chi questo blog lo legge e quindi da un senso e uno stimolo a chi qui scrive esclusivamente per passione.
Torniamo a noi! Ari mi ha fatto giustamente notare che le routines di python che leggono i file sono scritte in C. Quindi in realtà non è python che legge velocemente i file, ma il codice C dietro le quinte che fa il lavoro sporco.
Ne è seguita una piccola discussione sulla valenza da dare al mio precedente articolo e su quali sarebbero stati i test giusti da fare… e di seguito troverete i risultati che ho ottenuto 🙂
Ho giocato anche a fare la “Cassandra” di turno (non ditemi che non sapete chi è Cassandra 😦 ) per cui vi riporto le mie sibilline parole:

“Per cui nella seconda parte dell’articolo faremo i test che suggerisci, ma ho il sospetto che vedremo dei risultati interessanti, almeno in base a quella che è stata la mia esperienza con Java.”

Ma andiamo a cominciare…
Il test eseguito è molto semplice. Ho fatto calcolare il fattoriale da 1 a 10.000 con una bella procedura non ricorsiva e poi ho fatto un “ottimo” sort con una procedura non efficente (ditemi quanti di voi saprebbero scrivere a memoria un algoritmo di quicksort 🙂 )

Vediamo i sorgenti e i risultati ottenuti per python, C, Objective C e Java.

#!/usr/bin/env python2.6
# -*- coding: utf-8 -*-

import time
import array

MAXLOOP = 10000
nArray = array.array('d', range(MAXLOOP))

t0 = time.time()
print "Tempo inizio caricamento array: ", time.strftime("%H:%M:%S")
for i in range(MAXLOOP):
  f = float(1)

  for a in range(i):
    f = f * a

  nArray[i] = i + f

t1 = time.time()
print "Tempo fine caricamento array: ", time.strftime("%H:%M:%S")
print "Tempo di caricamento dei dati: ", t1 - t0, " secondi"

t0 = time.time()
print "Tempo inizio ordinamento: ", time.strftime("%H:%M:%S")

# ordinamento in maniera decrescente
for i in range(MAXLOOP):
  dMax = nArray[i];

  for j in range (i + 1, MAXLOOP):
    if nArray[j] > dMax:
      dTemp = nArray[j];
      nArray[j] = dMax;
      nArray[i] = dTemp;
      dMax = dTemp;

t1 = time.time()
print "Tempo fine ordinamento: ", time.strftime("%H:%M:%S")
print "Tempo di caricamento dei dati: ", t1 - t0, " secondi"

I tempi sono stati sulla mia macchina freebsd sono stati:

– 11,06 s per il caricamento dell’array con calcolo del fattoriale
– 42,07 s per il sort

Vediamo il C come si comporta:

#include <stdio.h>
#include <time.h>

const int MAXLOOP = 10000;

int main (int argc, const char * argv[])
{
  time_t startTime;
  time_t endTime;
  struct tm * timeinfo;

  long double nArray[MAXLOOP];
  int i;

  time(&startTime);
  timeinfo = localtime(&startTime);
  printf("Tempo inizio caricamento array: %s", asctime(timeinfo));

  for (i = 0; i < MAXLOOP; i++)
  {
    // Calcolo del fattoriale
    long double f = 1;
    int a;

    for(a = 1; a <= i; a++)
    {
      f *= a;
    }

    nArray[i] = i * f;
  }

  time(&endTime);
  timeinfo = localtime(&endTime);
  printf("Tempo fine caricamento array: %s", asctime(timeinfo));

  time(&startTime);
  timeinfo = localtime(&startTime);
  printf("Tempo inizio ordinamento: %s", asctime(timeinfo));
  // ordinamento in maniera decrescente
  for (i = 0; i < MAXLOOP; i++)
  {
    double dMax = nArray[i];
    int j;
    for (j = i + 1; j < MAXLOOP; j++)     
    {       
      if (nArray[j] > dMax)
      {
        double dTemp = nArray[j];
        nArray[j] = dMax;
        nArray[i] = dTemp;
        dMax = dTemp;
      }
    }
  }
  time(&endTime);
  timeinfo = localtime(&endTime);
  printf("Tempo fine ordinamento: %s", asctime(timeinfo));

  return 0;
}

Tempi:

– 4 s per il caricamento dell’array con calcolo del fattoriale
– meno di 1 s per il sort

Adesso è il turno di objective C.

#import <Foundation/Foundation.h>

const int MAXLOOP = 10000;

int main (int argc, const char * argv[])
{
  NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

  NSDate *startTime;
  NSDate *endTime;

  NSMutableArray *nArray = [[NSMutableArray alloc] initWithCapacity: MAXLOOP];
  int i;

  startTime = [NSDate date];
  NSLog(@"Tempo inizio caricamento array: %@", startTime);

  for (i = 0; i < MAXLOOP; i++)
  {
    // Calcolo del fattoriale
    long double f = 1;
    int a;

    for(a = 1; a <= i; a++)
    {
      f *= a;
    }

    [nArray addObject: [NSNumber numberWithDouble: i * f]];
  }

  endTime = [NSDate date];
  NSLog(@"Tempo fine caricamento array: %@", endTime);

  startTime = [NSDate date];
  NSLog(@"Tempo inizio sort: %@", startTime);

  // ordinamento in maniera decrescente
  for (i = 0; i < MAXLOOP; i++)
  {
    double dMax = [[nArray objectAtIndex: i] doubleValue];
    int j;
    for (j = i + 1; j < MAXLOOP; j++)     
    {       
      if ([[nArray objectAtIndex: j] doubleValue] > dMax)
      {
        double dTemp = [[nArray objectAtIndex: j] doubleValue];
        [nArray replaceObjectAtIndex: j withObject: [NSNumber numberWithDouble: dMax]];
        [nArray replaceObjectAtIndex: i withObject: [NSNumber numberWithDouble: dTemp]];
        dMax = dTemp;
      }
    }
  }
  endTime = [NSDate date];
  NSLog(@"Tempo fine sort: %@", endTime);

  [pool drain];
  return 0;

}

Tempi:

– 4 s per il caricamento dell’array con calcolo del fattoriale
– 2,13 s per il sort

Per finire passiamo a Java.

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SortTest {

    static final int MAXLOOP = 10000;

    public static void main(String[] args) {
        // TODO code application logic here
        int i;
        double[] nArray = new double[MAXLOOP];

        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        Date startTime = new Date();
        System.out.printf("Tempo inizio caricamento array: %s\n", dateFormat.format(startTime));
        long start = System.nanoTime();

        for (i = 0; i < MAXLOOP; i++)
        {
            // Calcolo del fattoriale
            double f = 1;
            int a;

            for(a = 1; a <= i; a++)
            {
                f *= a;
            }

            nArray[i] = i * f;
        }
        long end = System.nanoTime();

        Date endTime = new Date();
        System.out.printf("Tempo fine caricamento array: %s\n", dateFormat.format(endTime));

        System.out.printf("Tempo caricamento array: %d ms\n", (end - start) / 1000000);

        startTime = new Date();
        System.out.printf("Tempo inizio ordinamento: %s\n", dateFormat.format(startTime));
        start = System.nanoTime();

        // ordinamento in maniera decrescente
        for (i = 0; i < MAXLOOP; i++)
        {
            double dMax = nArray[i];
            int j;
            for (j = i + 1; j < MAXLOOP; j++)             
            {                 
                if (nArray[j] > dMax)
                {
                    double dTemp = nArray[j];
                    nArray[j] = dMax;
                    nArray[i] = dTemp;
                    dMax = dTemp;
                }
            }
        }
        end = System.nanoTime();

        endTime = new Date();
        System.out.printf("Tempo fine ordinamento: %s\n", dateFormat.format(endTime));

        System.out.printf("Tempo ordinamento array: %d ms\n", (end - start) / 1000000);
    }
}

Tempi:

– 248 ms per il caricamento dell’array con calcolo del fattoriale
– 278 ms per il sort

Quindi riassumiamo il tutto in un formato più leggibile.

Caricamento
+ fattoriale

Sort

Python

11,06 s

42,07 s

C

4 s

< 1 s

Objective-C

4 s

2,13 s

Java

0,25 s

0,28 s

Per cui come diceva Ari, il compilato è più efficente. Possiamo notare la differenza di tempi nel sort tra objective-c e C ma l’array usato in objective-c è un NSMutableArray che vuole oggetti al suo interno, per cui è presente un overhead dovuto alla creazione dell’oggetto… in soldoni non stiamo spostando dei semplici long double come in C.
Infine la sorpresa “cassandriana” di Java… che è un bytecode che viene interpretato da una virtual machine.
Anche python genera del bytecode… evidentemente meno efficente di quello java.

Ma torniamo a noi con le conclusioni di questa seconda parte. Comincio mettendo le mani avanti. Io sono un programamtore “stagionato” o “antico” questo vuol dire che mi piace il codice che funziona velocemente e non mi ammazza un processore con 6 core e GHz di potenza se lancio 2 istanze di un programma. Anche se preferisco il C/C++ o Delphi, non mi faccio trascinare dalle mode e ho capito oramai da anni che il linguaggio “buono” è quello che ti permette di staccare il cliente dalla propria giugulare prima di finire dissanguati 🙂 per cui se il mio software deve leggere file, fare sort e usare le regular expression allora python mi permette con poche righe di codice di fare tutto ciò e con poche modifiche di spostarmi su varie piattaforme.
Se invece il problema da risolvere è in ambiente real time o prevede calcoli a go-go o pochissima RAM come in un sistema embedded allora la scelta giusta è il C (provate a far girare python su una centralina di un’auto 😛 ). Se volete lavorare su MAC e scrivere PDF e vedere le vostre applicazioni volare passate ad objective-C.
La scusa della battaglia compilati VS interpretati era solo un pretesto per scrivere del codice e sfatare preconcetti e come ho detto in più occasioni non voleva essere un test scientifico che avrebbe richiesto uno rigore diverso.

L’importante è “divertirsi” ed imparare. Senza diventare gli hooligans dei chip. Ciao e alla prossima

Posta un commento o usa questo indirizzo per il trackback.

Commenti

  • juhan  Il 6 dicembre 2011 alle 09:59

    Dikiyvolk, la “s”!
    Quando ero piccolo io si diceva che quelli che non la usavano erano gli afro-americani. Non sapevo dei calabro-italiani. Ma va bene anche così 😉

  • juhan  Il 6 dicembre 2011 alle 10:15

    C’è anche un piccolo problema nel codice riportato (quelli in C) il < viene visto come carattere speciale se lo inserisci quando sei in “Visuale” e non in “HTML”, cosa che credevo capitasse solo a me. Hai perso anche un a-capo, nel C, in un for.

    Vedi quando qualcuno parla male di Python io divento furioso e carico a testa bassa come un toro 😀
    No non sto parlando della squadra di calcio più bella del mondo 8)

    • dikiyvolk  Il 6 dicembre 2011 alle 11:03

      Ciao juhan,

      Grazie per le segnalazioni. Ho messo tutto a posto. Sai che la Calabria è vicina all’Africa 🙂 un mio parente diceva i calafricani 🙂
      Ho dovuto martellare un sacco di codice che nel copia-incolla aveva preso strane forme 😦

      Dai non ho parlato male di python 🙂 ho detto che è lento… che non sempre è un difetto 😀

      Ciao e grazie ancora.

  • al3hex  Il 7 dicembre 2011 alle 01:04

    Non l’avrei mai detto che Java potesse essere così efficiente, addirittura più del C.
    Per curiosità personale, ho eseguito sulla mia macchina il test in python utilizzando però pypy (interprete python alternativo che utilizza un compilatore JIT). Tempi:
    – caricamento array + calcolo fattoriale: 0,65 – 0,75 s
    – sort: 1,02 – 1,15 s
    Risultati davvero niente male, soprattutto se confrontati con quelli ottenuti da python stesso.

    • dikiyvolk  Il 7 dicembre 2011 alle 08:41

      Questa sera farò le prove di tutte le versioni dei software usati per i test sul mio mac in maniera da avere lo stesso ambiente e processore. I precedenti test erano stati fatti su una macchina virtuale con freebsd, ma non esiste (o meglio non ho trovato) il package di pypy. Ho il terrore di scoprire gli errori della compilazione dai sorgenti 🙂 e un sacco di lavoro da finire in ufficio… sono curioso anche io di vedere i risultati così juhan smetterà di dirmi che parlo male di python 😀

  • Lightuono  Il 7 dicembre 2011 alle 13:06

    Per l’ordinamento in maniera decrescente avrei scritto :

    # ordinamento in maniera decrescente
    for i in range(MAXLOOP):

    for j in range (i + 1, MAXLOOP):
    if nArray[j] > nArray[i]:
    dTemp = nArray[j];
    nArray[j] = nArray[i];
    nArray[i] = dTemp;

    Interessante articolo 🙂

  • Dikiyvolk  Il 7 dicembre 2011 alle 21:10

    Rieccomi dopo aver fatto i test, sul computer di casa. Allora per prima cosa il sorgente del codice C era sbagliato 😦 nel copia-incolla erano spariti i nomi degli include… chiedo venia ma non me ne ero accorto…
    Comunque nonostante la stanchezza cronica ho fatto i test. I risultati sono stati

    – python
    9,75 s per il calcolo del fattoriale e 57,32 s per il sort
    – C
    3 s per il calcolo del fattoriale e < 1 s per il sort
    – objective-C
    2,62 s per il calcolo del fattoriale e 2,62 s per il sort (si sono proprio uguali)
    – Java
    0,11 s per il calcolo del fattoriale e 0,10 s per il sort
    – pypy
    1,39 s per il calcolo del fattoriale e 1,42 per il sort

    Scrivendo il sort crescente come suggerito da Lightuono che ha perfettamente ragione nel suo commento il tempo di esecuzione con pypy diventa:
    1,15 s per il calcolo del fattoriale e 1,39 per il sort

    Devo indagare sul perché Java è così veloce (qualcuno può suggerire?)… e comunque complimenty a pypy.

Trackback

Rispondi

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

Logo 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: