Сравнение Java, Groovy, Jython и Python

Я пытаюсь сравнить расчет Монте-Карло PI (3,14159), бросая дротики. Я реализовал свой код на Java, Groovy, BeanShell, Julia, Jython и Python (Python2 реализован на C).

Вот мой исходный код Java "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");
      }
}

Вот мой код Groovy в файле "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")
       

В качестве альтернативы я удалил такие определения, как «float» и «int» (т. е. свободные типы). Это проверяет производительность с использованием «свободных» типов.

Я переименовал «MonteCarloPI.groovy» в файл сценария BeanShell «MonteCarloPI.bsh» (синтаксис BeanShell очень похож на Groovy).

В случае стандартного языка Python код «MonteCarloPI_CPython.py» выглядит так:

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))

Этот код выполняется либо в CPython 2.7.18 (Python реализован на C), либо в Jython 2.7.2 (Java-реализация). Для Python 3.8.3 ("Python3") замените "xrange" на "range".

Я также реализовал тот же алгоритм в 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}"

Вот код с использованием Юлии:

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) )

Я запустил «MonteCarloPI.java», «MonteCarloPI.groovy», «MonteCarloPI.py», «MonteCarloPI.bsh» и MonteCarloPI.rb в редакторе ДатаМелт. Код julia был обработан с помощью локально установленной julia-1.5.0/bin.

Вот результаты тестов на процессоре Intel(R) Core(TM) i5-4690K с тактовой частотой 3,50 ГГц (ubuntu 20.04, 8 ГБ памяти) с 2048 МБ, выделенными для JDK9 при запуске кода 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

Как видите, вычисления Java и Groovy занимают примерно одинаковое время (около 2 секунд). С незакрепленными типами в Groovy выполнение происходит в 9 раз медленнее. Python в 12 раз медленнее, чем Java и Groovy. Python3 еще медленнее. JRuby такой же медленный, как Python. PyPy довольно быстр (но медленнее, чем Java/Groovy). Но BeanShell вообще не может сделать этот расчет (это занимает вечность, и мой компьютер никогда не перестает обрабатывать этот файл).

Есть мудрость по этому поводу?

Я предполагаю, что интерпретатор Jython не использует какие-либо примитивные типы значений, а только экземпляры объектов.

emilles 21.01.2019 03:16

Я также подозреваю, что BeanShell и Jython работают только с объектами. Я также добавил стандартный Python для полноты (очень медленный)

user7975996 21.01.2019 03:28
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
2
2 401
2

Ответы 2

Хорошо сделано. Интересное сравнение у вас получилось. Как разработчик Python, я хотел бы добавить некоторые дополнительные сведения о Python.

Я предполагаю, что это медленнее в основном из-за динамической типизации. Другая причина заключается в том, что вы вычисляете скалярные значения (т. е. используете цикл for и вычисляете одно число за раз). Одним из преимуществ Python являются векторные вычисления с использованием библиотеки NumPy (это позволяет вычислять несколько чисел одновременно). Итак, вот моя реализация алгоритма. Примечание. Я использую 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))

Вот результаты тестов на моем компьютере i7 x64 (Windows 10):

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

Как видите, исходный код Python, работающий на моем компьютере, работает медленнее, чем код Python на вашем компьютере. Таким образом, оптимизированная версия может быть даже быстрее, чем Java или Groovy.

Надеюсь это поможет.

Спасибо! Я подозреваю, что аналогичный трюк можно сделать, вызвав какую-нибудь библиотеку Java из Jython. Кстати, это обсуждение вызвало ветку Reddit reddit.com/r/Python/comments/aiu0ak. Я узнал, что Groovy может быть в 3 раза медленнее, используя переменные со свободной типизацией (в отличие от приведенного выше кода). Однако pypy работает так же быстро, как Groovy и Java (около 3 секунд).

user7975996 24.01.2019 01:51

Разработчик Jython предложил использовать xrange() вместо range(). Это не влияет на CPython. Но это ускоряет нас, Jython.

user7975996 25.01.2019 01:28

Для кода Julia вы включаете время компиляции в свой тест. Еще одна вещь, которую следует отметить, это то, что вы используете глобальные переменные, которые, как известно, убивают производительность. С вашей базовой версией время выполнения на моей машине составляет 17,7 секунды. Перемещая все в функцию, я получаю 0,83 секунды. Удаление времени компиляции из этого приводит к 713,625 мс. Окончательная версия моего кода такова (обратите внимание, что вы насчитали слишком много в своем цикле).

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)

Обратите внимание, что возможны дальнейшие улучшения. Было бы полезно выделить массив случайных чисел вне цикла, а не вызывать rand дважды за итерацию. Другие советы по повышению эффективности можно найти здесь: https://docs.julialang.org/en/v1/manual/performance-tips/.

Привет, Ян. Я изменил код без «BenchmarkTools» и подтверждаю, что время выполнения составляет 0,74 секунды. Таким образом, это быстрее, чем любой другой пример кода в этом потоке.

user7975996 17.08.2020 02:12

Использование r.nextFloat() в Groovy изменит контрольные значения для Groovy с 3 до 2 секунд. Все-таки Юля в 2 раза быстрее.

user7975996 17.08.2020 02:35

Другие вопросы по теме