Newsgroup: it.comp.java
Titolo: Prestazioni Java

Sto imparando Java (vengo dal C++, ho usato anche C e 
VB.NET
).
Voglio scrivere i miei programmi in Java perchï¿œ m'interessa il fatto che 
il programma possa girare praticamente senza problemi su SO diversi. Per 
questo sono anche disposto ad accettare il fatto di non avere le stesse 
performance che avevo con il C++.
 
Tuttavia voi che conoscete Java certamente meglio di me, dato che io sto 
imparando, aiutatemi a programmare nel modo piï¿œ corretto Java, cosï¿œ 
anche da diminuire questo gap, di cui, dalle mie prime prove, in alcuni 
casi si ï¿œ dimostrato accettabile, in altri casi, pesante. Questo 
probabilmente deriva in gran parte da errori che commetto io, in parte 
sarï¿œ anche fisiologico, (ï¿œ inutile che ce la raccontiamo, il codice 
nativo ï¿œ il codice nativo) penso che comunque esista un modo per 
scrivere programmi piï¿œ efficienti.
 
Andiamo nel concreto della mia (opinabile) esperienza...
Ho preso spunto da test che avevo fatto mesi fa (prima di cimentarmi con 
Java) in cui facevo girare lo stesso algoritmo ma scritto in linguaggi 
diversi e cronometravo i tempo. Gli algoritmi non erano "ottimizzati" ma 
riportati 1:1 in differenti linguaggi. Ben sapendo che sfruttando alcune 
"peculiaritï¿œ" di ciascun linguaggio si sarebbe potuto fare "ben meglio".
 
Nel test che avevo fatto c'era un programma che lavora sugli interi e 
l'altro che lavora in virgola mobile.
 
L'articolo per chi fosse curioso ï¿œ:
http://www.paoloferraresi.it/images/PDF/articoli/Rivisitazione%20degli%20strumenti%20informatici%20per%20il%20calcolo%20e%20la%20simulazione.pdf
 
 
Ecco il programma che testa la velocitï¿œ in virgola mobile.
Tutti i test (sia quelli dell'articolo, sia questo in Java) sono stati 
eseguiti sul mio vecchio PC con CPU i7 940 a 2.9 Ghz.
 
Ecco la variante Java del programma per il calcolo in virgola mobile: 
integra l'equazione differenziale y''(x) = -0.5 per x da 0 a 10 con il 
metodo di Eulero.
 
public class Application {
     static void linspace(double x[],double x1, double x2) {
         final int N = x.length; final double t = (x2-x1)/(N-1); double 
cx = x[0];
         for(int i = 0;i<N;++i) { x[i]=cx; cx+=t; }
     }
     static void Eulero2g(double x[],double y2[],double y0, double 
yp0,double y1[],double y[],int N) {
         final double passo = x[1]-x[0];
         y[0]=y0; y1[0]=yp0; yp0+= 0.5d * passo * y2[0];
         for(int i = 1;i<=N-1;++i) {
             double passoy2 = passo*y2[i];
             y[i]=y[i-1]+passo*yp0; y1[i]=y1[i-1]+passoy2; yp0+=passoy2;
         }
     }
     static double trapz(double x[], double y[],double Q) {
         final int N = x.length - 1; final double h = 0.5d*(x[1]-x[0]);
         for(int i = 0;i<N;++i) Q+=(y[i]+y[i+1])*h;
         return Q;
     }
     static public void main(String args[]) {
         long t1 = System.currentTimeMillis(); final int NP = 10000000;
         double x[] = new double[NP+1]; double y2[] = new double[NP+1];
         double y1[] = new double[NP+1]; double y[] = new double[NP+1];
         linspace(x,0d,10d); // crea uno spazio lineare di NP+1 punti 
tra 0 e 10
         for(int i = 0;i<=NP;++i) y2[i]=-0.5d; // riempie y2 con la 
derivata seconda
         Eulero2g(x,y2,0.0,2.5,y1,y,NP+1); // integra l'equazione 
differenziale
         double Q = trapz(x,y,0.0);
         long elapsed = System.currentTimeMillis()-t1;
         System.out.println("Secondi impiegati : " + (double)elapsed/1000d);
         System.out.println("Q = "+Q);
     }
}
 
ha impiegato 0.176s su un Windows7 64 bit e JVM a 64 bit, 0.202s se la 
JVM ï¿œ a 32 bit.
In C++ ottenevo 0.125s, (ma anche 0.187s a seconda del compilatore).
Non ï¿œ una differenza a mio avviso significativa. E' piï¿œ o meno in linea 
con quanto mi aspettavo.
 
Il crivello di Eratostene invece mi da delle differenze incredibili.
 
public class Application {
     static final int NP = 100000000;
     static int Sieve(int N) {
         List<Boolean> v = new ArrayList<>(Collections.nCopies(N+1, true));
         List<Integer> M = new ArrayList<>();
         // crivello di Eratostene
         for(int i=2;i*i<=N;++i) {
             if (v.get(i)) {
                 for(int j=i*i;j<=N;j+=i) {
                     v.set(j, Boolean.FALSE);
                 }
             }
         }
         // aggiunge i numeri primi alla lista
         for(int i=2;i<=N;++i) {
             if (v.get(i)) M.add(i);
         }
         return M.size();
     }
     static public void main(String args[]) {
         long t1 = System.currentTimeMillis();
         int j = Sieve(NP);
         long elapsed = System.currentTimeMillis()-t1;
         System.out.println("Secondi impiegati : " + (double)elapsed/1000d);
         System.out.println("Trovati " + j + " numeri primi.");
     }
}
 
Impiega oltre 16 secondi su JVM a 64 bit. Sulla 32 non ho fatto prove... 
ma mi aspetto molto meno (a differenza del programma di prima), dato che 
indicizza grossi vettori.
I risultati sono clamorosi: in C++ con Visual Studio 2010 impiega 1.1s, 
e con il Borland C++ 3.3s.
Persino in 
VB.NET
 impiego 1.4s.
 
Modificando il programma ottengo:
 
import java.util.*;
public class Application {
     static final int NP = 100000000;
     static int Sieve(int N) {
         boolean v[] = new boolean[N+1];
         Arrays.fill(v, true);
         // crivello di Eratostene
         for(int i=2;i*i<=N;++i) {
             if (v[i]==true) {
                 for(int j=i*i;j<=N;j+=i) {
                     v[j]=false;
                 }
             }
         }
         int count = 0;
         for(int i = 2;i<=N;++i) if (v[i]) ++count;
         return count;
     }
     static public void main(String args[]) {
         long t1 = System.currentTimeMillis();
         int j = Sieve(NP);
         long elapsed = System.currentTimeMillis()-t1;
         System.out.println("Secondi impiegati : " + (double)elapsed/1000d);
         System.out.println("Trovati " + j + " numeri primi.");
     }
}
 
che impiega 1.4s, ma ovviamente non c'ï¿œ piï¿œ corrispondenza 1:1 con il 
programma C++. Anche il programma in C++ senza la classe 
std::vector<bool> ma con un array di bool andrebbe piï¿œ veloce.
 
Altra cosa: se se sostituisco a boolean v[] un Boolean v[], per usare 
Arrays.setAll(v, s->true); o anche parallelSetAll e poi conto i numeri 
primi con Arrays.stream(v).filter(s->s==true).count(); ottengo un 
rallentamento. l'uso della funzione "parallel" addirittura rallenta 
(evidentemente la creazione dei thread ha il suo peso).
 
Per la mia esperienza, ï¿œ meglio utilizzare collections e stream il meno 
possibile se vuoi velocitï¿œ. Almeno quando ï¿œ noto il numero di elementi, 
usare sempre un Array. Ma forse sono io che devo ancora capire la 
filosofia di questo lingaggio.
 
Sbaglio? C'ï¿œ qualcosa che non so? Mi potete dare consigli?
Ho imparato la sintassi del linguaggio, ma dove posso imparare il modo 
di usarne davvero le peculiaritï¿œ per scrivere software efficiente? C'ï¿œ 
qualche articolo ben fatto? Quelli che ho trovato dicono le solite 
cose... mi sembrano cose generiche (loop unrolling e altre cose giï¿œ 
sentite).
Questo argomento mi sta abbastanza a cuore, ad esempio io prima del C++ 
programmavo in C, e quando sono passato al C++ mi dicevano che il 
software sarebbe stato piï¿œ lento... invece dopo averlo imparato bene, 
non c'era piï¿œ differenza.
 
Grazie, ciao.

 Sbaglio? C'� qualcosa che non so? Mi potete dare consigli?
 
sbagli nel senso che stai tentando di comparare frutta con animali.
Se vuoi comparare la velocita' di due linguaggi, o meglio, del JIT della
JVM con quello che butta fuori un compilatore C++, devi fare in modo che
giochino ad armi pari, cioe' usare "cose" che entrambi i linguaggi
hanno, quindi usare piu' o meno solo tipi primari (int, float,..) e
evitare roba della std e API java, che non si sa bene come sono
implementate.
 
Ciao Jack
-- 
Yoda of Borg am I! Assimilated shall you be! Futile resistance is, hmm?


Ciao e grazie innanuzitutto per la celere risposta.
Come diceva un celebre calciatore (noto letterato) - "sono completamente 
d'accordo, a met�". :)
Da un punto di vista tecnico le tue osservazioni non fanno una piega, da 
un punto di vista "pratico" per� mi lasciano perplesso: se ho un 
algoritmo scritto in C++ che usa std::vector<int> (mettiamo che lo 
faccia a ragion veduta, non come nel mio esempio, dove la cosa era 
"voluta" ma avrei potuto benissimo usare un array in tutti i casi), come 
lo implementeresti in Java, con un int []?
 
Diciamo che in pratica a std::vector del C++ in Java si contrappone 
List. (almeno negli esempi sui libri che ho visto, quando ci sono dei 
vettori di dimensioni non note a priori si usa List).
 
Se un programma C++ con gli array � circa veloce come uno in Java con 
gli array mentre uno in C++ con std::vector � molto pi� veloce di uno in 
Java con List<> comunque anche se quel che dici � vero, per me � un 
problema. (magari anche dovuto alla mia ignoranza... non ce l'ho con 
Java, che comunque voglio usare sempre di pi�).
 
Poi anche il fatto di Boolean o boolean... io preferisco il secondo, ma 
prova in Java a fare una List di boolean... (tu lo sai benissimo cosa 
succede). In C++ i container trattano benissimo anche tipi primitivi...
La mia sensazione � le collezioni Java non siano efficienti come quelle 
del C++, a causa delle limitazioni del linguaggio, che obbliga all'uso 
di oggetti.
 
Ciao, buona Domenica.
 

> implementate.
 
Ti confermo che senza ricorrere a container generici, il programma:
 
public class Application {
     static final int N = 100000000;
     static public void main(String args[]) {
         long t1 = System.currentTimeMillis();
         final boolean v[] = new boolean[N+1];
         for(int i=0;i<=N;++i) v[i]=true;
 
         // Crivello di Eratostene
	for (int i=2;i*i<=N;++i) {
             if (v[i]) {
                 for (int j = i*i;j<=N;j+=i) {
                     v[j]=false;
                 }
             }
         }
         int conta = 0;
         for(int i = 2;i<=N;++i) if (v[i]) ++conta;
 
         long elapsed = System.currentTimeMillis()-t1;
         System.out.println("Secondi impiegati : " + (double)elapsed/1000d);
         System.out.println("Trovati " + conta + " numeri primi.");
     }
}
 
strappa lo stesso tempo di 1.3s sia in C++ che in Java.
Ribadisco: da quel che vedo Java � veloce, ma l'uso della Collezioni 
generiche rallenta molto di pi� Java che il C++.
 
Ciao.
 

 generiche rallenta molto di pi� Java che il C++.
 
perche' le liste in Java sono implementate come liste, mentre in C++
almeno il tuo vector<bool> e' implementato in altro modo.


 rallentamento
 
Non usare i lambda, allo stato attuale sono ancora troppo immaturi, 
soprattutto a livello prestazionale
 
Enrico


Si avevo immaginato. Come anche gli stream (che poi spesso le cose vanno 
assieme). Grazie.
Comunque a parte questo Java mi piace, diventer� il mio linguaggio 
perch� comunque mi piace l'idea di non dover riscrivere una versione per 
Windows, una per Solaris, una per Mac... e devo dire che comunque come 
velocit� � migliorato parecchio, anni fa mi sembrava incredibilmente pi� 
lento, adesso vedo che se non si cade in certe "trappole" � possibile 
scrive codice efficiente.
Ciao e grazie a tutti!