calcul mathematique simple, optimisation Java

calcul mathematique simple, optimisation Java - Java - Programmation

Marsh Posté le 24-09-2009 à 20:51:31    

ci dessous le code et le temps d'execution d'un petit programme tout simple, en C et en Java
 


$ cat cr.c
#include "math.h"
#define SIZEA 1024
#define SIZEB 4096
 
static double mat[SIZEA][SIZEB];
static double var[SIZEA];
static double cor[SIZEA][SIZEA];
 
int main(int argc, char** argv) {
 
        int i,j,k,l;
        for (i=0;i<SIZEA;i++) {
                for (j=0;j<SIZEB;j++) {
                        mat[i][j]=pow(-1,i)*i/(j+1.0);
                }
        }
        for (i=0;i<SIZEA;i++) {
                double mean;
                mean = 0;
                for (j=0;j<SIZEB;j++) {
                        mean+=mat[i][j];
                }
                mean/=SIZEB;
                double variance = 0;
                for (j=0;j<SIZEB;j++) {
                        variance+=pow((mat[i][j]-mean),2);
                }
                variance/=SIZEB;
                var[i]=variance;
        }
        // calculate correlation
        for (i=0;i<SIZEA;i++) {
                for (j=0;j<SIZEA;j++) {
                        cor[i][j]=0;
                        for(k=0;k<SIZEB;k++) {
                                cor[i][j]=mat[i][k]*mat[j][k]/(SIZEB*sqrt(var[i]*var[j]));
                        }
                }
        }
        return 0;
}
$ cat cr.java
class Cr {
 
public static void main(String [] args) {
 
        double[][] mat = new double[1024][4096];
        double[] var = new double[1024];
        double[][] cor = new double[1024][1024];
 
        int i,j,k,l;
        for (i=0;i<1024;i++) {
                for (j=0;j<4096;j++) {
                        mat[i][j]=Math.pow(-1,i)*i/(j+1.0);
                }
        }
        for (i=0;i<1024;i++) {
                double mean;
                mean = 0;
                for (j=0;j<4096;j++) {
                        mean+=mat[i][j];
                }
                mean/=4096;
                double variance=0;
                for (j=0;j<4096;j++) {
                        variance+=Math.pow((mat[i][j]-mean),2);
                }
                variance/=4096;
                var[i]=variance;
        }
        // calculate correlation
        for (i=0;i<1024;i++) {
                for (j=0;j<1024;j++) {
                        cor[i][j]=0;
                        for(k=0;k<4096;k++) {
                                cor[i][j]=mat[i][k]*mat[j][k]/(4096*Math.sqrt(var[i]*var[j]));
                        }
                }
        }
}
};
 
$ gcc -lm cr.c -o cr
$ gcc --version
gcc (Debian 4.3.4-2) 4.3.4
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
$ javac cr.java
$ java -version
java version "1.6.0_0"
OpenJDK Runtime Environment (IcedTea6 1.5) (6b16-4)
OpenJDK 64-Bit Server VM (build 14.0-b15, mixed mode)
$ time ./cr
87.9u 0.0s 1:27.92 99.9% 0+0k 0+0io 0pf+0w
$ time java Cr
117.5u 46.8s 1:24.47 194.6% 0+0k 0+112io 1pf+0w
 


 
 
 
Constatations:
 
- le temps CPU user+system consomme est plus faible pour le binaire C que pour le semi-binaire Java (Java 80% plus lent)
- le temps d’execution est plus lent de 3s sur 1min30s environ avec avantage pour Java
- C a tout son temps en “user” alors que Java a une repartition 70-30 pour user et system
 
Questions:
1) Pourquoi le temps CPU C est il bien plus faible que Java? Je m’attendais a ce que Java soit un peu plus lent, le temps de convertir le .class en instructions machines, mais pas dans de telles proportions?
2) Pourquoi C consomme t’il du temps user quasi uniquement, et Java du temps user et systeme?
3) Pourquoi le temps d’execution reel de Java est il plus court malgre le temps CPU quasi 2x plus long? Mon programme n’est pas multi-thread, donc ca ne devrait pas arriver?
 
 
 
Merci
 
 
--ztg
 
 

Reply

Marsh Posté le 24-09-2009 à 20:51:31   

Reply

Marsh Posté le 24-09-2009 à 20:57:25    

Code :
  1. for(k=0;k<4096;k++) {
  2.   cor[i][j]=mat[i][k]*mat[j][k]/(4096*Math.sqrt(var[i]*var[j]));
  3. }


tu es sûr de vouloir calculer cor[i][j] 4096 fois (et d'écraser la valeur précédente à chaque fois) ?
A mon avis il manque un "+" :o


Message édité par sligor le 24-09-2009 à 20:58:46
Reply

Marsh Posté le 24-09-2009 à 20:58:38    

il devrait y avoir un "+=" au lieu du "=", exact
 
mais a la limite ca ne change rien, vu que l'erreur est dans les deux, non?

Reply

Marsh Posté le 24-09-2009 à 20:59:29    

par contre ça risque de démultiplier le temps de calcul :o
si les compilo sont pas cons ils ont supprimé cette boucle


Message édité par sligor le 24-09-2009 à 20:59:51
Reply

Marsh Posté le 24-09-2009 à 20:59:58    

ce qui importe ici c'est que les deux fassent la meme chose, pas l'exactitude du calcul

Reply

Marsh Posté le 24-09-2009 à 21:00:16    

ok je modifie et je vois si ca change

Reply

Marsh Posté le 24-09-2009 à 21:04:19    

bon on pouvait s'y attendre, les deux executions sont plus lentes apres cette petite modif (merci sligor de l'avoir mentionnee, effectivement on aurait pu imaginer que le compilo avait optimise ca en prenant juste la 4096e valeur... mais en fait non)
 
nouveaux temps d'execution:
 

# gcc -lm cr.c -o cr && time ./cr
94.6u 0.0s 1:34.63 99.9% 0+0k 0+0io 0pf+0w
# javac cr.java && time java Cr
117.0u 47.4s 1:24.55 194.5% 0+0k 0+112io 1pf+0w


 
la question demeure

Reply

Marsh Posté le 24-09-2009 à 21:08:05    

il sont cons ces compilateurs [:ocolor]

 

Sinon: tu as des tableau énormes (~64Mo) donc tu exploses la mémoire cache et le CPU se tourne les pouces à attendre les datas à chaque fois.
Tu effectues des calculs compliqués sqrt float & div float: le cpu se tourne les pouces en attendant la fpu. La performance de la machine impacte beaucoup plus le résultat que les optimisations de compilation


Message édité par sligor le 24-09-2009 à 21:08:18
Reply

Marsh Posté le 24-09-2009 à 21:09:26    

un autre truc, dans la meme portion de code, je calcule k fois 4096*Math.sqrt(var[i]*var[j]) alors qu'une suffirait (en pratique je fais ca souvent pour pas faire exploser des limites hautes pour les entiers, mais ici ca a peu de sens)
 
me demande si gcc est assez intelligent pour optimiser ca, je teste

Reply

Marsh Posté le 24-09-2009 à 21:10:29    

>sligor
 
ok ca a du sens
 
maintenant, pourquoi cette difference entre l'execution C et Java?

Reply

Marsh Posté le 24-09-2009 à 21:10:29   

Reply

Marsh Posté le 24-09-2009 à 21:12:57    

au passage: -ffast-math peut être très utile: testé et approuvé :D

Reply

Marsh Posté le 24-09-2009 à 21:13:55    

deja teste, ca ralentit mon execution C de 5% (va savoir pourquoi)

Reply

Marsh Posté le 24-09-2009 à 21:16:46    

tu es sûr ? chez moi je gagne presque un rapport 10 juste en ajoutant cette option :D
Quelle version de gcc au passage ?

 

gcc -lm -O3 -mfpmath=sse -msse2 cr.c -o cr -ffast-math


Message édité par sligor le 24-09-2009 à 21:16:55
Reply

Marsh Posté le 24-09-2009 à 21:20:39    

apres modif du diviseur dans corr[i][j] (des deux cotes bien sur)
et test --fast-math (c'etait en fait un autre flag qui me donnait -5%, j'ai parle trop vite)
 
 


# gcc -lm cr.c -o cr && time ./cr
24.6u 0.0s 0:24.67 100.0% 0+0k 0+0io 0pf+0w
 
# gcc -lm -ffast-math cr.c -o cr && time ./cr
24.6u 0.0s 0:24.68 100.0% 0+0k 0+0io 0pf+0w
 
# javac cr.java && time java Cr
6.9u 1.1s 0:06.10 132.6% 0+0k 0+64io 1pf+0w
 


 
 
[:rofl]
 
je comprends plus rien :(

Reply

Marsh Posté le 24-09-2009 à 21:21:34    

ah ouais
 


# gcc -lm -O3 -mfpmath=sse -msse2 -ffast-math cr.c -o cr && time ./cr
5.8u 0.0s 0:05.89 99.8% 0+0k 0+0io 0pf+0w

Reply

Marsh Posté le 24-09-2009 à 21:22:39    

ca va mieux en effet

Reply

Marsh Posté le 24-09-2009 à 21:24:53    

en fait -O3 avec -ffast-math permet à GCC de vectoriser le code
tu peux utiliser "-ftree-vectorizer-verbose=5" pour voir ce qu'il arrive à vectoriser. Mais de toute façon la boucle qui prend le plus de temps et qui est donc la plus importante est la dernière


Message édité par sligor le 24-09-2009 à 21:25:05
Reply

Marsh Posté le 24-09-2009 à 21:27:16    

en augmentant la taille des tableaux pour avoir qqch de plus significatif:
 


# gcc -lm -O3 -mfpmath=sse -msse2 -ffast-math cr.c -o cr && time ./cr
24.5u 0.0s 0:24.61 100.0% 0+0k 0+0io 0pf+0w
# javac cr.java && time java Cr
33.6u 12.2s 0:25.05 183.3% 0+0k 0+64io 1pf+0w


 
c'est quoi tout ce temps system en Java :??:
et pourquoi 183.3%? le temps system serait il execute en // ?

Reply

Marsh Posté le 24-09-2009 à 21:28:07    

enfin au final, Java 87% plus lent

Reply

Marsh Posté le 24-09-2009 à 21:32:51    

questions restantes:
 
- pourquoi tant de temps CPU en plus en Java par rapport a C?
- qu'est ce que ce temps systeme?
(optionnel)
- pourquoi 64io en Java et 0 en C? pourquoi 1pf en Java et 0 en C?

Reply

Marsh Posté le 24-09-2009 à 21:38:05    

A vue de nez ca ressemble à un optimisation de java (fais donc un htop pendant l'execution, tu vas comprendre).
 
D'ailleurs si tu regardes bien le rapport de time :  
C : 99.9 % (1 CPU)
Java : 194.5% (2 CPUs)
 
Après, quelle est cette optimisation...


---------------
Ce n'est point ma façon de penser qui a fait mon malheur, c'est celle des autres.
Reply

Marsh Posté le 24-09-2009 à 21:45:02    

entre parentheses, avec -mtune=native C est encore plus rapide (1s de moins que sans)
 


# gcc -lm -O3 -mfpmath=sse -msse2 -ffast-math -mtune=native cr.c -o cr          # time ./cr
23.5u 0.0s 0:23.62 100.0% 0+0k 0+0io 0pf+0w


 
>e_esprit
 
en theorie ce calcul est parfaitement parallelisable, c'est a dire que si j'ai 8 cpu je vais quasi 8 fois plus vite
maintenant, java est 80% plus lent mais utilise 2 CPU donc bat C sur la ligne d'arrivee... mais que fait il exactement, et pourquoi?

Reply

Marsh Posté le 24-09-2009 à 22:49:44    

Ayayaye, encore quelqu'un qui veut comparer des pommes et des oranges...
 
Bon alors c'est simple: quand tu lances programme Java, tu lances la JVM (un énorme programme très complexe) qui à son tour lance ton programme. Tu ne peux pas comparer le temps qu'il faut pour démarrer une JVM avec le temps qu'il faut pour démarrer un programme C déjà compilé qui est nul ou presque. La JVM est lourde à charger, c'est connu.
 
Si tu veux vraiment chronométrer un temps d'exécution, tu dois mesurer le temps d'exécution de l'application en récupérant une valeur sur l'horloge système juste avant et juste après le calcul (par exemple avec System.currentTimeMillis() même si ce n'est pas le plus précis), sinon tu vas mesurer également le temps qu'il faut pour démarrer et arrêter la JVM!
 
De plus, la JVM commence par interpréter puis compile nativement les parties du code les plus fréquemment utilisées au fil du temps (compilateur JIT intelligent), ce qui veut dire que la première exécution du calcul sera plus lente que la dernière si tu en fais plusieurs. Raison pour laquelle quand on fait des benchmarks Java, il est plus "fair play" de ne pas chronométrer la première exécution du code ni même la deuxième, mais plutôt la 3e ou 4e exécution (sans arrêter la JVM entre chaque exécution bien entendu!).
 
Tu devrais alors mesurer des performances quasi semblables avec sans doute un avantage pour le code C, en particulier parce que les calculs de type racine carrée, sinus et cosinus sont plus lents en Java qu'en C.
 
Enfin sache qu'il existe un JVM client et une JVM serveur (dont la disponibilité varie d'une plate-forme à l'autre) qui peuvent offrir des performances différentes.


Message édité par cbeyls le 24-09-2009 à 22:53:25
Reply

Marsh Posté le 24-09-2009 à 22:53:10    

faut pas délirer, sur 23s le temps de lancement de la JVM est très négligeable. De même pour le JIT, ici on execute une boucle plusieurs millions de fois


Message édité par sligor le 24-09-2009 à 22:53:20
Reply

Marsh Posté le 24-09-2009 à 22:58:43    

En tous cas la JVM explique les IO et le reste des différences avec le code C.
 
Effectivement si on exécute une racine carrée plusieurs millions de fois, alors que cette opération est connue pour être beaucoup plus lente en java qu'en C, ce n'est pas étonnant que ce soit très lent :)
 
Mais bon je pense que comparer du Java et du C pour savoir lequel est le "meilleur" n'a aucun sens. Comparer plusieurs JVM ou implémentations Java serait plus intéressant.
 
Tu pourrais aussi essayer le compilateur java natif GCJ versus GCC.

Reply

Marsh Posté le 24-09-2009 à 23:20:48    

>cbeyls
 
merci pour ces explications
le but ici n'etait pas de dire "qui ou qui est le plus rapide" (ca je crois qu'on le sait) mais "qui fait quoi et comment" pour expliquer les differences
et ton post m'eclaire sur certains points :jap:

Reply

Marsh Posté le 24-09-2009 à 23:36:35    

Content d'avoir éclairé ta lanterne!
 
Petite remarque quand même: le C n'est pas toujours le plus rapide contrairement à ce qu'on croit. En particulier, Java peut être plus efficace que du code C pour l'allocation de mémoire, la gestion des Exceptions et autres. Et avec chaque nouvelle version de Java, les performances sont améliorées.
 
Avant de me faire démolir par les pro-C, je tiens à préciser que j'aime et utilise de nombreux langages dont le Java et le C :)

Reply

Marsh Posté le 24-09-2009 à 23:38:13    

Quand les exceptions impactent les performances c'est qu'elle sont mal utilisées :o (valable pour n'importe quel langage)

Reply

Marsh Posté le 24-09-2009 à 23:39:26    

Joelf ! viens nous apprendre à parcourir une matrice !
 
(sinon je pow(x, 2) faut voir à faire soit même le * et zieuter l'asm, des fois c'est mieux).

Reply

Marsh Posté le 24-09-2009 à 23:43:04    

question subsidiaire:
 
qu'est ce que la JVM fait avec mon kernel pendant 30% du temps cpu?

Reply

Marsh Posté le 24-09-2009 à 23:43:39    

taz, que veux tu dire pour le parcours d'une matrice?

Reply

Marsh Posté le 24-09-2009 à 23:44:59    

cbeyls a écrit :


[...]
 
Mais bon je pense que comparer du Java et du C pour savoir lequel est le "meilleur" n'a aucun sens. Comparer plusieurs JVM ou implémentations Java serait plus intéressant.
 
Tu pourrais aussi essayer le compilateur java natif GCJ versus GCC.


 :jap:  
 
Le type de JVM et de compilo JIT va avoir une influence non négligeable en terme d'exécution de code, l'architecture utilisée joue pas mal aussi ; à ce niveau je pense plus particulièrement aux implémentations Zero/Shark vs Cacao.
 
Aujourd'hui, en matière de JDK, l'implémentation open source OpenJDK est aussi performante (si ce n'est plus performante) que celle de chez Sun.


---------------
THRAK (def.) : 1) A sudden and precise impact moving from intention, direction and commitment, in service of an aim. 2) 117 guitars almost striking the same chord simultaneously.
Reply

Marsh Posté le 24-09-2009 à 23:51:24    

mes tests ci-dessus sont OpenJDK+Hotspot
 
je vais regarder Zero/Shark et Cacao

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed