5 votes

Étalonnage de Java, Groovy, Jython et Python

J'essaie d'évaluer un calcul de Monte Carlo de PI (3.14159) en lançant des fléchettes. J'ai implémenté mon code en Java, Groovy, BeanShell, Julia, Jython et Python (Python2 implémenté en C).

Voici mon code Java original "MonteCarloPI.java" :

import java.util.Random; 

public class MonteCarloPI {
     public static void main(String[] args)
       {
         int nThrows = 0;
         int nSuccess = 0;
         double x, y;
         long then = System.nanoTime();
         int events=(int)1e8;
         Random r = new Random(); 
         for (int i = 0; i < events; i++) {
            x = r.nextFloat();      // Throw a dart
            y = r.nextFloat();
            nThrows++;
            if ( x*x + y*y <= 1 )  nSuccess++;
       }
 double itime = ((System.nanoTime() - then)/1e9);
 System.out.println("Time for calculations (sec): " + itime+"\n");
 System.out.println("Pi = " + 4*(double)nSuccess/(double)nThrows +"\n");
      }
}

Voici mon code Groovy dans le fichier "MonteCarloPI.groovy" :

import java.util.Random

int nThrows = 0
int nSuccess = 0
double x, y
long then = System.nanoTime()
int events=1e8
r = new Random()
for (int i = 0; i < events; i++) {
            x = r.nextFloat()      // Throw a dart
            y = r.nextFloat()
            nThrows++
            if ( x*x + y*y <= 1 )  nSuccess++
}
itime = ((System.nanoTime() - then)/1e9)
System.out.println("Time for calculations (sec): " + itime+"\n")
System.out.println("Pi = " + 4*(double)nSuccess/(double)nThrows +"\n")

Alternativement, j'ai supprimé les définitions telles que "float" et "int" (c'est-à-dire les types libres). Ceci permet de vérifier les performances en utilisant des types "loose".

J'ai renommé "MonteCarloPI.groovy" en un fichier BeanShell script "MonteCarloPI.bsh" (BeanShell a une syntaxe très similaire à Groovy)

Dans le cas du langage Python standard, le code "MonteCarloPI_CPython.py" ressemble à ceci :

import random,time

nThrows,nSuccess = 0,0
then = time.time()
events=int(1e8)
for i in xrange(events):
   x,y = random.random(),random.random();   # Throw a dart                   
   nThrows +=1
   if ( x*x + y*y <= 1 ):  nSuccess+=1
itime = time.time() - then
print ("Time: ",itime,"sec Pi = ",4*nSuccess/float(nThrows))

Ce code est exécuté soit en CPython 2.7.18 (Python implémenté en C), soit en Jython 2.7.2 (implémentation Java). Pour Python 3.8.3 ("Python3"), remplacez "xrange" par "range".

J'ai également implémenté le même algorithme dans JRuby (MonteCarloPI.rb) :

require "java"
java_import java.lang.System;
java_import java.util.Random;

nThrows = 0; nSuccess = 0;
xthen = System.nanoTime();
events=1e8;
r = Random.new();
for i  in 0 .. events do
  x = r.nextFloat();      #  Throw a dart
  y = r.nextFloat();
  nThrows +=1
   if ( x*x + y*y <= 1 )
                nSuccess += 1
  end
end
itime = (System.nanoTime() - xthen)/1e9;
xpi=(4.0*nSuccess)/nThrows
puts "Time for calculations (sec):  #{itime}"
puts "Pi = #{xpi}"

Voici le code utilisant Julia :

using Random
nThrows = 0
nSuccess = 0
events=1e8
then = time()
for j in 0:events
        x = rand();      #  Throw a dart
        y = rand();
        global  nThrows += 1;
        if  x*x + y*y <= 1
                 global nSuccess += 1;
        end
end
itime = time() - then
println( "Time for calculations (sec):", itime, " sec")
println( "Pi = ", 4.0*nSuccess/float(nThrows) )

J'ai exécuté "MonteCarloPI.java", "MonteCarloPI.groovy", "MonteCarloPI.py", "MonteCarloPI.bsh" et MonteCarloPI.rb à l'intérieur. DataMelt éditeur. Le code julia a été traité en utilisant julia-1.5.0/bin installé localement.

Voici les résultats du benchmark sur un CPU Intel(R) Core(TM) i5-4690K @ 3.50GHz (ubuntu 20.04, 8 GB de mémoire), avec 2048 MB alloués pour JDK9 lors de l'exécution de code Groovy, Jython, BeanShell :

Java   code:   1.7 sec Pi = 3.14176584  -> executed in DataMelt/JDK9
Groovy code:   2.1 sec Pi = 3.14144832  -> executed in DataMelt/JDK9
Groovy code:   18 sec Pi = 3.14141132  -> same but with "loose" types 
Julia code:    15 sec Pi = 3.14156104  -> executed in julia-1.5.0
Python code:   24 sec Pi = 3.14188036  -> executed in CPython 2.7.18
Python code:   30 sec Pi = 3.14188230  -> executed in CPython 3.2.8
Python code:    3 sec Pi = 3.14188036  -> executed using PyPy
Jython code:   24 sec Pi = 3.14187860  -> executed in DataMelt/JDK9
JRuby  code:   25 sec Pi = 3.14187860  -> executed in DataMelt/JDK9
BeanShell code: takes forever?!       -> executed in DataMelt/JDK9

Comme vous pouvez le constater, les calculs Java et Groovy prennent à peu près le même temps (environ 2 secondes). Avec des types lâches dans Groovy, l'exécution est 9 fois plus lente. Python est un facteur 12 plus lent que Java et Groovy. Python3 est encore plus lent. JRuby est aussi lent que Python. PyPy est plutôt rapide (mais plus lent que Java/Groovy). Mais BeanShell ne peut pas du tout faire ce calcul (cela prend une éternité, et mon ordinateur n'arrête jamais de traiter ce fichier).

Un avis sur la question ?

2voto

Beau travail. La comparaison que vous avez faite est intéressante. En tant que développeur Python, j'aimerais ajouter un point de vue supplémentaire sur Python.

Je suppose qu'il est plus lent principalement à cause du typage dynamique. Une autre raison est que vous calculez des valeurs scalaires (c'est-à-dire que vous utilisez la boucle for et calculez un nombre à la fois). L'un des avantages de Python est le calcul vectoriel grâce à la bibliothèque NumPy (qui permet de calculer plusieurs nombres en même temps). Voici donc mon implémentation de l'algorithme. Note : J'utilise Python 3.6.

import numpy as np
import time

start = time.time()

events = int(1e8)
nThrows, nSuccess = 0, 0

x, y = np.random.uniform(size=(2, events))
nSuccess = (x*x + y*y <= 1).sum()
nThrows = events
pi = 4*nSuccess/float(nThrows)

stop = time.time()
print('Time: {}, Pi = {}'.format(stop-start, pi))

Voici les résultats du benchmark sur mon ordinateur i7 x64 (Windows 10) :

Python (original code):      42.6s  Pi = 3.1414672
Python (my optimized code):  4.7s   Pi = 3.1417642

Comme vous pouvez le constater, le code python original exécuté sur mon ordinateur est plus lent que le code python exécuté sur votre ordinateur. Ainsi, la version optimisée pourrait être encore plus rapide que Java ou Groovy.

J'espère que cela vous aidera.

1voto

Jan Strube Points 11

Pour le code Julia, vous incluez le temps de compilation dans votre benchmark. L'autre chose à noter est que vous utilisez des variables globales, ce qui est bien connu pour tuer les performances. Avec votre version de base, le temps d'exécution sur ma machine est de 17,7 secondes. En déplaçant tout dans une fonction, je descends à 0,83 secondes. En enlevant le temps de compilation, je suis descendu à 713,625 ms. La version finale de mon code est la suivante (notez que vous avez compté un de trop dans votre boucle).

using Random
using BenchmarkTools

function benchmark()
    nThrows = 0
    nSuccess = 0
    events = 100_000_000
    for j in 1:events
            x = rand()      #  Throw a dart
            y = rand()
            nThrows += 1
            if  x^2 + y^2 <= 1
                nSuccess += 1
            end
    end
    4.0*nSuccess/nThrows
end

pi = @btime benchmark()
println( "Pi = ",  pi)

Notez que d'autres améliorations sont possibles. Il pourrait être bénéfique d'allouer un tableau de nombres aléatoires en dehors de la boucle plutôt que d'appeler rand deux fois par itération. Vous trouverez d'autres conseils sur les performances ici : https://docs.julialang.org/en/v1/manual/performance-tips/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X