Я пытаюсь сравнить расчет Монте-Карло 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 вообще не может сделать этот расчет (это занимает вечность, и мой компьютер никогда не перестает обрабатывать этот файл).
Есть мудрость по этому поводу?
Я также подозреваю, что BeanShell и Jython работают только с объектами. Я также добавил стандартный Python для полноты (очень медленный)




Хорошо сделано. Интересное сравнение у вас получилось. Как разработчик 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 секунд).
Разработчик Jython предложил использовать xrange() вместо range(). Это не влияет на CPython. Но это ускоряет нас, Jython.
Для кода 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 секунды. Таким образом, это быстрее, чем любой другой пример кода в этом потоке.
Использование r.nextFloat() в Groovy изменит контрольные значения для Groovy с 3 до 2 секунд. Все-таки Юля в 2 раза быстрее.
Я предполагаю, что интерпретатор Jython не использует какие-либо примитивные типы значений, а только экземпляры объектов.